@sublang/playbook 17.7.0 → 17.8.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/slc/text2gears.md CHANGED
@@ -35,7 +35,8 @@ E.g.:
35
35
  - Coder
36
36
  - Reviewer
37
37
 
38
- Capitalize English role names (e.g., `Writer`); quote non-English names (e.g., `作者`) when needed to distinguish from prose.
38
+ Each role name shall be one capitalized word — an uppercase letter, or a letter of a script without case, followed only by letters and digits — so a two-word role is written as one word, e.g. `RiskOfficer`, never `Risk Officer`.
39
+ Quote non-English names (e.g., `作者`) when needed to distinguish from prose.
39
40
 
40
41
  ## Behaviors
41
42
 
@@ -67,14 +68,15 @@ Boss-visible contract and is nonconformant.
67
68
 
68
69
  ### Authored prompt fragments
69
70
 
70
- Source may compose one acting prompt from authored Markdown instruction blocks and runtime context that it explicitly says to relay in quotes (`>`).
71
+ Source may compose one acting prompt from authored Markdown instruction blocks and runtime context that it explicitly marks as relayed in quotes.
72
+ The relay marker is a backticked `>` in ASCII or full-width parentheses, `` (`>`) `` or `` (`>`) ``, in the prose introducing the blockquote; the words around it, such as "in quotes" or "以引用形式", are prose in the Source's language.
71
73
  A fenced `markdown` block introduced as an instruction or prompt is an authored static prompt fragment: its fence delimiters are Source syntax, while every interior line and blank line is prompt content preserved after documented Markdown unescaping.
72
74
 
73
75
  An instruction fence and a relayed-context fragment that apply to one behavior shall appear in the target blockquote in their Source order.
74
76
  Distinct non-empty fragments shall be separated by one blank prompt line unless Source explicitly supplies a different boundary.
75
77
  text2gears shall not move a shared instruction ahead of behavior-specific context, move quoted evidence after an instruction that Source says follows the evidence, or otherwise regroup fragments for convenience.
76
78
 
77
- Where Source says that a runtime value is relayed in quotes, the leading `>` is prompt content rather than Source-only blockquote syntax.
79
+ Where Source marks a runtime value as relayed in quotes, the leading `>` is prompt content rather than Source-only blockquote syntax.
78
80
  If Source supplies a blockquoted template for that relay, text2gears shall keep one literal leading `>` on every quoted line; the target GEARS line therefore uses its outer blockquote marker followed by the literal marker, such as `> > Coder output: <coder-output>`.
79
81
  If Source names the relayed value but supplies no template, text2gears shall emit a bare quoted prompt-content line, exactly `> <token>`, written as `> > <token>` in the GEARS file because the first marker encloses the prompt and the second is literal content, without an added label or surrounding prose, and shall not summarize, paraphrase, or invent the relayed value.
80
82
  An ordinary Source blockquote that specifies a complete acting prompt without requiring quoted relay retains the existing rule above: its one leading marker is Source syntax and is not prompt content.
package/src/runtime.d.ts CHANGED
@@ -255,6 +255,12 @@ export interface PlaybookRepositoryReceipt {
255
255
  readonly baseline: PlaybookRepositoryObservation;
256
256
  readonly after?: PlaybookRepositoryObservation;
257
257
  readonly commitOid?: string;
258
+ /**
259
+ * Every new commit, oldest first and ending at `commitOid`, present exactly
260
+ * when a `one-descendant-commit` receipt's HEAD advanced by more than one
261
+ * (DR-088).
262
+ */
263
+ readonly commits?: readonly string[];
258
264
  readonly preExisting?: PlaybookRepositoryPreExistingChanges;
259
265
  }
260
266
  /** One durably ordered physical governed-player boundary (DR-040). */
package/src/runtime.ts CHANGED
@@ -618,6 +618,12 @@ export interface PlaybookRepositoryReceipt {
618
618
  readonly baseline: PlaybookRepositoryObservation;
619
619
  readonly after?: PlaybookRepositoryObservation;
620
620
  readonly commitOid?: string;
621
+ /**
622
+ * Every new commit, oldest first and ending at `commitOid`, present exactly
623
+ * when a `one-descendant-commit` receipt's HEAD advanced by more than one
624
+ * (DR-088).
625
+ */
626
+ readonly commits?: readonly string[];
621
627
  readonly preExisting?: PlaybookRepositoryPreExistingChanges;
622
628
  }
623
629
 
