@sublang/playbook 17.8.1 → 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.
@@ -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
- return `${made}; HEAD moved from ${baselineHead} to the proven commit ${commitOid}.`;
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
- 'Repository changes made before the failure:',
1565
- ...commits.map(
1566
- (commit) => `- A step ${commitBeforeFailureClause(commit)}`,
1567
- ),
1568
- 'The failure does not undo these commits.',
1569
- ].join('\n');
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` committed,
1588
- * one statement each, in boundary sequence order: every new commit where a
1589
- * receipt proves more than one, and the commit already at HEAD where a step
1590
- * whose outcomes all commit or ask named it over an `unchanged` receipt. A
1591
- * step in a deferred chain reads the chain's cumulative receipt at its last
1592
- * boundary.
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
- ): readonly string[] {
1598
- const statements: string[] = [];
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
- ? `- ${action.label} (ready)`
2177
- : `- ${action.label} (${standing}: ${controlReasonPhrase(action.reason)})`;
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 `Failure:` line's sentence. */
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 = oneTerminalMark(text);
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
- ? oneTerminalMark(guard(text))
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
- parts.push(
2435
- [
2436
- ...(assembled.failure === undefined ? [] : [assembled.failure]),
2437
- 'Controls:',
2438
- ...(failure.controls.length === 0 ? ['- none'] : failure.controls),
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 string[];
5060
+ let commitStatements: readonly StepCommitStatement[];
4943
5061
  try {
4944
5062
  const ledger = assertPlaybookEffectLedger(currentEffectLedger());
4945
5063
  carried = carriedPreExistingChanges(ledger, presentedEffectPrefix);
4946
- commitStatements = stepCommitStatements(ledger, presentedEffectPrefix);
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 boundaries.
4960
- if (commitStatements.length > 0) {
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
- commitStatements.join('\n'),
4964
- commitStatements,
5093
+ shownStatements.map(({ shown }) => shown).join('\n'),
5094
+ shownStatements.map(({ text }) => text),
4965
5095
  );
4966
- for (const fact of commitStatements) {
4967
- joinSettlementFact(turn, fact);
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
- text: `Failure: ${failure.sentence}`,
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 or does not reveal stays a
1294
- string, and the judge prompt and validation error name every non-string shape
1295
- ([DR-089](../specs/decisions/089-judge-replies-take-the-machines-shapes.md)).
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 non-string JSON value shape the compiled machine admits for
569
- * each semantic-owned field of each governed outcome, keyed by state id and
570
- * then by outcome. Exported so linked machinery of an artifact's own reads
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>;