@sublang/playbook 17.6.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/package.json +1 -1
- package/reference/sdlc/code.playbook/bin/machine-identity.js +18 -0
- package/reference/sdlc/code.playbook/bin/repository-effects.js +41 -23
- package/reference/sdlc/code.playbook/bin/session-store.js +73 -23
- package/reference/sdlc/code.playbook/host-capabilities.d.ts +2 -0
- package/reference/sdlc/code.playbook/playbook-captain.js +162 -12
- package/reference/sdlc/code.playbook/playbook-captain.ts +217 -11
- package/reference/sdlc/code.playbook/session-assets.js +7 -1
- package/reference/sdlc/code.playbook/session-store.d.ts +13 -1
- package/reference/sdlc/code.playbook/session-store.js +11 -1
- package/slc/link.md +29 -6
- package/slc/text2gears.md +5 -3
- package/src/runtime.d.ts +6 -0
- package/src/runtime.ts +6 -0
- package/src/xstate-playbook-runtime.d.ts +20 -3
- package/src/xstate-playbook-runtime.js +267 -18
- package/src/xstate-playbook-runtime.ts +375 -21
- package/src/xstate-runtime.d.ts +23 -3
- package/src/xstate-runtime.js +97 -13
- package/src/xstate-runtime.ts +159 -19
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
}
|