@@ -1,4 +1,5 @@
1
1
  import type { AnyStateMachine, EventObject, PromiseActorLogic } from 'xstate';
2
+ import { type PlaybookSemanticValueShape, type PlaybookSemanticValueShapes } from './xstate-runtime.js';
2
3
  import type { CaptainResult, JsonValue, PlaybookEffectBoundary, PlaybookEffectBoundaryStart, PlaybookEffectLedger, PlaybookEffectLedgerCapability, NormalizedError, PlaybookFailureCause, PlaybookPendingBossQuestion, PlaybookPorts, PlaybookRepositoryReceipt, PlaybookRuntimeFactory, PlaybookSession, PlaybookState, PlayerResult } from './runtime.js';
3
4
  export interface PlaybookPendingBossQuestionContext {
4
5
  questionId: string;
@@ -494,7 +495,7 @@ export declare function defaultBuildJudgePrompt(input: PlaybookPlayerInput, fina
494
495
  * self-inflicted defect. Exported so linked machinery of an artifact's own
495
496
  * renders the identical contract instead of restating it.
496
497
  */
497
- export declare function renderGovernedOutcomeContract(guard: string, description: string, outcome: XStateGovernedOutcomeSpec | undefined): string[];
498
+ export declare function renderGovernedOutcomeContract(guard: string, description: string, outcome: XStateGovernedOutcomeSpec | undefined, shapes?: Readonly<Record<string, PlaybookSemanticValueShape>>): string[];
498
499
  export interface PlayerAdjudicationSpec {
499
500
  buildJudgePrompt?: (input: PlaybookPlayerInput, finalText: string) => string;
500
501
  extractRequiredFields?: (description: string) => string[];
@@ -522,13 +523,22 @@ export declare function createPlayerBridge(spec: PlayerBridgeSpec, ports: Playbo
522
523
  /**
523
524
  * Default direct-Captain adjudicator prompt (DR-025). The single statement of
524
525
  * the `{ guard, …structuralPayloadFields }` reply contract, shared with the
525
- * compiled default Captain artifact so the wording cannot drift.
526
+ * compiled default Captain artifact so the wording cannot drift. Each declared
527
+ * key keeps its description verbatim and gains the exact reply it takes
528
+ * (DR-089): `guard` plus the structural fields its `Output shall include`
529
+ * clause names, each with its authored placeholder, while the visible
530
+ * `question` and `response` fields are named as runtime-supplied. A
531
+ * `correction` restates a structurally invalid first reply and its
532
+ * validation error for the one corrective adjudication.
526
533
  */
527
534
  export declare function defaultBuildCaptainJudgePrompt(input: {
528
535
  readonly stateId: string;
529
536
  readonly sourceItem: string;
530
537
  readonly result: Readonly<Record<string, string>>;
531
- }, finalText: string): string;
538
+ }, finalText: string, correction?: {
539
+ readonly reply: string;
540
+ readonly error: string;
541
+ }): string;
532
542
  /**
533
543
  * Targets of the FSM's `awaitBossReply` BOSS_REPLY transitions, plus — for
534
544
  * the parallel shape gears2fsm compiles (DR-067) — the targets of each
@@ -554,6 +564,13 @@ export declare function stateDescriptionsFromMachine(machine: AnyStateMachine):
554
564
  * other than `success` or `failure`, is a malformed artifact and throws.
555
565
  */
556
566
  export declare function terminalOutcomesFromMachine(machine: AnyStateMachine, label?: string): ReadonlyMap<string, 'success' | 'failure'>;
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
571
+ * the same contract the shared factory enforces.
572
+ */
573
+ export declare function inferSemanticValueShapes(machine: AnyStateMachine, authority: XStateOutcomeAuthoritySpec): ReadonlyMap<string, PlaybookSemanticValueShapes>;
557
574
  /**
558
575
  * Build a `PlaybookRuntimeFactory` that interprets the given FSM artifact
559
576
  * under the slc/link.md contract. The factory provides every actor kind the
@@ -20,9 +20,9 @@ import { randomUUID } from 'node:crypto';
20
20
  import { isDeepStrictEqual } from 'node:util';
21
21
  import PQueue from 'p-queue';
22
22
  import { createActor, fromPromise } from 'xstate';
23
- import { createAcceptedOutcomeConsumer, } from './accepted-outcome.js';
23
+ import { ACCEPTED_OUTCOME_ACTION_TYPE, createAcceptedOutcomeConsumer, } from './accepted-outcome.js';
24
24
  import { assertPlaybookFailureCause } from './runtime.js';
25
- import { assertPlaybookRuntimeSnapshot, assertPlaybookEffectLedger, attachPlaybookFailureCause, combineAbortSignals, createNestedPlaybookBridge, detachPersistedMachineSnapshot, normalizeError, normalizePlaybookSnapshot, snapshotJsonValue, snapshotPlaybookSession, isPlaybookEffectLedgerMonotonicExtension, PlaybookSemanticCandidateStructureError, reconcilePlaybookSemanticEvidence, validateCaptainResult, validatePlayerResult, waitForPlaybookQuiescence, } from './xstate-runtime.js';
25
+ import { assertPlaybookRuntimeSnapshot, assertPlaybookEffectLedger, attachPlaybookFailureCause, combineAbortSignals, createNestedPlaybookBridge, describePlaybookSemanticValueShape, detachPersistedMachineSnapshot, normalizeError, normalizePlaybookSnapshot, snapshotJsonValue, snapshotPlaybookSession, isPlaybookEffectLedgerMonotonicExtension, PlaybookSemanticCandidateStructureError, reconcilePlaybookSemanticEvidence, validateCaptainResult, validatePlayerResult, waitForPlaybookQuiescence, } from './xstate-runtime.js';
26
26
  export const BOSS_REPLY_ERRORS = {
27
27
  missingQuestion: "needsBossReply outcome missing 'question' field",
28
28
  unregisteredState: (stateId) => `state ${stateId} declared needsBossReply but is not registered as resumable`,
@@ -906,7 +906,7 @@ export function defaultBuildJudgePrompt(input, finalText) {
906
906
  * self-inflicted defect. Exported so linked machinery of an artifact's own
907
907
  * renders the identical contract instead of restating it.
908
908
  */
909
- export function renderGovernedOutcomeContract(guard, description, outcome) {
909
+ export function renderGovernedOutcomeContract(guard, description, outcome, shapes) {
910
910
  const { meaning, clause } = splitOutputClause(description);
911
911
  // Each field's authored segment runs from its token to the next token:
912
912
  // the annotated `name: <placeholder>` form, or the bare name followed by
@@ -934,11 +934,20 @@ export function renderGovernedOutcomeContract(guard, description, outcome) {
934
934
  }
935
935
  const replyMembers = [`"guard": ${JSON.stringify(guard)}`];
936
936
  const semanticAsAuthored = [];
937
+ const valueShapes = [];
937
938
  const runtimeSupplied = [];
938
939
  for (const [field, authority] of Object.entries(outcome?.fields ?? {})) {
939
940
  if (authority === 'semantic') {
940
941
  const entry = authored.get(field);
941
- replyMembers.push(`${JSON.stringify(field)}: ${entry?.placeholder ?? '<string>'}`);
942
+ // DR-089: the value has the shape the compiled machine admits.
943
+ const shape = shapes !== undefined && Object.prototype.hasOwnProperty.call(shapes, field)
944
+ ? shapes[field]
945
+ : 'string';
946
+ const words = describePlaybookSemanticValueShape(shape);
947
+ replyMembers.push(`${JSON.stringify(field)}: ${entry?.placeholder ??
948
+ (shape === 'string' ? '<string>' : `<${words.replace(/^a /, '')}>`)}`);
949
+ if (shape !== 'string')
950
+ valueShapes.push(`\`${field}\` is ${words}`);
942
951
  semanticAsAuthored.push(entry?.segment ?? `\`${field}\``);
943
952
  }
944
953
  else {
@@ -949,6 +958,9 @@ export function renderGovernedOutcomeContract(guard, description, outcome) {
949
958
  meaning.length === 0 ? `- \`${guard}\`` : `- \`${guard}\` — ${meaning}`,
950
959
  ` Reply exactly: { ${replyMembers.join(', ')} }`,
951
960
  ];
961
+ if (valueShapes.length > 0) {
962
+ lines.push(` JSON value shape: ${valueShapes.join('; ')}`);
963
+ }
952
964
  if (semanticAsAuthored.length > 0) {
953
965
  lines.push(` Semantic fields as authored: ${semanticAsAuthored.join('; ')}`);
954
966
  }
@@ -957,7 +969,7 @@ export function renderGovernedOutcomeContract(guard, description, outcome) {
957
969
  }
958
970
  return lines;
959
971
  }
960
- function buildGovernedJudgePrompt(input, finalText, outcomes, correction) {
972
+ function buildGovernedJudgePrompt(input, finalText, outcomes, correction, shapes) {
961
973
  const lines = [
962
974
  'This is hidden control work. Do not call tools, inspect files, or seek external evidence.',
963
975
  'Decide only from the supplied player output and declared outcomes.',
@@ -976,7 +988,7 @@ function buildGovernedJudgePrompt(input, finalText, outcomes, correction) {
976
988
  '',
977
989
  ];
978
990
  for (const [guard, description] of Object.entries(input.result)) {
979
- lines.push(...renderGovernedOutcomeContract(guard, description, outcomes[guard]));
991
+ lines.push(...renderGovernedOutcomeContract(guard, description, outcomes[guard], shapes?.[guard]));
980
992
  }
981
993
  if (correction !== undefined) {
982
994
  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.');
@@ -1133,9 +1145,15 @@ export function createPlayerBridge(spec, ports, getActiveSignal, boundary, onCon
1133
1145
  /**
1134
1146
  * Default direct-Captain adjudicator prompt (DR-025). The single statement of
1135
1147
  * the `{ guard, …structuralPayloadFields }` reply contract, shared with the
1136
- * compiled default Captain artifact so the wording cannot drift.
1148
+ * compiled default Captain artifact so the wording cannot drift. Each declared
1149
+ * key keeps its description verbatim and gains the exact reply it takes
1150
+ * (DR-089): `guard` plus the structural fields its `Output shall include`
1151
+ * clause names, each with its authored placeholder, while the visible
1152
+ * `question` and `response` fields are named as runtime-supplied. A
1153
+ * `correction` restates a structurally invalid first reply and its
1154
+ * validation error for the one corrective adjudication.
1137
1155
  */
1138
- export function defaultBuildCaptainJudgePrompt(input, finalText) {
1156
+ export function defaultBuildCaptainJudgePrompt(input, finalText, correction) {
1139
1157
  const lines = [];
1140
1158
  lines.push('Adjudicate the direct Captain output for this FSM state.');
1141
1159
  lines.push(`State id: ${input.stateId}`);
@@ -1149,25 +1167,71 @@ export function defaultBuildCaptainJudgePrompt(input, finalText) {
1149
1167
  lines.push('Result keys and descriptions:');
1150
1168
  for (const [key, description] of Object.entries(input.result)) {
1151
1169
  lines.push(`- \`${key}\` — ${description}`);
1170
+ const { clause } = splitOutputClause(description);
1171
+ const replyMembers = [`"guard": ${JSON.stringify(key)}`];
1172
+ const runtimeSupplied = [];
1173
+ const seen = new Set();
1174
+ for (const m of clause === undefined
1175
+ ? []
1176
+ : clause.matchAll(PAYLOAD_FIELD_PATTERN)) {
1177
+ const field = m[1];
1178
+ if (seen.has(field))
1179
+ continue;
1180
+ seen.add(field);
1181
+ if (field === 'question' || field === 'response') {
1182
+ runtimeSupplied.push(`\`${field}\``);
1183
+ continue;
1184
+ }
1185
+ const placeholder = m[2]?.trim();
1186
+ replyMembers.push(`${JSON.stringify(field)}: ${placeholder !== undefined && placeholder.length > 0
1187
+ ? placeholder
1188
+ : '<value>'}`);
1189
+ }
1190
+ lines.push(` Reply exactly: { ${replyMembers.join(', ')} }`);
1191
+ if (runtimeSupplied.length > 0) {
1192
+ lines.push(` Runtime-supplied, do not include: ${runtimeSupplied.join(', ')}`);
1193
+ }
1152
1194
  }
1153
1195
  lines.push('');
1154
1196
  lines.push('Pick exactly one outcome by `guard` and return JSON ' +
1155
1197
  '`{ guard, …structuralPayloadFields }`. Do not include `question` or ' +
1156
1198
  '`response`; the runtime injects the visible text.');
1199
+ lines.push('Reply with exactly that outcome\'s reply shape above and no other field.');
1200
+ if (correction !== undefined) {
1201
+ lines.push('', 'Your first reply was structurally invalid:', '', '```', correction.reply, '```', '', `Validation error: ${correction.error}`, 'Correct only that structure using the same Captain output and outcome schema.');
1202
+ }
1157
1203
  return lines.join('\n');
1158
1204
  }
1205
+ /**
1206
+ * A direct-Captain adjudication reply that fails structural validation
1207
+ * (DR-089): it earns one corrective adjudication, and a second such reply
1208
+ * parks the step as a decided `judge-failed` failure rather than escaping as
1209
+ * a control-plane error.
1210
+ */
1211
+ class CaptainAdjudicationStructureError extends Error {
1212
+ constructor(message) {
1213
+ super(message);
1214
+ this.name = 'CaptainAdjudicationStructureError';
1215
+ }
1216
+ }
1159
1217
  function adjudicateCaptainOutput(extractFields, input, finalText, judgeText) {
1160
- const parsed = parseJudgeJson(judgeText);
1218
+ let parsed;
1219
+ try {
1220
+ parsed = parseJudgeJson(judgeText);
1221
+ }
1222
+ catch (error) {
1223
+ throw new CaptainAdjudicationStructureError(`adjudicate: ${error instanceof Error ? error.message : 'judge response is not valid JSON'}`);
1224
+ }
1161
1225
  if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
1162
- throw new Error('adjudicate: judge response is not a JSON object');
1226
+ throw new CaptainAdjudicationStructureError('adjudicate: judge response is not a JSON object');
1163
1227
  }
1164
1228
  const obj = parsed;
1165
1229
  const guard = obj.guard;
1166
1230
  if (typeof guard !== 'string') {
1167
- throw new Error('adjudicate: judge response missing string "guard" field');
1231
+ throw new CaptainAdjudicationStructureError('adjudicate: judge response missing string "guard" field');
1168
1232
  }
1169
1233
  if (!Object.prototype.hasOwnProperty.call(input.result, guard)) {
1170
- throw new Error(`adjudicate: unknown guard "${guard}" — declared guards: ${Object.keys(input.result).join(', ')}`);
1234
+ throw new CaptainAdjudicationStructureError(`adjudicate: unknown guard "${guard}" — declared guards: ${Object.keys(input.result).join(', ')}`);
1171
1235
  }
1172
1236
  const required = extractFields(input.result[guard]);
1173
1237
  const allowed = new Set(['guard']);
@@ -1177,14 +1241,14 @@ function adjudicateCaptainOutput(extractFields, input, finalText, judgeText) {
1177
1241
  }
1178
1242
  for (const key of Object.keys(obj)) {
1179
1243
  if (!allowed.has(key)) {
1180
- throw new Error(`adjudicate: judge response supplied undeclared field "${key}" for guard "${guard}"`);
1244
+ throw new CaptainAdjudicationStructureError(`adjudicate: judge response supplied undeclared field "${key}" for guard "${guard}"`);
1181
1245
  }
1182
1246
  }
1183
1247
  for (const field of required) {
1184
1248
  if (field === 'question' || field === 'response')
1185
1249
  continue;
1186
1250
  if (obj[field] === undefined || obj[field] === null) {
1187
- throw new Error(`adjudicate: judge response missing required field "${field}" for guard "${guard}"`);
1251
+ throw new CaptainAdjudicationStructureError(`adjudicate: judge response missing required field "${field}" for guard "${guard}"`);
1188
1252
  }
1189
1253
  }
1190
1254
  const output = { ...obj, guard };
@@ -1934,6 +1998,156 @@ function snapshotOutcomeAuthority(descriptor, label, playerStates, verbatimPaylo
1934
1998
  governedPlayerStates: Object.freeze(normalizedStates),
1935
1999
  });
1936
2000
  }
2001
+ // ---------------------------------------------------------------------------
2002
+ // DR-089: one type contract for semantic-owned fields. The compiled machine's
2003
+ // own validator decides which JSON value it admits for each field, so the
2004
+ // runtime asks it once at construction: each governed outcome is presented to
2005
+ // the machine's transition selection — guards only, no action runs — with
2006
+ // sample values, and the first combination of shapes whose arm carries that
2007
+ // outcome's accepted-outcome marker is the contract the judge is shown and the
2008
+ // reconciler enforces. A field whose shape the machine does not reveal, or
2009
+ // that it admits as a string, stays a string.
2010
+ // ---------------------------------------------------------------------------
2011
+ const SEMANTIC_SHAPE_SAMPLES = [
2012
+ ['string', 'sample'],
2013
+ ['string-array', ['sample']],
2014
+ ['number-array', [1]],
2015
+ ['number', 1],
2016
+ ['boolean', true],
2017
+ ];
2018
+ const SHAPE_PROBE_TEXT = 'sample';
2019
+ const SHAPE_PROBE_COMMIT = '0123456789abcdef0123456789abcdef01234567';
2020
+ const SHAPE_PROBE_FIELD_LIMIT = 4;
2021
+ function isAcceptedOutcomeMarker(action, stateId, guard) {
2022
+ if (!isPlainObject(action) || action.type !== ACCEPTED_OUTCOME_ACTION_TYPE) {
2023
+ return false;
2024
+ }
2025
+ const params = action.params;
2026
+ return (isPlainObject(params) &&
2027
+ params.source === stateId &&
2028
+ params.acceptedOutcome === guard);
2029
+ }
2030
+ function shapeCombinations(fieldCount) {
2031
+ const combinations = [[]];
2032
+ for (let field = 0; field < fieldCount; field += 1) {
2033
+ const next = [];
2034
+ for (const prefix of combinations) {
2035
+ for (let sample = 0; sample < SEMANTIC_SHAPE_SAMPLES.length; sample += 1) {
2036
+ next.push([...prefix, sample]);
2037
+ }
2038
+ }
2039
+ combinations.splice(0, combinations.length, ...next);
2040
+ }
2041
+ // Fewest non-string fields first, then the sample order, so a machine that
2042
+ // admits strings keeps them.
2043
+ return combinations.sort((left, right) => left.filter((index) => index !== 0).length -
2044
+ right.filter((index) => index !== 0).length ||
2045
+ left.join(',').localeCompare(right.join(',')));
2046
+ }
2047
+ /**
2048
+ * DR-089: the non-string JSON value shape the compiled machine admits for
2049
+ * each semantic-owned field of each governed outcome, keyed by state id and
2050
+ * then by outcome. Exported so linked machinery of an artifact's own reads
2051
+ * the same contract the shared factory enforces.
2052
+ */
2053
+ export function inferSemanticValueShapes(machine, authority) {
2054
+ const shapesByState = new Map();
2055
+ const playerNodes = new Map();
2056
+ const visit = (node) => {
2057
+ if (node.invoke.some(({ src }) => src === 'player')) {
2058
+ const playbookMeta = isPlainObject(node.config.meta)
2059
+ ? node.config.meta.playbook
2060
+ : undefined;
2061
+ const stateId = isPlainObject(playbookMeta) && typeof playbookMeta.stateId === 'string'
2062
+ ? playbookMeta.stateId
2063
+ : typeof node.config.id === 'string'
2064
+ ? node.config.id
2065
+ : node.key;
2066
+ if (!playerNodes.has(stateId))
2067
+ playerNodes.set(stateId, node);
2068
+ }
2069
+ for (const child of Object.values(node.states))
2070
+ visit(child);
2071
+ };
2072
+ try {
2073
+ visit(machine.root);
2074
+ }
2075
+ catch {
2076
+ return shapesByState;
2077
+ }
2078
+ const configContext = machine
2079
+ .config?.context;
2080
+ const context = isPlainObject(configContext) ? configContext : {};
2081
+ for (const [stateId, outcomes] of Object.entries(authority.governedPlayerStates)) {
2082
+ const node = playerNodes.get(stateId);
2083
+ const invokeId = node?.invoke.find(({ src }) => src === 'player')?.id;
2084
+ if (node === undefined || invokeId === undefined)
2085
+ continue;
2086
+ let snapshot;
2087
+ try {
2088
+ const value = [...node.path].reduceRight((inner, key) => (inner === undefined ? key : { [key]: inner }), undefined);
2089
+ snapshot = machine.resolveState({ value: value, context });
2090
+ }
2091
+ catch {
2092
+ continue;
2093
+ }
2094
+ const stateShapes = {};
2095
+ for (const [guard, outcome] of Object.entries(outcomes)) {
2096
+ const semanticFields = Object.entries(outcome.fields)
2097
+ .filter(([, fieldAuthority]) => fieldAuthority === 'semantic')
2098
+ .map(([field]) => field);
2099
+ if (semanticFields.length === 0 ||
2100
+ semanticFields.length > SHAPE_PROBE_FIELD_LIMIT) {
2101
+ continue;
2102
+ }
2103
+ const base = { guard };
2104
+ for (const [field, fieldAuthority] of Object.entries(outcome.fields)) {
2105
+ if (fieldAuthority === 'effect')
2106
+ base[field] = SHAPE_PROBE_COMMIT;
2107
+ else if (fieldAuthority !== 'semantic')
2108
+ base[field] = SHAPE_PROBE_TEXT;
2109
+ }
2110
+ const admits = (values) => {
2111
+ try {
2112
+ const transitions = machine.getTransitionData(snapshot, {
2113
+ type: `xstate.done.actor.${invokeId}`,
2114
+ output: { ...base, ...values },
2115
+ actorId: invokeId,
2116
+ });
2117
+ return transitions.some((transition) => transition.actions.some((action) => isAcceptedOutcomeMarker(action, stateId, guard)));
2118
+ }
2119
+ catch {
2120
+ return false;
2121
+ }
2122
+ };
2123
+ for (const combination of shapeCombinations(semanticFields.length)) {
2124
+ const values = {};
2125
+ semanticFields.forEach((field, index) => {
2126
+ values[field] = SEMANTIC_SHAPE_SAMPLES[combination[index]][1];
2127
+ });
2128
+ if (!admits(values))
2129
+ continue;
2130
+ const shapes = {};
2131
+ semanticFields.forEach((field, index) => {
2132
+ let shape = SEMANTIC_SHAPE_SAMPLES[combination[index]][0];
2133
+ if (shape === 'string-array' && admits({ ...values, [field]: [1] })) {
2134
+ shape = 'string-or-number-array';
2135
+ }
2136
+ if (shape !== 'string')
2137
+ shapes[field] = shape;
2138
+ });
2139
+ if (Object.keys(shapes).length > 0) {
2140
+ stateShapes[guard] = Object.freeze(shapes);
2141
+ }
2142
+ break;
2143
+ }
2144
+ }
2145
+ if (Object.keys(stateShapes).length > 0) {
2146
+ shapesByState.set(stateId, Object.freeze(stateShapes));
2147
+ }
2148
+ }
2149
+ return shapesByState;
2150
+ }
1937
2151
  function sameStringSet(left, right) {
1938
2152
  if (left.length !== right.length)
1939
2153
  return false;
@@ -2513,6 +2727,7 @@ export function createXStatePlaybookRuntime(machine, spec) {
2513
2727
  verbatimPayloadFields,
2514
2728
  };
2515
2729
  const outcomeAuthority = snapshotOutcomeAuthority(specDescriptors.outcomeAuthority, label, roleStates, verbatimPayloadFields);
2730
+ const semanticValueShapes = inferSemanticValueShapes(machine, outcomeAuthority);
2516
2731
  for (const [stateId, outcomes] of Object.entries(outcomeAuthority.governedPlayerStates)) {
2517
2732
  if (Object.values(outcomes).some(({ repositoryDisposition }) => repositoryDisposition === 'deferred') &&
2518
2733
  !resumableStateIds.has(stateId)) {
@@ -2900,6 +3115,7 @@ export function createXStatePlaybookRuntime(machine, spec) {
2900
3115
  semanticCandidate: candidate.semanticCandidate,
2901
3116
  finalText: candidate.finalText,
2902
3117
  receipt,
3118
+ semanticValueShapes: semanticValueShapes.get(candidate.sourceStateId),
2903
3119
  });
2904
3120
  if (awaitingLogicalReceipt &&
2905
3121
  reconciliation.status !== 'deferred') {
@@ -3745,7 +3961,7 @@ export function createXStatePlaybookRuntime(machine, spec) {
3745
3961
  }
3746
3962
  let raw;
3747
3963
  try {
3748
- raw = await boundary.callJudge('player-output-adjudication', input.stateId, buildGovernedJudgePrompt(input, finalText, outcomes), signal);
3964
+ raw = await boundary.callJudge('player-output-adjudication', input.stateId, buildGovernedJudgePrompt(input, finalText, outcomes, undefined, semanticValueShapes.get(input.stateId)), signal);
3749
3965
  }
3750
3966
  catch (error) {
3751
3967
  governedSettlementsByBoundaryId.set(completion.boundary.boundaryId, unresolvedGovernedSettlement('judge transport failed', error, signal));
@@ -3779,6 +3995,7 @@ export function createXStatePlaybookRuntime(machine, spec) {
3779
3995
  semanticCandidate: candidate,
3780
3996
  finalText,
3781
3997
  receipt: completion.outcomeReceipt,
3998
+ semanticValueShapes: semanticValueShapes.get(input.stateId),
3782
3999
  });
3783
4000
  }
3784
4001
  catch (error) {
@@ -3811,7 +4028,7 @@ export function createXStatePlaybookRuntime(machine, spec) {
3811
4028
  correctiveRaw = await boundary.callJudge('player-output-adjudication', input.stateId, buildGovernedJudgePrompt(input, finalText, outcomes, {
3812
4029
  reply: raw,
3813
4030
  error: structuralError.message,
3814
- }), signal);
4031
+ }, semanticValueShapes.get(input.stateId)), signal);
3815
4032
  }
3816
4033
  catch (error) {
3817
4034
  governedSettlementsByBoundaryId.set(completion.boundary.boundaryId, unresolvedGovernedSettlement('corrective judge failed', error, signal));
@@ -3825,6 +4042,7 @@ export function createXStatePlaybookRuntime(machine, spec) {
3825
4042
  semanticCandidate: candidate,
3826
4043
  finalText,
3827
4044
  receipt: completion.outcomeReceipt,
4045
+ semanticValueShapes: semanticValueShapes.get(input.stateId),
3828
4046
  });
3829
4047
  }
3830
4048
  catch (error) {
@@ -4202,12 +4420,13 @@ export function createXStatePlaybookRuntime(machine, spec) {
4202
4420
  const actorInput = input.snapshot.input;
4203
4421
  const outcomes = governedOutcomesForBoundary(saved);
4204
4422
  const candidate = saved.semanticCandidate ??
4205
- parseGovernedSemanticCandidate(await boundary.callJudge('player-output-adjudication', saved.sourceStateId, buildGovernedJudgePrompt(actorInput, saved.finalText, outcomes), signal));
4423
+ parseGovernedSemanticCandidate(await boundary.callJudge('player-output-adjudication', saved.sourceStateId, buildGovernedJudgePrompt(actorInput, saved.finalText, outcomes, undefined, semanticValueShapes.get(saved.sourceStateId)), signal));
4206
4424
  const result = reconcilePlaybookSemanticEvidence({
4207
4425
  outcomes,
4208
4426
  semanticCandidate: candidate,
4209
4427
  finalText: saved.finalText,
4210
4428
  receipt: saved.physicalReceipt,
4429
+ semanticValueShapes: semanticValueShapes.get(saved.sourceStateId),
4211
4430
  });
4212
4431
  signal.throwIfAborted();
4213
4432
  const next = {
@@ -5334,7 +5553,37 @@ export function createXStatePlaybookRuntime(machine, spec) {
5334
5553
  }
5335
5554
  const judgePrompt = defaultBuildCaptainJudgePrompt(input, finalText);
5336
5555
  const raw = await boundary.callJudge('captain-output-adjudication', input.stateId, judgePrompt, active);
5337
- const output = adjudicateCaptainOutput(extractFields, input, finalText, raw);
5556
+ let output;
5557
+ try {
5558
+ output = adjudicateCaptainOutput(extractFields, input, finalText, raw);
5559
+ }
5560
+ catch (error) {
5561
+ if (!(error instanceof CaptainAdjudicationStructureError)) {
5562
+ throw error;
5563
+ }
5564
+ // DR-089: one corrective adjudication over the same visible
5565
+ // output, restating the reply and its validation error; an
5566
+ // abort before it begins starts none.
5567
+ active.throwIfAborted();
5568
+ const correctiveRaw = await boundary.callJudge('captain-output-adjudication', input.stateId, defaultBuildCaptainJudgePrompt(input, finalText, {
5569
+ reply: raw,
5570
+ error: error.message,
5571
+ }), active);
5572
+ try {
5573
+ output = adjudicateCaptainOutput(extractFields, input, finalText, correctiveRaw);
5574
+ }
5575
+ catch (corrective) {
5576
+ if (!(corrective instanceof CaptainAdjudicationStructureError)) {
5577
+ throw corrective;
5578
+ }
5579
+ // A second invalid reply parks the step for Boss as a
5580
+ // decided adjudication failure, never a control-plane error.
5581
+ const failure = markFsmResultFailure(new Error(`${label} Captain outcome stayed undecided: ${corrective.message}`));
5582
+ const cause = governedSettlementCause('corrective semantic candidate is invalid', undefined, false, undefined);
5583
+ failureCauses.retain(failure, cause);
5584
+ throw attachPlaybookFailureCause(failure, cause);
5585
+ }
5586
+ }
5338
5587
  validateBossReplyOutput(input, output, resumableStateIds);
5339
5588
  return output;
5340
5589
  }