@sublang/playbook 12.1.0 → 12.2.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sublang/playbook",
3
- "version": "12.1.0",
3
+ "version": "12.2.1",
4
4
  "type": "module",
5
5
  "description": "Composable XState v5 playbook runtime with compiled Captain, CODE, REVIEW, DECIDE, and DEV workflows driven by GEARS specs.",
6
6
  "license": "Apache-2.0",
@@ -64,6 +64,10 @@ const ROLE_ID_PATTERN = /^[a-z][a-z0-9_-]*$/;
64
64
  const HOST_CAPABILITIES_OPTION_KEY = 'hostCapabilities';
65
65
  const UNRESOLVED_EFFECT_RECONCILIATION_ACTION_ID = 'reconcile:unresolved-effect';
66
66
  const UNRESOLVED_EFFECT_ABANDONMENT_ACTION_ID = 'abandon:unresolved-effect';
67
+ // The fixed machine-syntax guard of a player's Boss-question suspension
68
+ // (slc/link.md §Boss-reply suspension): the accepted outcome that parks on
69
+ // Boss rather than sparing Boss a relay.
70
+ const BOSS_QUESTION_OUTCOME = 'needsBossReply';
67
71
  const RESUMPTION_DUPLICATE_EFFECT_WARNING = 'Warning: resumption may duplicate external effects attempted after the retained boundary; verify the current world before continuing.';
