@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
|
@@ -28,6 +28,7 @@ import type {
|
|
|
28
28
|
PromiseActorLogic,
|
|
29
29
|
} from 'xstate';
|
|
30
30
|
import {
|
|
31
|
+
ACCEPTED_OUTCOME_ACTION_TYPE,
|
|
31
32
|
createAcceptedOutcomeConsumer,
|
|
32
33
|
type AcceptedOutcomeReceipt,
|
|
33
34
|
} from './accepted-outcome.js';
|
|
@@ -38,6 +39,7 @@ import {
|
|
|
38
39
|
attachPlaybookFailureCause,
|
|
39
40
|
combineAbortSignals,
|
|
40
41
|
createNestedPlaybookBridge,
|
|
42
|
+
describePlaybookSemanticValueShape,
|
|
41
43
|
detachPersistedMachineSnapshot,
|
|
42
44
|
normalizeError,
|
|
43
45
|
normalizePlaybookSnapshot,
|
|
@@ -49,6 +51,8 @@ import {
|
|
|
49
51
|
validateCaptainResult,
|
|
50
52
|
validatePlayerResult,
|
|
51
53
|
waitForPlaybookQuiescence,
|
|
54
|
+
type PlaybookSemanticValueShape,
|
|
55
|
+
type PlaybookSemanticValueShapes,
|
|
52
56
|
} from './xstate-runtime.js';
|
|
53
57
|
import type {
|
|
54
58
|
CaptainResult,
|
|
@@ -1760,6 +1764,7 @@ export function renderGovernedOutcomeContract(
|
|
|
1760
1764
|
guard: string,
|
|
1761
1765
|
description: string,
|
|
1762
1766
|
outcome: XStateGovernedOutcomeSpec | undefined,
|
|
1767
|
+
shapes?: Readonly<Record<string, PlaybookSemanticValueShape>>,
|
|
1763
1768
|
): string[] {
|
|
1764
1769
|
const { meaning, clause } = splitOutputClause(description);
|
|
1765
1770
|
// Each field's authored segment runs from its token to the next token:
|
|
@@ -1790,13 +1795,24 @@ export function renderGovernedOutcomeContract(
|
|
|
1790
1795
|
}
|
|
1791
1796
|
const replyMembers = [`"guard": ${JSON.stringify(guard)}`];
|
|
1792
1797
|
const semanticAsAuthored: string[] = [];
|
|
1798
|
+
const valueShapes: string[] = [];
|
|
1793
1799
|
const runtimeSupplied: string[] = [];
|
|
1794
1800
|
for (const [field, authority] of Object.entries(outcome?.fields ?? {})) {
|
|
1795
1801
|
if (authority === 'semantic') {
|
|
1796
1802
|
const entry = authored.get(field);
|
|
1803
|
+
// DR-089: the value has the shape the compiled machine admits.
|
|
1804
|
+
const shape =
|
|
1805
|
+
shapes !== undefined && Object.prototype.hasOwnProperty.call(shapes, field)
|
|
1806
|
+
? shapes[field]!
|
|
1807
|
+
: 'string';
|
|
1808
|
+
const words = describePlaybookSemanticValueShape(shape);
|
|
1797
1809
|
replyMembers.push(
|
|
1798
|
-
`${JSON.stringify(field)}: ${
|
|
1810
|
+
`${JSON.stringify(field)}: ${
|
|
1811
|
+
entry?.placeholder ??
|
|
1812
|
+
(shape === 'string' ? '<string>' : `<${words.replace(/^a /, '')}>`)
|
|
1813
|
+
}`,
|
|
1799
1814
|
);
|
|
1815
|
+
if (shape !== 'string') valueShapes.push(`\`${field}\` is ${words}`);
|
|
1800
1816
|
semanticAsAuthored.push(entry?.segment ?? `\`${field}\``);
|
|
1801
1817
|
} else {
|
|
1802
1818
|
runtimeSupplied.push(`\`${field}\` (${authority}-owned)`);
|
|
@@ -1806,6 +1822,9 @@ export function renderGovernedOutcomeContract(
|
|
|
1806
1822
|
meaning.length === 0 ? `- \`${guard}\`` : `- \`${guard}\` — ${meaning}`,
|
|
1807
1823
|
` Reply exactly: { ${replyMembers.join(', ')} }`,
|
|
1808
1824
|
];
|
|
1825
|
+
if (valueShapes.length > 0) {
|
|
1826
|
+
lines.push(` JSON value shape: ${valueShapes.join('; ')}`);
|
|
1827
|
+
}
|
|
1809
1828
|
if (semanticAsAuthored.length > 0) {
|
|
1810
1829
|
lines.push(
|
|
1811
1830
|
` Semantic fields as authored: ${semanticAsAuthored.join('; ')}`,
|
|
@@ -1824,6 +1843,7 @@ function buildGovernedJudgePrompt(
|
|
|
1824
1843
|
finalText: string,
|
|
1825
1844
|
outcomes: Readonly<Record<string, XStateGovernedOutcomeSpec>>,
|
|
1826
1845
|
correction?: { readonly reply: string; readonly error: string },
|
|
1846
|
+
shapes?: PlaybookSemanticValueShapes,
|
|
1827
1847
|
): string {
|
|
1828
1848
|
const lines = [
|
|
1829
1849
|
'This is hidden control work. Do not call tools, inspect files, or seek external evidence.',
|
|
@@ -1844,7 +1864,12 @@ function buildGovernedJudgePrompt(
|
|
|
1844
1864
|
];
|
|
1845
1865
|
for (const [guard, description] of Object.entries(input.result)) {
|
|
1846
1866
|
lines.push(
|
|
1847
|
-
...renderGovernedOutcomeContract(
|
|
1867
|
+
...renderGovernedOutcomeContract(
|
|
1868
|
+
guard,
|
|
1869
|
+
description,
|
|
1870
|
+
outcomes[guard],
|
|
1871
|
+
shapes?.[guard],
|
|
1872
|
+
),
|
|
1848
1873
|
);
|
|
1849
1874
|
}
|
|
1850
1875
|
if (correction !== undefined) {
|
|
@@ -2103,7 +2128,13 @@ export function createPlayerBridge(
|
|
|
2103
2128
|
/**
|
|
2104
2129
|
* Default direct-Captain adjudicator prompt (DR-025). The single statement of
|
|
2105
2130
|
* the `{ guard, …structuralPayloadFields }` reply contract, shared with the
|
|
2106
|
-
* compiled default Captain artifact so the wording cannot drift.
|
|
2131
|
+
* compiled default Captain artifact so the wording cannot drift. Each declared
|
|
2132
|
+
* key keeps its description verbatim and gains the exact reply it takes
|
|
2133
|
+
* (DR-089): `guard` plus the structural fields its `Output shall include`
|
|
2134
|
+
* clause names, each with its authored placeholder, while the visible
|
|
2135
|
+
* `question` and `response` fields are named as runtime-supplied. A
|
|
2136
|
+
* `correction` restates a structurally invalid first reply and its
|
|
2137
|
+
* validation error for the one corrective adjudication.
|
|
2107
2138
|
*/
|
|
2108
2139
|
export function defaultBuildCaptainJudgePrompt(
|
|
2109
2140
|
input: {
|
|
@@ -2112,6 +2143,7 @@ export function defaultBuildCaptainJudgePrompt(
|
|
|
2112
2143
|
readonly result: Readonly<Record<string, string>>;
|
|
2113
2144
|
},
|
|
2114
2145
|
finalText: string,
|
|
2146
|
+
correction?: { readonly reply: string; readonly error: string },
|
|
2115
2147
|
): string {
|
|
2116
2148
|
const lines: string[] = [];
|
|
2117
2149
|
lines.push('Adjudicate the direct Captain output for this FSM state.');
|
|
@@ -2126,6 +2158,35 @@ export function defaultBuildCaptainJudgePrompt(
|
|
|
2126
2158
|
lines.push('Result keys and descriptions:');
|
|
2127
2159
|
for (const [key, description] of Object.entries(input.result)) {
|
|
2128
2160
|
lines.push(`- \`${key}\` — ${description}`);
|
|
2161
|
+
const { clause } = splitOutputClause(description);
|
|
2162
|
+
const replyMembers = [`"guard": ${JSON.stringify(key)}`];
|
|
2163
|
+
const runtimeSupplied: string[] = [];
|
|
2164
|
+
const seen = new Set<string>();
|
|
2165
|
+
for (const m of clause === undefined
|
|
2166
|
+
? []
|
|
2167
|
+
: clause.matchAll(PAYLOAD_FIELD_PATTERN)) {
|
|
2168
|
+
const field = m[1]!;
|
|
2169
|
+
if (seen.has(field)) continue;
|
|
2170
|
+
seen.add(field);
|
|
2171
|
+
if (field === 'question' || field === 'response') {
|
|
2172
|
+
runtimeSupplied.push(`\`${field}\``);
|
|
2173
|
+
continue;
|
|
2174
|
+
}
|
|
2175
|
+
const placeholder = m[2]?.trim();
|
|
2176
|
+
replyMembers.push(
|
|
2177
|
+
`${JSON.stringify(field)}: ${
|
|
2178
|
+
placeholder !== undefined && placeholder.length > 0
|
|
2179
|
+
? placeholder
|
|
2180
|
+
: '<value>'
|
|
2181
|
+
}`,
|
|
2182
|
+
);
|
|
2183
|
+
}
|
|
2184
|
+
lines.push(` Reply exactly: { ${replyMembers.join(', ')} }`);
|
|
2185
|
+
if (runtimeSupplied.length > 0) {
|
|
2186
|
+
lines.push(
|
|
2187
|
+
` Runtime-supplied, do not include: ${runtimeSupplied.join(', ')}`,
|
|
2188
|
+
);
|
|
2189
|
+
}
|
|
2129
2190
|
}
|
|
2130
2191
|
lines.push('');
|
|
2131
2192
|
lines.push(
|
|
@@ -2133,26 +2194,66 @@ export function defaultBuildCaptainJudgePrompt(
|
|
|
2133
2194
|
'`{ guard, …structuralPayloadFields }`. Do not include `question` or ' +
|
|
2134
2195
|
'`response`; the runtime injects the visible text.',
|
|
2135
2196
|
);
|
|
2197
|
+
lines.push(
|
|
2198
|
+
'Reply with exactly that outcome\'s reply shape above and no other field.',
|
|
2199
|
+
);
|
|
2200
|
+
if (correction !== undefined) {
|
|
2201
|
+
lines.push(
|
|
2202
|
+
'',
|
|
2203
|
+
'Your first reply was structurally invalid:',
|
|
2204
|
+
'',
|
|
2205
|
+
'```',
|
|
2206
|
+
correction.reply,
|
|
2207
|
+
'```',
|
|
2208
|
+
'',
|
|
2209
|
+
`Validation error: ${correction.error}`,
|
|
2210
|
+
'Correct only that structure using the same Captain output and outcome schema.',
|
|
2211
|
+
);
|
|
2212
|
+
}
|
|
2136
2213
|
return lines.join('\n');
|
|
2137
2214
|
}
|
|
2138
2215
|
|
|
2216
|
+
/**
|
|
2217
|
+
* A direct-Captain adjudication reply that fails structural validation
|
|
2218
|
+
* (DR-089): it earns one corrective adjudication, and a second such reply
|
|
2219
|
+
* parks the step as a decided `judge-failed` failure rather than escaping as
|
|
2220
|
+
* a control-plane error.
|
|
2221
|
+
*/
|
|
2222
|
+
class CaptainAdjudicationStructureError extends Error {
|
|
2223
|
+
constructor(message: string) {
|
|
2224
|
+
super(message);
|
|
2225
|
+
this.name = 'CaptainAdjudicationStructureError';
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2139
2229
|
function adjudicateCaptainOutput(
|
|
2140
2230
|
extractFields: (description: string) => string[],
|
|
2141
2231
|
input: PlaybookCaptainInput,
|
|
2142
2232
|
finalText: string,
|
|
2143
2233
|
judgeText: string,
|
|
2144
2234
|
): PlaybookActorOutput {
|
|
2145
|
-
|
|
2235
|
+
let parsed: unknown;
|
|
2236
|
+
try {
|
|
2237
|
+
parsed = parseJudgeJson(judgeText);
|
|
2238
|
+
} catch (error) {
|
|
2239
|
+
throw new CaptainAdjudicationStructureError(
|
|
2240
|
+
`adjudicate: ${error instanceof Error ? error.message : 'judge response is not valid JSON'}`,
|
|
2241
|
+
);
|
|
2242
|
+
}
|
|
2146
2243
|
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
2147
|
-
throw new
|
|
2244
|
+
throw new CaptainAdjudicationStructureError(
|
|
2245
|
+
'adjudicate: judge response is not a JSON object',
|
|
2246
|
+
);
|
|
2148
2247
|
}
|
|
2149
2248
|
const obj = parsed as Record<string, unknown>;
|
|
2150
2249
|
const guard = obj.guard;
|
|
2151
2250
|
if (typeof guard !== 'string') {
|
|
2152
|
-
throw new
|
|
2251
|
+
throw new CaptainAdjudicationStructureError(
|
|
2252
|
+
'adjudicate: judge response missing string "guard" field',
|
|
2253
|
+
);
|
|
2153
2254
|
}
|
|
2154
2255
|
if (!Object.prototype.hasOwnProperty.call(input.result, guard)) {
|
|
2155
|
-
throw new
|
|
2256
|
+
throw new CaptainAdjudicationStructureError(
|
|
2156
2257
|
`adjudicate: unknown guard "${guard}" — declared guards: ${Object.keys(
|
|
2157
2258
|
input.result,
|
|
2158
2259
|
).join(', ')}`,
|
|
@@ -2165,7 +2266,7 @@ function adjudicateCaptainOutput(
|
|
|
2165
2266
|
}
|
|
2166
2267
|
for (const key of Object.keys(obj)) {
|
|
2167
2268
|
if (!allowed.has(key)) {
|
|
2168
|
-
throw new
|
|
2269
|
+
throw new CaptainAdjudicationStructureError(
|
|
2169
2270
|
`adjudicate: judge response supplied undeclared field "${key}" for guard "${guard}"`,
|
|
2170
2271
|
);
|
|
2171
2272
|
}
|
|
@@ -2173,7 +2274,7 @@ function adjudicateCaptainOutput(
|
|
|
2173
2274
|
for (const field of required) {
|
|
2174
2275
|
if (field === 'question' || field === 'response') continue;
|
|
2175
2276
|
if (obj[field] === undefined || obj[field] === null) {
|
|
2176
|
-
throw new
|
|
2277
|
+
throw new CaptainAdjudicationStructureError(
|
|
2177
2278
|
`adjudicate: judge response missing required field "${field}" for guard "${guard}"`,
|
|
2178
2279
|
);
|
|
2179
2280
|
}
|
|
@@ -3165,6 +3266,186 @@ function snapshotOutcomeAuthority(
|
|
|
3165
3266
|
});
|
|
3166
3267
|
}
|
|
3167
3268
|
|
|
3269
|
+
// ---------------------------------------------------------------------------
|
|
3270
|
+
// DR-089: one type contract for semantic-owned fields. The compiled machine's
|
|
3271
|
+
// own validator decides which JSON value it admits for each field, so the
|
|
3272
|
+
// runtime asks it once at construction: each governed outcome is presented to
|
|
3273
|
+
// the machine's transition selection — guards only, no action runs — with
|
|
3274
|
+
// sample values, and the first combination of shapes whose arm carries that
|
|
3275
|
+
// outcome's accepted-outcome marker is the contract the judge is shown and the
|
|
3276
|
+
// reconciler enforces. A field whose shape the machine does not reveal, or
|
|
3277
|
+
// that it admits as a string, stays a string.
|
|
3278
|
+
// ---------------------------------------------------------------------------
|
|
3279
|
+
|
|
3280
|
+
const SEMANTIC_SHAPE_SAMPLES: readonly (readonly [
|
|
3281
|
+
PlaybookSemanticValueShape,
|
|
3282
|
+
JsonValue,
|
|
3283
|
+
])[] = [
|
|
3284
|
+
['string', 'sample'],
|
|
3285
|
+
['string-array', ['sample']],
|
|
3286
|
+
['number-array', [1]],
|
|
3287
|
+
['number', 1],
|
|
3288
|
+
['boolean', true],
|
|
3289
|
+
];
|
|
3290
|
+
const SHAPE_PROBE_TEXT = 'sample';
|
|
3291
|
+
const SHAPE_PROBE_COMMIT = '0123456789abcdef0123456789abcdef01234567';
|
|
3292
|
+
const SHAPE_PROBE_FIELD_LIMIT = 4;
|
|
3293
|
+
|
|
3294
|
+
interface ProbedStateNode {
|
|
3295
|
+
readonly key: string;
|
|
3296
|
+
readonly path: readonly string[];
|
|
3297
|
+
readonly config: { readonly id?: unknown; readonly meta?: unknown };
|
|
3298
|
+
readonly invoke: readonly { readonly id: string; readonly src: unknown }[];
|
|
3299
|
+
readonly states: Readonly<Record<string, ProbedStateNode>>;
|
|
3300
|
+
}
|
|
3301
|
+
|
|
3302
|
+
function isAcceptedOutcomeMarker(
|
|
3303
|
+
action: unknown,
|
|
3304
|
+
stateId: string,
|
|
3305
|
+
guard: string,
|
|
3306
|
+
): boolean {
|
|
3307
|
+
if (!isPlainObject(action) || action.type !== ACCEPTED_OUTCOME_ACTION_TYPE) {
|
|
3308
|
+
return false;
|
|
3309
|
+
}
|
|
3310
|
+
const params = action.params;
|
|
3311
|
+
return (
|
|
3312
|
+
isPlainObject(params) &&
|
|
3313
|
+
params.source === stateId &&
|
|
3314
|
+
params.acceptedOutcome === guard
|
|
3315
|
+
);
|
|
3316
|
+
}
|
|
3317
|
+
|
|
3318
|
+
function shapeCombinations(fieldCount: number): readonly number[][] {
|
|
3319
|
+
const combinations: number[][] = [[]];
|
|
3320
|
+
for (let field = 0; field < fieldCount; field += 1) {
|
|
3321
|
+
const next: number[][] = [];
|
|
3322
|
+
for (const prefix of combinations) {
|
|
3323
|
+
for (let sample = 0; sample < SEMANTIC_SHAPE_SAMPLES.length; sample += 1) {
|
|
3324
|
+
next.push([...prefix, sample]);
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
combinations.splice(0, combinations.length, ...next);
|
|
3328
|
+
}
|
|
3329
|
+
// Fewest non-string fields first, then the sample order, so a machine that
|
|
3330
|
+
// admits strings keeps them.
|
|
3331
|
+
return combinations.sort(
|
|
3332
|
+
(left, right) =>
|
|
3333
|
+
left.filter((index) => index !== 0).length -
|
|
3334
|
+
right.filter((index) => index !== 0).length ||
|
|
3335
|
+
left.join(',').localeCompare(right.join(',')),
|
|
3336
|
+
);
|
|
3337
|
+
}
|
|
3338
|
+
|
|
3339
|
+
/**
|
|
3340
|
+
* DR-089: the non-string JSON value shape the compiled machine admits for
|
|
3341
|
+
* each semantic-owned field of each governed outcome, keyed by state id and
|
|
3342
|
+
* then by outcome. Exported so linked machinery of an artifact's own reads
|
|
3343
|
+
* the same contract the shared factory enforces.
|
|
3344
|
+
*/
|
|
3345
|
+
export function inferSemanticValueShapes(
|
|
3346
|
+
machine: AnyStateMachine,
|
|
3347
|
+
authority: XStateOutcomeAuthoritySpec,
|
|
3348
|
+
): ReadonlyMap<string, PlaybookSemanticValueShapes> {
|
|
3349
|
+
const shapesByState = new Map<string, PlaybookSemanticValueShapes>();
|
|
3350
|
+
const playerNodes = new Map<string, ProbedStateNode>();
|
|
3351
|
+
const visit = (node: ProbedStateNode): void => {
|
|
3352
|
+
if (node.invoke.some(({ src }) => src === 'player')) {
|
|
3353
|
+
const playbookMeta = isPlainObject(node.config.meta)
|
|
3354
|
+
? node.config.meta.playbook
|
|
3355
|
+
: undefined;
|
|
3356
|
+
const stateId =
|
|
3357
|
+
isPlainObject(playbookMeta) && typeof playbookMeta.stateId === 'string'
|
|
3358
|
+
? playbookMeta.stateId
|
|
3359
|
+
: typeof node.config.id === 'string'
|
|
3360
|
+
? node.config.id
|
|
3361
|
+
: node.key;
|
|
3362
|
+
if (!playerNodes.has(stateId)) playerNodes.set(stateId, node);
|
|
3363
|
+
}
|
|
3364
|
+
for (const child of Object.values(node.states)) visit(child);
|
|
3365
|
+
};
|
|
3366
|
+
try {
|
|
3367
|
+
visit((machine as unknown as { root: ProbedStateNode }).root);
|
|
3368
|
+
} catch {
|
|
3369
|
+
return shapesByState;
|
|
3370
|
+
}
|
|
3371
|
+
const configContext = (machine as unknown as { config?: { context?: unknown } })
|
|
3372
|
+
.config?.context;
|
|
3373
|
+
const context = isPlainObject(configContext) ? configContext : {};
|
|
3374
|
+
for (const [stateId, outcomes] of Object.entries(
|
|
3375
|
+
authority.governedPlayerStates,
|
|
3376
|
+
)) {
|
|
3377
|
+
const node = playerNodes.get(stateId);
|
|
3378
|
+
const invokeId = node?.invoke.find(({ src }) => src === 'player')?.id;
|
|
3379
|
+
if (node === undefined || invokeId === undefined) continue;
|
|
3380
|
+
let snapshot: unknown;
|
|
3381
|
+
try {
|
|
3382
|
+
const value = [...node.path].reduceRight<unknown>(
|
|
3383
|
+
(inner, key) => (inner === undefined ? key : { [key]: inner }),
|
|
3384
|
+
undefined,
|
|
3385
|
+
);
|
|
3386
|
+
snapshot = machine.resolveState({ value: value as never, context });
|
|
3387
|
+
} catch {
|
|
3388
|
+
continue;
|
|
3389
|
+
}
|
|
3390
|
+
const stateShapes: Record<string, Readonly<Record<string, PlaybookSemanticValueShape>>> = {};
|
|
3391
|
+
for (const [guard, outcome] of Object.entries(outcomes)) {
|
|
3392
|
+
const semanticFields = Object.entries(outcome.fields)
|
|
3393
|
+
.filter(([, fieldAuthority]) => fieldAuthority === 'semantic')
|
|
3394
|
+
.map(([field]) => field);
|
|
3395
|
+
if (
|
|
3396
|
+
semanticFields.length === 0 ||
|
|
3397
|
+
semanticFields.length > SHAPE_PROBE_FIELD_LIMIT
|
|
3398
|
+
) {
|
|
3399
|
+
continue;
|
|
3400
|
+
}
|
|
3401
|
+
const base: Record<string, JsonValue> = { guard };
|
|
3402
|
+
for (const [field, fieldAuthority] of Object.entries(outcome.fields)) {
|
|
3403
|
+
if (fieldAuthority === 'effect') base[field] = SHAPE_PROBE_COMMIT;
|
|
3404
|
+
else if (fieldAuthority !== 'semantic') base[field] = SHAPE_PROBE_TEXT;
|
|
3405
|
+
}
|
|
3406
|
+
const admits = (values: Readonly<Record<string, JsonValue>>): boolean => {
|
|
3407
|
+
try {
|
|
3408
|
+
const transitions = machine.getTransitionData(snapshot as never, {
|
|
3409
|
+
type: `xstate.done.actor.${invokeId}`,
|
|
3410
|
+
output: { ...base, ...values },
|
|
3411
|
+
actorId: invokeId,
|
|
3412
|
+
} as never);
|
|
3413
|
+
return transitions.some((transition) =>
|
|
3414
|
+
(transition.actions as readonly unknown[]).some((action) =>
|
|
3415
|
+
isAcceptedOutcomeMarker(action, stateId, guard),
|
|
3416
|
+
),
|
|
3417
|
+
);
|
|
3418
|
+
} catch {
|
|
3419
|
+
return false;
|
|
3420
|
+
}
|
|
3421
|
+
};
|
|
3422
|
+
for (const combination of shapeCombinations(semanticFields.length)) {
|
|
3423
|
+
const values: Record<string, JsonValue> = {};
|
|
3424
|
+
semanticFields.forEach((field, index) => {
|
|
3425
|
+
values[field] = SEMANTIC_SHAPE_SAMPLES[combination[index]!]![1];
|
|
3426
|
+
});
|
|
3427
|
+
if (!admits(values)) continue;
|
|
3428
|
+
const shapes: Record<string, PlaybookSemanticValueShape> = {};
|
|
3429
|
+
semanticFields.forEach((field, index) => {
|
|
3430
|
+
let shape = SEMANTIC_SHAPE_SAMPLES[combination[index]!]![0];
|
|
3431
|
+
if (shape === 'string-array' && admits({ ...values, [field]: [1] })) {
|
|
3432
|
+
shape = 'string-or-number-array';
|
|
3433
|
+
}
|
|
3434
|
+
if (shape !== 'string') shapes[field] = shape;
|
|
3435
|
+
});
|
|
3436
|
+
if (Object.keys(shapes).length > 0) {
|
|
3437
|
+
stateShapes[guard] = Object.freeze(shapes);
|
|
3438
|
+
}
|
|
3439
|
+
break;
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
if (Object.keys(stateShapes).length > 0) {
|
|
3443
|
+
shapesByState.set(stateId, Object.freeze(stateShapes));
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
return shapesByState;
|
|
3447
|
+
}
|
|
3448
|
+
|
|
3168
3449
|
function sameStringSet(left: readonly string[], right: readonly string[]): boolean {
|
|
3169
3450
|
if (left.length !== right.length) return false;
|
|
3170
3451
|
const expected = new Set(right);
|
|
@@ -4010,6 +4291,10 @@ export function createXStatePlaybookRuntime<
|
|
|
4010
4291
|
roleStates,
|
|
4011
4292
|
verbatimPayloadFields,
|
|
4012
4293
|
);
|
|
4294
|
+
const semanticValueShapes = inferSemanticValueShapes(
|
|
4295
|
+
machine,
|
|
4296
|
+
outcomeAuthority,
|
|
4297
|
+
);
|
|
4013
4298
|
for (const [stateId, outcomes] of Object.entries(
|
|
4014
4299
|
outcomeAuthority.governedPlayerStates,
|
|
4015
4300
|
)) {
|
|
@@ -4584,6 +4869,7 @@ export function createXStatePlaybookRuntime<
|
|
|
4584
4869
|
semanticCandidate: candidate.semanticCandidate,
|
|
4585
4870
|
finalText: candidate.finalText,
|
|
4586
4871
|
receipt,
|
|
4872
|
+
semanticValueShapes: semanticValueShapes.get(candidate.sourceStateId),
|
|
4587
4873
|
});
|
|
4588
4874
|
if (
|
|
4589
4875
|
awaitingLogicalReceipt &&
|
|
@@ -5814,7 +6100,13 @@ export function createXStatePlaybookRuntime<
|
|
|
5814
6100
|
raw = await boundary.callJudge(
|
|
5815
6101
|
'player-output-adjudication',
|
|
5816
6102
|
input.stateId,
|
|
5817
|
-
buildGovernedJudgePrompt(
|
|
6103
|
+
buildGovernedJudgePrompt(
|
|
6104
|
+
input,
|
|
6105
|
+
finalText,
|
|
6106
|
+
outcomes,
|
|
6107
|
+
undefined,
|
|
6108
|
+
semanticValueShapes.get(input.stateId),
|
|
6109
|
+
),
|
|
5818
6110
|
signal,
|
|
5819
6111
|
);
|
|
5820
6112
|
} catch (error) {
|
|
@@ -5857,6 +6149,7 @@ export function createXStatePlaybookRuntime<
|
|
|
5857
6149
|
semanticCandidate: candidate,
|
|
5858
6150
|
finalText,
|
|
5859
6151
|
receipt: completion.outcomeReceipt,
|
|
6152
|
+
semanticValueShapes: semanticValueShapes.get(input.stateId),
|
|
5860
6153
|
});
|
|
5861
6154
|
} catch (error) {
|
|
5862
6155
|
if (!(error instanceof PlaybookSemanticCandidateStructureError)) {
|
|
@@ -5903,10 +6196,16 @@ export function createXStatePlaybookRuntime<
|
|
|
5903
6196
|
correctiveRaw = await boundary.callJudge(
|
|
5904
6197
|
'player-output-adjudication',
|
|
5905
6198
|
input.stateId,
|
|
5906
|
-
buildGovernedJudgePrompt(
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
6199
|
+
buildGovernedJudgePrompt(
|
|
6200
|
+
input,
|
|
6201
|
+
finalText,
|
|
6202
|
+
outcomes,
|
|
6203
|
+
{
|
|
6204
|
+
reply: raw,
|
|
6205
|
+
error: structuralError.message,
|
|
6206
|
+
},
|
|
6207
|
+
semanticValueShapes.get(input.stateId),
|
|
6208
|
+
),
|
|
5910
6209
|
signal,
|
|
5911
6210
|
);
|
|
5912
6211
|
} catch (error) {
|
|
@@ -5928,6 +6227,7 @@ export function createXStatePlaybookRuntime<
|
|
|
5928
6227
|
semanticCandidate: candidate,
|
|
5929
6228
|
finalText,
|
|
5930
6229
|
receipt: completion.outcomeReceipt,
|
|
6230
|
+
semanticValueShapes: semanticValueShapes.get(input.stateId),
|
|
5931
6231
|
});
|
|
5932
6232
|
} catch (error) {
|
|
5933
6233
|
if (!(error instanceof PlaybookSemanticCandidateStructureError)) {
|
|
@@ -6468,7 +6768,13 @@ export function createXStatePlaybookRuntime<
|
|
|
6468
6768
|
await boundary.callJudge(
|
|
6469
6769
|
'player-output-adjudication',
|
|
6470
6770
|
saved.sourceStateId,
|
|
6471
|
-
buildGovernedJudgePrompt(
|
|
6771
|
+
buildGovernedJudgePrompt(
|
|
6772
|
+
actorInput,
|
|
6773
|
+
saved.finalText!,
|
|
6774
|
+
outcomes,
|
|
6775
|
+
undefined,
|
|
6776
|
+
semanticValueShapes.get(saved.sourceStateId),
|
|
6777
|
+
),
|
|
6472
6778
|
signal,
|
|
6473
6779
|
),
|
|
6474
6780
|
);
|
|
@@ -6477,6 +6783,7 @@ export function createXStatePlaybookRuntime<
|
|
|
6477
6783
|
semanticCandidate: candidate,
|
|
6478
6784
|
finalText: saved.finalText,
|
|
6479
6785
|
receipt: saved.physicalReceipt,
|
|
6786
|
+
semanticValueShapes: semanticValueShapes.get(saved.sourceStateId),
|
|
6480
6787
|
});
|
|
6481
6788
|
signal.throwIfAborted();
|
|
6482
6789
|
const next = {
|
|
@@ -7958,12 +8265,59 @@ export function createXStatePlaybookRuntime<
|
|
|
7958
8265
|
judgePrompt,
|
|
7959
8266
|
active,
|
|
7960
8267
|
);
|
|
7961
|
-
|
|
7962
|
-
|
|
7963
|
-
|
|
7964
|
-
|
|
7965
|
-
|
|
7966
|
-
|
|
8268
|
+
let output: PlaybookActorOutput;
|
|
8269
|
+
try {
|
|
8270
|
+
output = adjudicateCaptainOutput(
|
|
8271
|
+
extractFields,
|
|
8272
|
+
input,
|
|
8273
|
+
finalText,
|
|
8274
|
+
raw,
|
|
8275
|
+
);
|
|
8276
|
+
} catch (error) {
|
|
8277
|
+
if (!(error instanceof CaptainAdjudicationStructureError)) {
|
|
8278
|
+
throw error;
|
|
8279
|
+
}
|
|
8280
|
+
// DR-089: one corrective adjudication over the same visible
|
|
8281
|
+
// output, restating the reply and its validation error; an
|
|
8282
|
+
// abort before it begins starts none.
|
|
8283
|
+
active.throwIfAborted();
|
|
8284
|
+
const correctiveRaw = await boundary.callJudge(
|
|
8285
|
+
'captain-output-adjudication',
|
|
8286
|
+
input.stateId,
|
|
8287
|
+
defaultBuildCaptainJudgePrompt(input, finalText, {
|
|
8288
|
+
reply: raw,
|
|
8289
|
+
error: error.message,
|
|
8290
|
+
}),
|
|
8291
|
+
active,
|
|
8292
|
+
);
|
|
8293
|
+
try {
|
|
8294
|
+
output = adjudicateCaptainOutput(
|
|
8295
|
+
extractFields,
|
|
8296
|
+
input,
|
|
8297
|
+
finalText,
|
|
8298
|
+
correctiveRaw,
|
|
8299
|
+
);
|
|
8300
|
+
} catch (corrective) {
|
|
8301
|
+
if (!(corrective instanceof CaptainAdjudicationStructureError)) {
|
|
8302
|
+
throw corrective;
|
|
8303
|
+
}
|
|
8304
|
+
// A second invalid reply parks the step for Boss as a
|
|
8305
|
+
// decided adjudication failure, never a control-plane error.
|
|
8306
|
+
const failure = markFsmResultFailure(
|
|
8307
|
+
new Error(
|
|
8308
|
+
`${label} Captain outcome stayed undecided: ${corrective.message}`,
|
|
8309
|
+
),
|
|
8310
|
+
);
|
|
8311
|
+
const cause = governedSettlementCause(
|
|
8312
|
+
'corrective semantic candidate is invalid',
|
|
8313
|
+
undefined,
|
|
8314
|
+
false,
|
|
8315
|
+
undefined,
|
|
8316
|
+
);
|
|
8317
|
+
failureCauses.retain(failure, cause);
|
|
8318
|
+
throw attachPlaybookFailureCause(failure, cause);
|
|
8319
|
+
}
|
|
8320
|
+
}
|
|
7967
8321
|
validateBossReplyOutput(input, output, resumableStateIds);
|
|
7968
8322
|
return output;
|
|
7969
8323
|
} catch (error) {
|
package/src/xstate-runtime.d.ts
CHANGED
|
@@ -54,6 +54,19 @@ export interface PlaybookSemanticOutcomeSpec {
|
|
|
54
54
|
readonly fields: Readonly<Record<string, PlaybookSemanticFieldAuthority>>;
|
|
55
55
|
readonly repositoryDisposition: PlaybookRepositoryDisposition;
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* The JSON value shape a compiled machine admits for one semantic-owned field
|
|
59
|
+
* (DR-089). A field the machine admits as a string, or whose shape the
|
|
60
|
+
* machine does not reveal, is `string`.
|
|
61
|
+
*/
|
|
62
|
+
export type PlaybookSemanticValueShape = 'string' | 'string-array' | 'number-array' | 'string-or-number-array' | 'number' | 'boolean';
|
|
63
|
+
/**
|
|
64
|
+
* Per outcome, the non-string shapes of its semantic-owned fields; a field
|
|
65
|
+
* left out is a string.
|
|
66
|
+
*/
|
|
67
|
+
export type PlaybookSemanticValueShapes = Readonly<Record<string, Readonly<Record<string, PlaybookSemanticValueShape>>>>;
|
|
68
|
+
/** The words a judge prompt or validation error uses for one value shape. */
|
|
69
|
+
export declare function describePlaybookSemanticValueShape(shape: PlaybookSemanticValueShape): string;
|
|
57
70
|
/** Evidence available when one governed semantic candidate is reconciled. */
|
|
58
71
|
export interface PlaybookSemanticEvidenceInput {
|
|
59
72
|
readonly outcomes: Readonly<Record<string, PlaybookSemanticOutcomeSpec>>;
|
|
@@ -61,15 +74,20 @@ export interface PlaybookSemanticEvidenceInput {
|
|
|
61
74
|
readonly finalText?: unknown;
|
|
62
75
|
readonly receipt?: unknown;
|
|
63
76
|
readonly runtimeFields?: unknown;
|
|
77
|
+
/**
|
|
78
|
+
* DR-089: the shape the compiled machine admits for each semantic-owned
|
|
79
|
+
* field, keyed by outcome; every field it leaves out is a string.
|
|
80
|
+
*/
|
|
81
|
+
readonly semanticValueShapes?: PlaybookSemanticValueShapes;
|
|
64
82
|
}
|
|
65
83
|
/** The exact detached actor output admitted to FSM delivery. */
|
|
66
|
-
export type PlaybookReconciledSemanticOutput = Readonly<Record<string,
|
|
84
|
+
export type PlaybookReconciledSemanticOutput = Readonly<Record<string, JsonValue>> & {
|
|
67
85
|
readonly guard: string;
|
|
68
86
|
};
|
|
69
87
|
/** Retained presentation and semantic evidence, kept separate from output. */
|
|
70
88
|
export interface PlaybookRetainedSemanticEvidence {
|
|
71
89
|
readonly finalText?: string;
|
|
72
|
-
readonly semanticCandidate: Readonly<Record<string,
|
|
90
|
+
readonly semanticCandidate: Readonly<Record<string, JsonValue>>;
|
|
73
91
|
}
|
|
74
92
|
export type PlaybookSemanticReconciliationReason = 'no-matching-outcome' | 'missing-presentation-evidence' | 'missing-repository-receipt' | 'invalid-repository-receipt' | 'repository-disposition-mismatch' | 'missing-effect-evidence' | 'missing-runtime-evidence' | 'inconsistent-runtime-evidence';
|
|
75
93
|
/** A fail-closed semantic/effect reconciliation decision. */
|
|
@@ -96,7 +114,9 @@ export declare class PlaybookSemanticCandidateStructureError extends TypeError {
|
|
|
96
114
|
* Reconcile one exact semantic candidate with host-owned effect evidence.
|
|
97
115
|
* Presentation prose remains opaque: it is retained verbatim and only its
|
|
98
116
|
* trimmed value is copied into linker-declared presentation fields. No
|
|
99
|
-
* repository fact is inferred from that prose or from the semantic candidate
|
|
117
|
+
* repository fact is inferred from that prose or from the semantic candidate;
|
|
118
|
+
* the prose may only name, as the work, the commit an `unchanged` receipt
|
|
119
|
+
* observed at HEAD (DR-088 §2).
|
|
100
120
|
*/
|
|
101
121
|
export declare function reconcilePlaybookSemanticEvidence(input: PlaybookSemanticEvidenceInput): PlaybookSemanticReconciliation;
|
|
102
122
|
/** Return the canonical empty host-owned effect-ledger mirror. */
|