@sublang/playbook 12.0.0 → 12.2.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/embedding.md +83 -1
- package/package.json +7 -1
- package/reference/sdlc/code.playbook/bin/repository-effects.js +501 -170
- package/reference/sdlc/code.playbook/bin/session-store.js +4 -1
- package/reference/sdlc/code.playbook/host-capabilities.d.ts +291 -0
- package/reference/sdlc/code.playbook/host-capabilities.js +40 -0
- package/reference/sdlc/code.playbook/playbook-captain.js +10 -0
- package/reference/sdlc/code.playbook/playbook-captain.ts +9 -0
- package/reference/sdlc/decide.playbook/decide.playbook.js +11 -7
- package/reference/sdlc/decide.playbook/decide.playbook.ts +11 -8
- package/slc/gears2fsm.md +20 -0
- package/slc/link.md +28 -1
- package/slc/text2gears.md +19 -1
- package/src/xstate-playbook-runtime.d.ts +15 -0
- package/src/xstate-playbook-runtime.js +127 -22
- package/src/xstate-playbook-runtime.ts +149 -24
|
@@ -568,6 +568,30 @@ export function defaultComposeCaptainPrompt(input, placeholderFields = {}) {
|
|
|
568
568
|
blocks.push(body);
|
|
569
569
|
return blocks.join('\n\n');
|
|
570
570
|
}
|
|
571
|
+
// A result description's output clause (slc/link.md §Captain adjudication):
|
|
572
|
+
// everything after `Output shall include` / `输出应包含` names the outcome's
|
|
573
|
+
// payload fields, each as a bare backticked name or the annotated
|
|
574
|
+
// `name: <placeholder>` form. The text before the clause is the outcome's
|
|
575
|
+
// meaning.
|
|
576
|
+
const OUTPUT_CLAUSE_MARKERS = [
|
|
577
|
+
'Output shall include',
|
|
578
|
+
'输出应包含',
|
|
579
|
+
];
|
|
580
|
+
const PAYLOAD_FIELD_PATTERN = /`([A-Za-z_$][A-Za-z0-9_$]*)(?::\s*([^`]*))?`/g;
|
|
581
|
+
// Separators between one field's authored segment and the next field token.
|
|
582
|
+
const SEGMENT_SEPARATOR_SUFFIX = /(?:\s|[,;.,、;。]|\band\b)+$/;
|
|
583
|
+
function splitOutputClause(description) {
|
|
584
|
+
for (const marker of OUTPUT_CLAUSE_MARKERS) {
|
|
585
|
+
const idx = description.indexOf(marker);
|
|
586
|
+
if (idx !== -1) {
|
|
587
|
+
return {
|
|
588
|
+
meaning: description.slice(0, idx).trim(),
|
|
589
|
+
clause: description.slice(idx + marker.length),
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return { meaning: description.trim() };
|
|
594
|
+
}
|
|
571
595
|
/**
|
|
572
596
|
* Default required-field extraction (slc/link.md §Captain adjudication).
|
|
573
597
|
* Limited to the description's `Output shall include` / `输出应包含` clause;
|
|
@@ -575,21 +599,11 @@ export function defaultComposeCaptainPrompt(input, placeholderFields = {}) {
|
|
|
575
599
|
* form.
|
|
576
600
|
*/
|
|
577
601
|
export function defaultExtractRequiredFields(description) {
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
for (const marker of markers) {
|
|
581
|
-
const idx = description.indexOf(marker);
|
|
582
|
-
if (idx !== -1) {
|
|
583
|
-
clauseStart = idx + marker.length;
|
|
584
|
-
break;
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
if (clauseStart === -1)
|
|
602
|
+
const { clause } = splitOutputClause(description);
|
|
603
|
+
if (clause === undefined)
|
|
588
604
|
return [];
|
|
589
|
-
const clause = description.slice(clauseStart);
|
|
590
605
|
const fields = [];
|
|
591
|
-
const
|
|
592
|
-
for (const m of clause.matchAll(re))
|
|
606
|
+
for (const m of clause.matchAll(PAYLOAD_FIELD_PATTERN))
|
|
593
607
|
fields.push(m[1]);
|
|
594
608
|
return fields;
|
|
595
609
|
}
|
|
@@ -615,6 +629,71 @@ export function defaultBuildJudgePrompt(input, finalText) {
|
|
|
615
629
|
}
|
|
616
630
|
return lines.join('\n');
|
|
617
631
|
}
|
|
632
|
+
/**
|
|
633
|
+
* Judge-facing rendering of one governed outcome (DR-040 §1). The artifact's
|
|
634
|
+
* description is not altered: its meaning is carried through verbatim, while
|
|
635
|
+
* its `Output shall include` clause — authored for the complete actor output
|
|
636
|
+
* — is replaced by the reply contract `outcomeAuthority` gives the judge:
|
|
637
|
+
* exactly `guard` plus the outcome's semantic-owned fields, each keeping the
|
|
638
|
+
* placeholder or guidance the clause authors for it, and every
|
|
639
|
+
* presentation-, effect-, or runtime-owned field named as runtime-supplied
|
|
640
|
+
* so the judge omits it. Rendering the clause verbatim asked the judge for
|
|
641
|
+
* `question`, `planningResult`, or `evaluatedRevision`, which the reconciler
|
|
642
|
+
* rejects as a structural error, spending the single correction on a
|
|
643
|
+
* self-inflicted defect. Exported so a bespoke linked runtime (DECIDE's
|
|
644
|
+
* parallel machinery) renders the identical contract instead of restating it.
|
|
645
|
+
*/
|
|
646
|
+
export function renderGovernedOutcomeContract(guard, description, outcome) {
|
|
647
|
+
const { meaning, clause } = splitOutputClause(description);
|
|
648
|
+
// Each field's authored segment runs from its token to the next token:
|
|
649
|
+
// the annotated `name: <placeholder>` form, or the bare name followed by
|
|
650
|
+
// its guidance (`` `irNumber` identifying the continued IR ``).
|
|
651
|
+
const authored = new Map();
|
|
652
|
+
if (clause !== undefined) {
|
|
653
|
+
const matches = [...clause.matchAll(PAYLOAD_FIELD_PATTERN)];
|
|
654
|
+
matches.forEach((m, i) => {
|
|
655
|
+
const field = m[1];
|
|
656
|
+
if (authored.has(field))
|
|
657
|
+
return;
|
|
658
|
+
const start = m.index ?? 0;
|
|
659
|
+
const end = matches[i + 1]?.index ?? clause.length;
|
|
660
|
+
const segment = clause
|
|
661
|
+
.slice(start, end)
|
|
662
|
+
.replace(SEGMENT_SEPARATOR_SUFFIX, '');
|
|
663
|
+
const placeholder = m[2]?.trim();
|
|
664
|
+
authored.set(field, {
|
|
665
|
+
segment,
|
|
666
|
+
...(placeholder !== undefined && placeholder.length > 0
|
|
667
|
+
? { placeholder }
|
|
668
|
+
: {}),
|
|
669
|
+
});
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
const replyMembers = [`"guard": ${JSON.stringify(guard)}`];
|
|
673
|
+
const semanticAsAuthored = [];
|
|
674
|
+
const runtimeSupplied = [];
|
|
675
|
+
for (const [field, authority] of Object.entries(outcome?.fields ?? {})) {
|
|
676
|
+
if (authority === 'semantic') {
|
|
677
|
+
const entry = authored.get(field);
|
|
678
|
+
replyMembers.push(`${JSON.stringify(field)}: ${entry?.placeholder ?? '<string>'}`);
|
|
679
|
+
semanticAsAuthored.push(entry?.segment ?? `\`${field}\``);
|
|
680
|
+
}
|
|
681
|
+
else {
|
|
682
|
+
runtimeSupplied.push(`\`${field}\` (${authority}-owned)`);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
const lines = [
|
|
686
|
+
meaning.length === 0 ? `- \`${guard}\`` : `- \`${guard}\` — ${meaning}`,
|
|
687
|
+
` Reply exactly: { ${replyMembers.join(', ')} }`,
|
|
688
|
+
];
|
|
689
|
+
if (semanticAsAuthored.length > 0) {
|
|
690
|
+
lines.push(` Semantic fields as authored: ${semanticAsAuthored.join('; ')}`);
|
|
691
|
+
}
|
|
692
|
+
if (runtimeSupplied.length > 0) {
|
|
693
|
+
lines.push(` Runtime-supplied, do not include: ${runtimeSupplied.join(', ')}`);
|
|
694
|
+
}
|
|
695
|
+
return lines;
|
|
696
|
+
}
|
|
618
697
|
function buildGovernedJudgePrompt(input, finalText, outcomes, correction) {
|
|
619
698
|
const lines = [
|
|
620
699
|
'This is hidden control work. Do not call tools, inspect files, or seek external evidence.',
|
|
@@ -627,17 +706,12 @@ function buildGovernedJudgePrompt(input, finalText, outcomes, correction) {
|
|
|
627
706
|
finalText,
|
|
628
707
|
'```',
|
|
629
708
|
'',
|
|
630
|
-
'Pick exactly one declared `guard
|
|
631
|
-
'
|
|
709
|
+
'Pick exactly one declared `guard` and reply with exactly that outcome\'s reply shape below: `guard` plus its semantic-owned fields and nothing else.',
|
|
710
|
+
'A field listed as runtime-supplied is owned by presentation, effect, or runtime evidence; the runtime fills it itself, and a reply that includes one is structurally invalid.',
|
|
632
711
|
'',
|
|
633
712
|
];
|
|
634
713
|
for (const [guard, description] of Object.entries(input.result)) {
|
|
635
|
-
|
|
636
|
-
.filter(([, authority]) => authority === 'semantic')
|
|
637
|
-
.map(([field]) => field);
|
|
638
|
-
lines.push(`- \`${guard}\` — semantic fields: ${semanticFields.length === 0
|
|
639
|
-
? '(none)'
|
|
640
|
-
: semanticFields.map((field) => `\`${field}\``).join(', ')}; ${description}`);
|
|
714
|
+
lines.push(...renderGovernedOutcomeContract(guard, description, outcomes[guard]));
|
|
641
715
|
}
|
|
642
716
|
if (correction !== undefined) {
|
|
643
717
|
lines.push('', 'Your first reply was structurally invalid:', '', '```', correction.reply, '```', '', `Validation error: ${correction.error}`, 'Correct only that structure using the same player output and outcome schema.');
|
|
@@ -2080,9 +2154,24 @@ export function createXStatePlaybookRuntime(machine, spec) {
|
|
|
2080
2154
|
return undefined;
|
|
2081
2155
|
}
|
|
2082
2156
|
}
|
|
2157
|
+
// DR-040 §4 parks only an envelope whose evidence proves a repository
|
|
2158
|
+
// delta or cannot exclude one. A standalone boundary whose complete
|
|
2159
|
+
// physical receipt is exactly `unchanged` excludes any effect, so missing
|
|
2160
|
+
// or unresolved semantics over it are an ordinary failure — the FSM's
|
|
2161
|
+
// failure state with its fenced retry — never a parked reconciliation:
|
|
2162
|
+
// that state's only exits, reconcile and abandon, project no effect
|
|
2163
|
+
// evidence from an `unchanged` receipt, so parking it deadlocks the
|
|
2164
|
+
// engagement. A boundary inside a deferred chain is judged by the chain's
|
|
2165
|
+
// cumulative receipt from its original baseline, not by its own step.
|
|
2166
|
+
function boundaryExcludesEffect(boundary) {
|
|
2167
|
+
return (boundary.logicalOperationId === undefined &&
|
|
2168
|
+
boundary.physicalReceipt?.classification === 'unchanged');
|
|
2169
|
+
}
|
|
2083
2170
|
function boundaryNeedsSemanticReconciliation(candidate, ledger) {
|
|
2084
2171
|
if (!runtimeBoundaryIsOwned(candidate))
|
|
2085
2172
|
return false;
|
|
2173
|
+
if (boundaryExcludesEffect(candidate))
|
|
2174
|
+
return false;
|
|
2086
2175
|
if (governedOutcomesForBoundary(candidate) === undefined)
|
|
2087
2176
|
return true;
|
|
2088
2177
|
const persisted = persistedBoundaryReconciliation(candidate, ledger);
|
|
@@ -3110,7 +3199,11 @@ export function createXStatePlaybookRuntime(machine, spec) {
|
|
|
3110
3199
|
if (governedSettlement !== undefined) {
|
|
3111
3200
|
governedSettlementsByBoundaryId.delete(boundaryId);
|
|
3112
3201
|
governedPlayerSettlements.set(result, governedSettlement);
|
|
3113
|
-
|
|
3202
|
+
// An unresolved settlement over an `unchanged` standalone receipt
|
|
3203
|
+
// still throws into the failure state through the player bridge; it
|
|
3204
|
+
// just never becomes an effect-possible envelope.
|
|
3205
|
+
if (governedSettlement.status === 'unresolved' &&
|
|
3206
|
+
!boundaryExcludesEffect(completed)) {
|
|
3114
3207
|
unresolvedSemanticBoundaryIds.add(boundaryId);
|
|
3115
3208
|
}
|
|
3116
3209
|
else {
|
|
@@ -4830,6 +4923,10 @@ export function createXStatePlaybookRuntime(machine, spec) {
|
|
|
4830
4923
|
delivery: deferredValue(),
|
|
4831
4924
|
};
|
|
4832
4925
|
activeDeferredContinuation = continuation;
|
|
4926
|
+
// Emissions are buffered only until the host durably starts the
|
|
4927
|
+
// continuation: an exit that starts no player (checkpoint mismatch,
|
|
4928
|
+
// ineligible operation) publishes nothing, so the bound wait it
|
|
4929
|
+
// preserves is never contradicted by a classification line.
|
|
4833
4930
|
deferInspectionEmissions = true;
|
|
4834
4931
|
let continuationStarted = false;
|
|
4835
4932
|
let deliverySettled = false;
|
|
@@ -4858,6 +4955,14 @@ export function createXStatePlaybookRuntime(machine, spec) {
|
|
|
4858
4955
|
continuation.playerContinuation = selectedContinuation;
|
|
4859
4956
|
continuationStarted = true;
|
|
4860
4957
|
actor.send(event);
|
|
4958
|
+
// The host has durably started this boundary and the FSM has
|
|
4959
|
+
// moved: publish the buffered classification line and the
|
|
4960
|
+
// authored transition now, and let every later transition,
|
|
4961
|
+
// accepted outcome, and status emit inline. Holding them until
|
|
4962
|
+
// the operation settled put the cause after its effects — the
|
|
4963
|
+
// player call, the nested call a target state starts, and their
|
|
4964
|
+
// finishes were traced and sequenced first (PBRT-37).
|
|
4965
|
+
settleDeferredInspectionBuffer(true);
|
|
4861
4966
|
// The invoked player remains gated inside boundary.callPlayer.
|
|
4862
4967
|
// Return to the host only after the raw player call settles so it
|
|
4863
4968
|
// can capture and persist the receipt before any actor output or
|
|
@@ -1304,6 +1304,36 @@ export function defaultComposeCaptainPrompt(
|
|
|
1304
1304
|
return blocks.join('\n\n');
|
|
1305
1305
|
}
|
|
1306
1306
|
|
|
1307
|
+
// A result description's output clause (slc/link.md §Captain adjudication):
|
|
1308
|
+
// everything after `Output shall include` / `输出应包含` names the outcome's
|
|
1309
|
+
// payload fields, each as a bare backticked name or the annotated
|
|
1310
|
+
// `name: <placeholder>` form. The text before the clause is the outcome's
|
|
1311
|
+
// meaning.
|
|
1312
|
+
const OUTPUT_CLAUSE_MARKERS: readonly string[] = [
|
|
1313
|
+
'Output shall include',
|
|
1314
|
+
'输出应包含',
|
|
1315
|
+
];
|
|
1316
|
+
const PAYLOAD_FIELD_PATTERN =
|
|
1317
|
+
/`([A-Za-z_$][A-Za-z0-9_$]*)(?::\s*([^`]*))?`/g;
|
|
1318
|
+
// Separators between one field's authored segment and the next field token.
|
|
1319
|
+
const SEGMENT_SEPARATOR_SUFFIX = /(?:\s|[,;.,、;。]|\band\b)+$/;
|
|
1320
|
+
|
|
1321
|
+
function splitOutputClause(description: string): {
|
|
1322
|
+
readonly meaning: string;
|
|
1323
|
+
readonly clause?: string;
|
|
1324
|
+
} {
|
|
1325
|
+
for (const marker of OUTPUT_CLAUSE_MARKERS) {
|
|
1326
|
+
const idx = description.indexOf(marker);
|
|
1327
|
+
if (idx !== -1) {
|
|
1328
|
+
return {
|
|
1329
|
+
meaning: description.slice(0, idx).trim(),
|
|
1330
|
+
clause: description.slice(idx + marker.length),
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
return { meaning: description.trim() };
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1307
1337
|
/**
|
|
1308
1338
|
* Default required-field extraction (slc/link.md §Captain adjudication).
|
|
1309
1339
|
* Limited to the description's `Output shall include` / `输出应包含` clause;
|
|
@@ -1311,20 +1341,10 @@ export function defaultComposeCaptainPrompt(
|
|
|
1311
1341
|
* form.
|
|
1312
1342
|
*/
|
|
1313
1343
|
export function defaultExtractRequiredFields(description: string): string[] {
|
|
1314
|
-
const
|
|
1315
|
-
|
|
1316
|
-
for (const marker of markers) {
|
|
1317
|
-
const idx = description.indexOf(marker);
|
|
1318
|
-
if (idx !== -1) {
|
|
1319
|
-
clauseStart = idx + marker.length;
|
|
1320
|
-
break;
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
|
-
if (clauseStart === -1) return [];
|
|
1324
|
-
const clause = description.slice(clauseStart);
|
|
1344
|
+
const { clause } = splitOutputClause(description);
|
|
1345
|
+
if (clause === undefined) return [];
|
|
1325
1346
|
const fields: string[] = [];
|
|
1326
|
-
const
|
|
1327
|
-
for (const m of clause.matchAll(re)) fields.push(m[1]);
|
|
1347
|
+
for (const m of clause.matchAll(PAYLOAD_FIELD_PATTERN)) fields.push(m[1]);
|
|
1328
1348
|
return fields;
|
|
1329
1349
|
}
|
|
1330
1350
|
|
|
@@ -1358,6 +1378,83 @@ export function defaultBuildJudgePrompt(
|
|
|
1358
1378
|
return lines.join('\n');
|
|
1359
1379
|
}
|
|
1360
1380
|
|
|
1381
|
+
/**
|
|
1382
|
+
* Judge-facing rendering of one governed outcome (DR-040 §1). The artifact's
|
|
1383
|
+
* description is not altered: its meaning is carried through verbatim, while
|
|
1384
|
+
* its `Output shall include` clause — authored for the complete actor output
|
|
1385
|
+
* — is replaced by the reply contract `outcomeAuthority` gives the judge:
|
|
1386
|
+
* exactly `guard` plus the outcome's semantic-owned fields, each keeping the
|
|
1387
|
+
* placeholder or guidance the clause authors for it, and every
|
|
1388
|
+
* presentation-, effect-, or runtime-owned field named as runtime-supplied
|
|
1389
|
+
* so the judge omits it. Rendering the clause verbatim asked the judge for
|
|
1390
|
+
* `question`, `planningResult`, or `evaluatedRevision`, which the reconciler
|
|
1391
|
+
* rejects as a structural error, spending the single correction on a
|
|
1392
|
+
* self-inflicted defect. Exported so a bespoke linked runtime (DECIDE's
|
|
1393
|
+
* parallel machinery) renders the identical contract instead of restating it.
|
|
1394
|
+
*/
|
|
1395
|
+
export function renderGovernedOutcomeContract(
|
|
1396
|
+
guard: string,
|
|
1397
|
+
description: string,
|
|
1398
|
+
outcome: XStateGovernedOutcomeSpec | undefined,
|
|
1399
|
+
): string[] {
|
|
1400
|
+
const { meaning, clause } = splitOutputClause(description);
|
|
1401
|
+
// Each field's authored segment runs from its token to the next token:
|
|
1402
|
+
// the annotated `name: <placeholder>` form, or the bare name followed by
|
|
1403
|
+
// its guidance (`` `irNumber` identifying the continued IR ``).
|
|
1404
|
+
const authored = new Map<
|
|
1405
|
+
string,
|
|
1406
|
+
{ readonly segment: string; readonly placeholder?: string }
|
|
1407
|
+
>();
|
|
1408
|
+
if (clause !== undefined) {
|
|
1409
|
+
const matches = [...clause.matchAll(PAYLOAD_FIELD_PATTERN)];
|
|
1410
|
+
matches.forEach((m, i) => {
|
|
1411
|
+
const field = m[1];
|
|
1412
|
+
if (authored.has(field)) return;
|
|
1413
|
+
const start = m.index ?? 0;
|
|
1414
|
+
const end = matches[i + 1]?.index ?? clause.length;
|
|
1415
|
+
const segment = clause
|
|
1416
|
+
.slice(start, end)
|
|
1417
|
+
.replace(SEGMENT_SEPARATOR_SUFFIX, '');
|
|
1418
|
+
const placeholder = m[2]?.trim();
|
|
1419
|
+
authored.set(field, {
|
|
1420
|
+
segment,
|
|
1421
|
+
...(placeholder !== undefined && placeholder.length > 0
|
|
1422
|
+
? { placeholder }
|
|
1423
|
+
: {}),
|
|
1424
|
+
});
|
|
1425
|
+
});
|
|
1426
|
+
}
|
|
1427
|
+
const replyMembers = [`"guard": ${JSON.stringify(guard)}`];
|
|
1428
|
+
const semanticAsAuthored: string[] = [];
|
|
1429
|
+
const runtimeSupplied: string[] = [];
|
|
1430
|
+
for (const [field, authority] of Object.entries(outcome?.fields ?? {})) {
|
|
1431
|
+
if (authority === 'semantic') {
|
|
1432
|
+
const entry = authored.get(field);
|
|
1433
|
+
replyMembers.push(
|
|
1434
|
+
`${JSON.stringify(field)}: ${entry?.placeholder ?? '<string>'}`,
|
|
1435
|
+
);
|
|
1436
|
+
semanticAsAuthored.push(entry?.segment ?? `\`${field}\``);
|
|
1437
|
+
} else {
|
|
1438
|
+
runtimeSupplied.push(`\`${field}\` (${authority}-owned)`);
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
const lines = [
|
|
1442
|
+
meaning.length === 0 ? `- \`${guard}\`` : `- \`${guard}\` — ${meaning}`,
|
|
1443
|
+
` Reply exactly: { ${replyMembers.join(', ')} }`,
|
|
1444
|
+
];
|
|
1445
|
+
if (semanticAsAuthored.length > 0) {
|
|
1446
|
+
lines.push(
|
|
1447
|
+
` Semantic fields as authored: ${semanticAsAuthored.join('; ')}`,
|
|
1448
|
+
);
|
|
1449
|
+
}
|
|
1450
|
+
if (runtimeSupplied.length > 0) {
|
|
1451
|
+
lines.push(
|
|
1452
|
+
` Runtime-supplied, do not include: ${runtimeSupplied.join(', ')}`,
|
|
1453
|
+
);
|
|
1454
|
+
}
|
|
1455
|
+
return lines;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1361
1458
|
function buildGovernedJudgePrompt(
|
|
1362
1459
|
input: PlaybookPlayerInput,
|
|
1363
1460
|
finalText: string,
|
|
@@ -1375,20 +1472,13 @@ function buildGovernedJudgePrompt(
|
|
|
1375
1472
|
finalText,
|
|
1376
1473
|
'```',
|
|
1377
1474
|
'',
|
|
1378
|
-
'Pick exactly one declared `guard
|
|
1379
|
-
'
|
|
1475
|
+
'Pick exactly one declared `guard` and reply with exactly that outcome\'s reply shape below: `guard` plus its semantic-owned fields and nothing else.',
|
|
1476
|
+
'A field listed as runtime-supplied is owned by presentation, effect, or runtime evidence; the runtime fills it itself, and a reply that includes one is structurally invalid.',
|
|
1380
1477
|
'',
|
|
1381
1478
|
];
|
|
1382
1479
|
for (const [guard, description] of Object.entries(input.result)) {
|
|
1383
|
-
const semanticFields = Object.entries(outcomes[guard]?.fields ?? {})
|
|
1384
|
-
.filter(([, authority]) => authority === 'semantic')
|
|
1385
|
-
.map(([field]) => field);
|
|
1386
1480
|
lines.push(
|
|
1387
|
-
|
|
1388
|
-
semanticFields.length === 0
|
|
1389
|
-
? '(none)'
|
|
1390
|
-
: semanticFields.map((field) => `\`${field}\``).join(', ')
|
|
1391
|
-
}; ${description}`,
|
|
1481
|
+
...renderGovernedOutcomeContract(guard, description, outcomes[guard]),
|
|
1392
1482
|
);
|
|
1393
1483
|
}
|
|
1394
1484
|
if (correction !== undefined) {
|
|
@@ -3480,11 +3570,28 @@ export function createXStatePlaybookRuntime<
|
|
|
3480
3570
|
}
|
|
3481
3571
|
}
|
|
3482
3572
|
|
|
3573
|
+
// DR-040 §4 parks only an envelope whose evidence proves a repository
|
|
3574
|
+
// delta or cannot exclude one. A standalone boundary whose complete
|
|
3575
|
+
// physical receipt is exactly `unchanged` excludes any effect, so missing
|
|
3576
|
+
// or unresolved semantics over it are an ordinary failure — the FSM's
|
|
3577
|
+
// failure state with its fenced retry — never a parked reconciliation:
|
|
3578
|
+
// that state's only exits, reconcile and abandon, project no effect
|
|
3579
|
+
// evidence from an `unchanged` receipt, so parking it deadlocks the
|
|
3580
|
+
// engagement. A boundary inside a deferred chain is judged by the chain's
|
|
3581
|
+
// cumulative receipt from its original baseline, not by its own step.
|
|
3582
|
+
function boundaryExcludesEffect(boundary: PlaybookEffectBoundary): boolean {
|
|
3583
|
+
return (
|
|
3584
|
+
boundary.logicalOperationId === undefined &&
|
|
3585
|
+
boundary.physicalReceipt?.classification === 'unchanged'
|
|
3586
|
+
);
|
|
3587
|
+
}
|
|
3588
|
+
|
|
3483
3589
|
function boundaryNeedsSemanticReconciliation(
|
|
3484
3590
|
candidate: PlaybookEffectBoundary,
|
|
3485
3591
|
ledger: PlaybookEffectLedger,
|
|
3486
3592
|
): boolean {
|
|
3487
3593
|
if (!runtimeBoundaryIsOwned(candidate)) return false;
|
|
3594
|
+
if (boundaryExcludesEffect(candidate)) return false;
|
|
3488
3595
|
if (governedOutcomesForBoundary(candidate) === undefined) return true;
|
|
3489
3596
|
const persisted = persistedBoundaryReconciliation(candidate, ledger);
|
|
3490
3597
|
if (persisted !== undefined) {
|
|
@@ -5009,7 +5116,13 @@ export function createXStatePlaybookRuntime<
|
|
|
5009
5116
|
if (governedSettlement !== undefined) {
|
|
5010
5117
|
governedSettlementsByBoundaryId.delete(boundaryId);
|
|
5011
5118
|
governedPlayerSettlements.set(result, governedSettlement);
|
|
5012
|
-
|
|
5119
|
+
// An unresolved settlement over an `unchanged` standalone receipt
|
|
5120
|
+
// still throws into the failure state through the player bridge; it
|
|
5121
|
+
// just never becomes an effect-possible envelope.
|
|
5122
|
+
if (
|
|
5123
|
+
governedSettlement.status === 'unresolved' &&
|
|
5124
|
+
!boundaryExcludesEffect(completed)
|
|
5125
|
+
) {
|
|
5013
5126
|
unresolvedSemanticBoundaryIds.add(boundaryId);
|
|
5014
5127
|
} else {
|
|
5015
5128
|
unresolvedSemanticBoundaryIds.delete(boundaryId);
|
|
@@ -7145,6 +7258,10 @@ export function createXStatePlaybookRuntime<
|
|
|
7145
7258
|
delivery: deferredValue<PlayerResult>(),
|
|
7146
7259
|
};
|
|
7147
7260
|
activeDeferredContinuation = continuation;
|
|
7261
|
+
// Emissions are buffered only until the host durably starts the
|
|
7262
|
+
// continuation: an exit that starts no player (checkpoint mismatch,
|
|
7263
|
+
// ineligible operation) publishes nothing, so the bound wait it
|
|
7264
|
+
// preserves is never contradicted by a classification line.
|
|
7148
7265
|
deferInspectionEmissions = true;
|
|
7149
7266
|
let continuationStarted = false;
|
|
7150
7267
|
let deliverySettled = false;
|
|
@@ -7181,6 +7298,14 @@ export function createXStatePlaybookRuntime<
|
|
|
7181
7298
|
continuation.playerContinuation = selectedContinuation;
|
|
7182
7299
|
continuationStarted = true;
|
|
7183
7300
|
actor!.send(event);
|
|
7301
|
+
// The host has durably started this boundary and the FSM has
|
|
7302
|
+
// moved: publish the buffered classification line and the
|
|
7303
|
+
// authored transition now, and let every later transition,
|
|
7304
|
+
// accepted outcome, and status emit inline. Holding them until
|
|
7305
|
+
// the operation settled put the cause after its effects — the
|
|
7306
|
+
// player call, the nested call a target state starts, and their
|
|
7307
|
+
// finishes were traced and sequenced first (PBRT-37).
|
|
7308
|
+
settleDeferredInspectionBuffer(true);
|
|
7184
7309
|
// The invoked player remains gated inside boundary.callPlayer.
|
|
7185
7310
|
// Return to the host only after the raw player call settles so it
|
|
7186
7311
|
// can capture and persist the receipt before any actor output or
|