68
72
  function parseRegisteredCommand(prompt) {
69
73
  const match = /^\/([A-Za-z][A-Za-z0-9_-]*)(?:\s+([\s\S]*))?$/.exec(prompt.trim());
@@ -2550,6 +2554,12 @@ export function createPlaybookCaptainShell(options, deps = {}) {
2550
2554
  if (summary.acceptedOutcomeTraceKeys.has(traceKey))
2551
2555
  return;
2552
2556
  summary.acceptedOutcomeTraceKeys.add(traceKey);
2557
+ // CAPTAIN-19/20: a saved interruption is a player reply Boss did not
2558
+ // have to relay. A Boss-question suspension is the one accepted outcome
2559
+ // that parks on Boss instead, so it saves nothing and counts nothing —
2560
+ // a turn that only parked must not claim it saved an interruption.
2561
+ if (receipt.acceptedOutcome === BOSS_QUESTION_OUTCOME)
2562
+ return;
2553
2563
  summary.counts.interruptions++;
2554
2564
  if (frame.entry.summaryPolicy?.copyPasteGuardNames.includes(receipt.acceptedOutcome)) {
2555
2565
  summary.counts.copyPastes++;
@@ -554,6 +554,10 @@ const HOST_CAPABILITIES_OPTION_KEY = 'hostCapabilities';
554
554
  const UNRESOLVED_EFFECT_RECONCILIATION_ACTION_ID =
555
555
  'reconcile:unresolved-effect';
556
556
  const UNRESOLVED_EFFECT_ABANDONMENT_ACTION_ID = 'abandon:unresolved-effect';
557
+ // The fixed machine-syntax guard of a player's Boss-question suspension
558
+ // (slc/link.md §Boss-reply suspension): the accepted outcome that parks on
559
+ // Boss rather than sparing Boss a relay.
560
+ const BOSS_QUESTION_OUTCOME = 'needsBossReply';
557
561
  const RESUMPTION_DUPLICATE_EFFECT_WARNING =
558
562
  'Warning: resumption may duplicate external effects attempted after the retained boundary; verify the current world before continuing.';
559
563
 
@@ -4293,6 +4297,11 @@ export function createPlaybookCaptainShell(
4293
4297
  const traceKey = `${frame.sessionId}:${trace.sequence}`;
4294
4298
  if (summary.acceptedOutcomeTraceKeys.has(traceKey)) return;
4295
4299
  summary.acceptedOutcomeTraceKeys.add(traceKey);
4300
+ // CAPTAIN-19/20: a saved interruption is a player reply Boss did not
4301
+ // have to relay. A Boss-question suspension is the one accepted outcome
4302
+ // that parks on Boss instead, so it saves nothing and counts nothing —
4303
+ // a turn that only parked must not claim it saved an interruption.
4304
+ if (receipt.acceptedOutcome === BOSS_QUESTION_OUTCOME) return;
4296
4305
  summary.counts.interruptions++;
4297
4306
  if (
4298
4307
  frame.entry.summaryPolicy?.copyPasteGuardNames.includes(
@@ -19,7 +19,7 @@ import { randomUUID } from 'node:crypto';
19
19
  import PQueue from 'p-queue';
20
20
  import { createActor, fromPromise } from 'xstate';
21
21
  import { createAcceptedOutcomeConsumer, } from '../../../src/accepted-outcome.js';
22
- import { assertJsonSafe, assertPlaybookEffectLedger, assertPlaybookRuntimeSnapshot, combineAbortSignals, createNestedPlaybookBridge, detachPersistedMachineSnapshot, normalizeError, normalizePlaybookSnapshot, PlaybookSemanticCandidateStructureError, reconcilePlaybookSemanticEvidence, snapshotJsonValue, snapshotPlaybookSession, validatePlayerResult, waitForPlaybookQuiescence, } from '../../../src/xstate-runtime.js';
22
+ import { assertJsonSafe, assertPlaybookEffectLedger, assertPlaybookRuntimeSnapshot, combineAbortSignals, createNestedPlaybookBridge, detachPersistedMachineSnapshot, normalizeError, normalizePlaybookSnapshot, PlaybookSemanticCandidateStructureError, reconcilePlaybookSemanticEvidence, renderGovernedOutcomeContract, snapshotJsonValue, snapshotPlaybookSession, validatePlayerResult, waitForPlaybookQuiescence, } from '../../../src/xstate-runtime.js';
23
23
  import decideMachine from './decide.fsm.js';
24
24
  function snapshotDecideRuntimeOptions(value) {
25
25
  const captured = snapshotJsonValue(value, 'DECIDE runtime options');
@@ -429,15 +429,19 @@ function buildAdjudicatorPrompt(input, playerOutput, correction) {
429
429
  lines.push('"""');
430
430
  lines.push('');
431
431
  lines.push('Guards (choose exactly one; the descriptions are authoritative and must be applied as written):');
432
+ // The shared engine's renderer (slc/link.md §Captain adjudication): each
433
+ // arm's meaning verbatim, with its `Output shall include` clause — authored
434
+ // for the complete actor output — replaced by the reply contract the
435
+ // outcome authority gives the judge. Rendering the clause verbatim asked
436
+ // the judge for presentation-owned `coderProposal` or `question`, which the
437
+ // reconciler rejects, spending the single correction on a self-inflicted
438
+ // defect.
432
439
  for (const [guard, description] of Object.entries(input.result)) {
433
- const semanticFields = Object.entries(outcomes[guard]?.fields ?? {})
434
- .filter(([, authority]) => authority === 'semantic')
435
- .map(([field]) => field);
436
- lines.push(`- ${guard}: semantic fields: ${semanticFields.length === 0 ? '(none)' : semanticFields.join(', ')}; ${description}`);
440
+ lines.push(...renderGovernedOutcomeContract(guard, description, outcomes[guard]));
437
441
  }
438
442
  lines.push('');
439
- lines.push('Reply with exactly the chosen `guard` and every semantic-owned field for that guard, and no other field.');
440
- lines.push('Do not include presentation-, effect-, or runtime-owned fields; the runtime supplies those from authoritative evidence.');
443
+ lines.push("Pick exactly one declared `guard` and reply with exactly that outcome's reply shape above: `guard` plus its semantic-owned fields and nothing else.");
444
+ lines.push('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.');
441
445
  if (correction !== undefined) {
442
446
  lines.push('');
443
447
  lines.push('Your first reply was structurally invalid:');
@@ -38,6 +38,7 @@ import {
38
38
  normalizePlaybookSnapshot,
39
39
  PlaybookSemanticCandidateStructureError,
40
40
  reconcilePlaybookSemanticEvidence,
41
+ renderGovernedOutcomeContract,
41
42
  snapshotJsonValue,
42
43
  snapshotPlaybookSession,
43
44
  validatePlayerResult,
@@ -660,22 +661,24 @@ function buildAdjudicatorPrompt(
660
661
  lines.push(
661
662
  'Guards (choose exactly one; the descriptions are authoritative and must be applied as written):',
662
663
  );
664
+ // The shared engine's renderer (slc/link.md §Captain adjudication): each
665
+ // arm's meaning verbatim, with its `Output shall include` clause — authored
666
+ // for the complete actor output — replaced by the reply contract the
667
+ // outcome authority gives the judge. Rendering the clause verbatim asked
668
+ // the judge for presentation-owned `coderProposal` or `question`, which the
669
+ // reconciler rejects, spending the single correction on a self-inflicted
670
+ // defect.
663
671
  for (const [guard, description] of Object.entries(input.result)) {
664
- const semanticFields = Object.entries(outcomes[guard]?.fields ?? {})
665
- .filter(([, authority]) => authority === 'semantic')
666
- .map(([field]) => field);
667
672
  lines.push(
668
- `- ${guard}: semantic fields: ${
669
- semanticFields.length === 0 ? '(none)' : semanticFields.join(', ')
670
- }; ${description}`,
673
+ ...renderGovernedOutcomeContract(guard, description, outcomes[guard]),
671
674
  );
672
675
  }
673
676
  lines.push('');
674
677
  lines.push(
675
- 'Reply with exactly the chosen `guard` and every semantic-owned field for that guard, and no other field.',
678
+ "Pick exactly one declared `guard` and reply with exactly that outcome's reply shape above: `guard` plus its semantic-owned fields and nothing else.",
676
679
  );
677
680
  lines.push(
678
- 'Do not include presentation-, effect-, or runtime-owned fields; the runtime supplies those from authoritative evidence.',
681
+ '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.',
679
682
  );
680
683
  if (correction !== undefined) {
681
684
  lines.push('');
package/slc/gears2fsm.md CHANGED
@@ -385,6 +385,9 @@ implementation.
385
385
 
386
386
  A literal call shall retain the existing representation: `playbookId` is the
387
387
  literal target and `text` is the composed GEARS blockquote.
388
+ Composing that text follows the quoted-relay rules of [link](link.md): a
389
+ relayed value that is empty contributes no line, a multi-line value is quoted
390
+ line by line, and the composer inserts no empty quoted line of its own.
388
391
 
389
392
  A dynamic call written
390
393
  ``Captain shall call playbook selected by `<target-field>`:`` shall declare the
package/slc/link.md CHANGED
@@ -1079,6 +1079,15 @@ runtime-owned payload field; `guard` shall name exactly one outcome declared
1079
1079
  by both the live result map and `outcomeAuthority`; and every semantic-owned
1080
1080
  field shall satisfy the result map's required-field type before any actor
1081
1081
  output is delivered.
1082
+ The judge prompt shall render each governed outcome's description with its
1083
+ meaning verbatim and its `Output shall include` clause replaced by that reply
1084
+ contract — exactly `guard` plus the semantic-owned fields, each keeping its
1085
+ authored placeholder or guidance, with every presentation-, effect-, or
1086
+ runtime-owned field named as runtime-supplied to omit — so the judge is never asked for a
1087
+ field it does not own; the artifact's description text stays unchanged.
1088
+ The shared engine shall export that rendering as `renderGovernedOutcomeContract`
1089
+ on `@sublang/playbook/xstate-runtime`, and a bespoke linked runtime shall
1090
+ render its judge prompt through it rather than restate the contract.
1082
1091
  The reconciler shall construct the complete actor output rather than accept a
1083
1092
  cross-authority object from the judge: every presentation-owned payload field
1084
1093
  shall receive the canonical `finalText.trim()` value; every effect-owned
@@ -1170,7 +1179,9 @@ Two default adjudication strategies, in selection order:
1170
1179
  to decide only from the supplied actor output and declared outcomes, and
1171
1180
  require exactly one JSON object with no prose. The judge prompt shall not
1172
1181
  interpret the player's output, paraphrase it, or alter the FSM's `result`
1173
- text — it carries the description verbatim.
1182
+ text — it carries the description verbatim, except that a governed
1183
+ schema-3 outcome's `Output shall include` clause is rendered as the
1184
+ authority-derived reply contract of §Captain adjudication.
1174
1185
  - **Marker-parse** (delegated-player alternative): a deterministic parser that
1175
1186
  scans the player output for a terminal control line such as
1176
1187
  `FSM-RESULT: { "guard": "...", ... }`. Useful when player adapters can
@@ -2402,6 +2413,10 @@ original template. Replacement strings are literal: placeholder-looking text
2402
2413
  inside Boss/catalog/plan/result values and JavaScript replacement tokens such
2403
2414
  as `$&`, `$$`, dollar-backtick, and `$'` shall not be interpreted or
2404
2415
  substituted again.
2416
+ A quoted relay line `> <placeholder>` whose value is empty shall be omitted
2417
+ from the composed text rather than left as an empty quoted line, a multi-line
2418
+ value shall be quoted line by line so every continuation line keeps its `>`
2419
+ marker, and the composer shall insert no empty quoted line of its own.
2405
2420
 
2406
2421
  ## Host adaptation (informative, not normative)
2407
2422
 
package/slc/text2gears.md CHANGED
@@ -145,6 +145,11 @@ The guard name shall match the ASCII identifier pattern
145
145
  The bullet order is authoritative, guard names are unique within the item, and
146
146
  the description shall name every required output property with its exact
147
147
  case-sensitive identifier.
148
+ An output property name shall match the same ASCII identifier pattern as a
149
+ guard name: a kebab-case Source placeholder such as `<coder-output>` names the
150
+ property `coderOutput` through the canonical kebab-token-to-camel-field mapping
151
+ of [link](link.md), never a quoted kebab-case key, because downstream artifacts
152
+ and calling playbooks consume these properties by name.
148
153
 
149
154
  A produced value consumed later shall have a declared producer: where any
150
155
  later item's blockquote reads a value through a `<placeholder>`, the item
@@ -242,6 +247,12 @@ text2gears shall emit an item whose behavior uses
242
247
  complete JSON-safe input-text template for that call.
243
248
  The literal target id shall be a stable configured playbook id, not a slash
244
249
  command or module specifier.
250
+ A nested-call item shall carry no `Results:` label: the child's terminal result
251
+ is its outcome, so Source's continuation after child success, abort, or failure
252
+ stays as prose after the blockquote for
253
+ [gears2fsm](gears2fsm.md#nested-playbook-calls) to route through
254
+ `invoke.onDone` and `invoke.onError`; a `Results:` block on a nested-call item
255
+ is malformed.
245
256
 
246
257
  Example:
247
258
 
@@ -323,6 +334,7 @@ exact English form regardless of Source language.
323
334
  A Source may itself be the normative specification of a transformation — e.g., a compiler phase definition, as when a meta pipeline compiles this file.
324
335
  Such a Source declares no roles and prompts none; Captain performs the specified transformation on request.
325
336
  Where such a Source carries a `## Compiled execution` section, text2gears shall compile it from that section alone: the section is the Source's complete behavior — its acting item, prompt, and `Results:` contract — and the remaining definition text is relayed content, not behaviors to transcribe, so the composition below applies only to a Source without that section.
337
+ That section's blockquote is complete as authored: text2gears shall emit it verbatim and shall append no relay line to it — the `<definition>` placeholder is its only runtime value, and the undelivered-value rule above does not add `<boss-intent>` or any other placeholder to it.
326
338
  Compose Captain-acting spec items for it: when a transformation request names the specification's source and target, Captain shall carry out the transformation as specified.
327
339
  Prompts shall carry the specification's normative requirements as instructions to Captain — deduplicated, one point per line — without inventing roles, triggers, or requirements the specification does not state.
328
340
 
@@ -412,6 +412,21 @@ export declare function defaultComposeCaptainPrompt(input: PlaybookCaptainInput,
412
412
  export declare function defaultExtractRequiredFields(description: string): string[];
413
413
  /** Default delegated-player adjudicator prompt. */
414
414
  export declare function defaultBuildJudgePrompt(input: PlaybookPlayerInput, finalText: string): string;
415
+ /**
416
+ * Judge-facing rendering of one governed outcome (DR-040 §1). The artifact's
417
+ * description is not altered: its meaning is carried through verbatim, while
418
+ * its `Output shall include` clause — authored for the complete actor output
419
+ * — is replaced by the reply contract `outcomeAuthority` gives the judge:
420
+ * exactly `guard` plus the outcome's semantic-owned fields, each keeping the
421
+ * placeholder or guidance the clause authors for it, and every
422
+ * presentation-, effect-, or runtime-owned field named as runtime-supplied
423
+ * so the judge omits it. Rendering the clause verbatim asked the judge for
424
+ * `question`, `planningResult`, or `evaluatedRevision`, which the reconciler
425
+ * rejects as a structural error, spending the single correction on a
426
+ * self-inflicted defect. Exported so a bespoke linked runtime (DECIDE's
427
+ * parallel machinery) renders the identical contract instead of restating it.
428
+ */
429
+ export declare function renderGovernedOutcomeContract(guard: string, description: string, outcome: XStateGovernedOutcomeSpec | undefined): string[];
415
430
  export interface PlayerAdjudicationSpec {
416
431
  buildJudgePrompt?: (input: PlaybookPlayerInput, finalText: string) => string;
417
432
  extractRequiredFields?: (description: string) => string[];
@@ -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 markers = ['Output shall include', '输出应包含'];
579
- let clauseStart = -1;
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 re = /`([A-Za-z_$][A-Za-z0-9_$]*)(?::[^`]*)?`/g;
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`. Include every semantic-owned field for that guard and no other field.',
631
- 'Do not include presentation-, effect-, or runtime-owned fields; the runtime supplies those from their authoritative evidence.',
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
- const semanticFields = Object.entries(outcomes[guard]?.fields ?? {})
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
- if (governedSettlement.status === 'unresolved') {
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 markers = ['Output shall include', '输出应包含'];
1315
- let clauseStart = -1;
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 re = /`([A-Za-z_$][A-Za-z0-9_$]*)(?::[^`]*)?`/g;
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`. Include every semantic-owned field for that guard and no other field.',
1379
- 'Do not include presentation-, effect-, or runtime-owned fields; the runtime supplies those from their authoritative evidence.',
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
- `- \`${guard}\` semantic fields: ${
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
- if (governedSettlement.status === 'unresolved') {
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