@sublang/playbook 12.0.0 → 12.2.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/docs/embedding.md +83 -1
- package/package.json +7 -1
- package/reference/sdlc/code.playbook/bin/repository-effects.js +501 -170
- package/reference/sdlc/code.playbook/bin/session-store.js +4 -1
- package/reference/sdlc/code.playbook/host-capabilities.d.ts +291 -0
- package/reference/sdlc/code.playbook/host-capabilities.js +40 -0
- package/reference/sdlc/code.playbook/playbook-captain.js +10 -0
- package/reference/sdlc/code.playbook/playbook-captain.ts +9 -0
- package/reference/sdlc/decide.playbook/decide.playbook.js +11 -7
- package/reference/sdlc/decide.playbook/decide.playbook.ts +11 -8
- package/slc/gears2fsm.md +20 -0
- package/slc/link.md +28 -1
- package/slc/text2gears.md +19 -1
- package/src/xstate-playbook-runtime.d.ts +15 -0
- package/src/xstate-playbook-runtime.js +127 -22
- package/src/xstate-playbook-runtime.ts +149 -24
|
@@ -4080,7 +4080,10 @@ function validateEffectLedgerAuthority(value, record, owner) {
|
|
|
4080
4080
|
return authority;
|
|
4081
4081
|
}
|
|
4082
4082
|
|
|
4083
|
-
|
|
4083
|
+
// Exported for the private worktree host-capability constructor in
|
|
4084
|
+
// repository-effects.js, so an external host's in-memory ledger applies the
|
|
4085
|
+
// exact command semantics the durable Captain record applies.
|
|
4086
|
+
export function applyEffectLedgerCommands(
|
|
4084
4087
|
ledgerValue,
|
|
4085
4088
|
authority,
|
|
4086
4089
|
uncertain,
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
|
|
3
|
+
|
|
4
|
+
export type JsonValue =
|
|
5
|
+
| null
|
|
6
|
+
| boolean
|
|
7
|
+
| number
|
|
8
|
+
| string
|
|
9
|
+
| readonly JsonValue[]
|
|
10
|
+
| { readonly [key: string]: JsonValue };
|
|
11
|
+
|
|
12
|
+
export type PlaybookRepositoryDisposition =
|
|
13
|
+
| 'unchanged'
|
|
14
|
+
| 'one-descendant-commit'
|
|
15
|
+
| 'deferred';
|
|
16
|
+
export type RepositoryReceiptClassification =
|
|
17
|
+
| 'unchanged'
|
|
18
|
+
| 'one-descendant-commit'
|
|
19
|
+
| 'multiple-commits'
|
|
20
|
+
| 'rewritten-or-non-descendant'
|
|
21
|
+
| 'worktree-only-change'
|
|
22
|
+
| 'concurrent-or-foreign-change'
|
|
23
|
+
| 'observation-ambiguous';
|
|
24
|
+
export declare const REPOSITORY_RECEIPT_CLASSIFICATIONS: readonly RepositoryReceiptClassification[];
|
|
25
|
+
|
|
26
|
+
export interface RepositoryIdentity {
|
|
27
|
+
readonly worktree: string;
|
|
28
|
+
readonly gitDir: string;
|
|
29
|
+
}
|
|
30
|
+
export interface PlaybookRepositoryObservation extends RepositoryIdentity {
|
|
31
|
+
readonly head: string;
|
|
32
|
+
readonly projection: Readonly<Record<string, JsonValue>>;
|
|
33
|
+
readonly projectionDigest: string;
|
|
34
|
+
}
|
|
35
|
+
export interface PlaybookRepositoryReceipt {
|
|
36
|
+
readonly classification: RepositoryReceiptClassification;
|
|
37
|
+
readonly baseline: PlaybookRepositoryObservation;
|
|
38
|
+
readonly after?: PlaybookRepositoryObservation;
|
|
39
|
+
readonly commitOid?: string;
|
|
40
|
+
}
|
|
41
|
+
export interface RepositoryReceiptOptions {
|
|
42
|
+
readonly allowedDispositions: readonly PlaybookRepositoryDisposition[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export declare function observeGitRepository(
|
|
46
|
+
cwd: string,
|
|
47
|
+
): Promise<PlaybookRepositoryObservation>;
|
|
48
|
+
export declare function classifyRepositoryReceipt(
|
|
49
|
+
baseline: PlaybookRepositoryObservation,
|
|
50
|
+
after: PlaybookRepositoryObservation,
|
|
51
|
+
options: RepositoryReceiptOptions,
|
|
52
|
+
): Promise<PlaybookRepositoryReceipt>;
|
|
53
|
+
export declare function captureRepositoryReceipt(
|
|
54
|
+
baseline: PlaybookRepositoryObservation,
|
|
55
|
+
options: RepositoryReceiptOptions,
|
|
56
|
+
): Promise<PlaybookRepositoryReceipt>;
|
|
57
|
+
|
|
58
|
+
export interface PlaybookPendingBossQuestion {
|
|
59
|
+
readonly questionId: string;
|
|
60
|
+
readonly asker:
|
|
61
|
+
| { readonly kind: 'captain' }
|
|
62
|
+
| { readonly kind: 'role'; readonly roleId: string };
|
|
63
|
+
readonly question: string;
|
|
64
|
+
readonly sourceItem?: string;
|
|
65
|
+
}
|
|
66
|
+
export interface PlaybookEffectBoundary {
|
|
67
|
+
readonly sequence: number;
|
|
68
|
+
readonly boundaryId: string;
|
|
69
|
+
readonly attemptId: string;
|
|
70
|
+
readonly attemptNumber: number;
|
|
71
|
+
readonly playbookId: string;
|
|
72
|
+
readonly runtimeSessionId: string;
|
|
73
|
+
readonly turnId: number;
|
|
74
|
+
readonly callId: string;
|
|
75
|
+
readonly roleId: string;
|
|
76
|
+
readonly sourceStateId: string;
|
|
77
|
+
readonly sourceOutcomeSchema: JsonValue;
|
|
78
|
+
readonly dispositions: readonly PlaybookRepositoryDisposition[];
|
|
79
|
+
readonly canonicalWorktree: RepositoryIdentity;
|
|
80
|
+
readonly baseline: PlaybookRepositoryObservation;
|
|
81
|
+
readonly after?: PlaybookRepositoryObservation;
|
|
82
|
+
readonly physicalReceipt?: PlaybookRepositoryReceipt;
|
|
83
|
+
readonly finalText?: string;
|
|
84
|
+
readonly semanticCandidate?: JsonValue;
|
|
85
|
+
readonly initialSemanticCandidate?: JsonValue;
|
|
86
|
+
readonly correctionBudget: {
|
|
87
|
+
readonly limit: 1;
|
|
88
|
+
readonly spent: boolean;
|
|
89
|
+
};
|
|
90
|
+
readonly cohortId?: string;
|
|
91
|
+
readonly logicalOperationId?: string;
|
|
92
|
+
}
|
|
93
|
+
export type PlaybookEffectBoundaryStart = Omit<
|
|
94
|
+
PlaybookEffectBoundary,
|
|
95
|
+
| 'sequence'
|
|
96
|
+
| 'attemptId'
|
|
97
|
+
| 'attemptNumber'
|
|
98
|
+
| 'after'
|
|
99
|
+
| 'physicalReceipt'
|
|
100
|
+
| 'finalText'
|
|
101
|
+
| 'semanticCandidate'
|
|
102
|
+
| 'initialSemanticCandidate'
|
|
103
|
+
>;
|
|
104
|
+
export type EffectBoundarySeed = Omit<
|
|
105
|
+
PlaybookEffectBoundaryStart,
|
|
106
|
+
'playbookId' | 'canonicalWorktree' | 'baseline' | 'cohortId'
|
|
107
|
+
>;
|
|
108
|
+
export interface PlaybookEffectLogicalOperation {
|
|
109
|
+
readonly sequence: number;
|
|
110
|
+
readonly operationId: string;
|
|
111
|
+
readonly playbookId: string;
|
|
112
|
+
readonly runtimeSessionId: string;
|
|
113
|
+
readonly boundaryIds: readonly string[];
|
|
114
|
+
readonly originalBaseline: PlaybookRepositoryObservation;
|
|
115
|
+
readonly checkpoint?: PlaybookRepositoryObservation;
|
|
116
|
+
readonly pendingQuestion?: PlaybookPendingBossQuestion;
|
|
117
|
+
readonly playerContinuation?: JsonValue;
|
|
118
|
+
readonly checkpointRestorationEligible: boolean;
|
|
119
|
+
readonly logicalReceipt?: PlaybookRepositoryReceipt;
|
|
120
|
+
}
|
|
121
|
+
export interface PlaybookEffectLedger {
|
|
122
|
+
readonly schemaVersion: 1;
|
|
123
|
+
readonly revision: number;
|
|
124
|
+
readonly boundaries: readonly PlaybookEffectBoundary[];
|
|
125
|
+
readonly logicalOperations: readonly PlaybookEffectLogicalOperation[];
|
|
126
|
+
}
|
|
127
|
+
export type PlaybookEffectLedgerCommand =
|
|
128
|
+
| {
|
|
129
|
+
readonly kind: 'start-boundaries';
|
|
130
|
+
readonly boundaries: readonly [
|
|
131
|
+
PlaybookEffectBoundaryStart,
|
|
132
|
+
...PlaybookEffectBoundaryStart[],
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
| {
|
|
136
|
+
readonly kind: 'replace-boundaries';
|
|
137
|
+
readonly replacements: readonly [
|
|
138
|
+
{
|
|
139
|
+
readonly expected: PlaybookEffectBoundary;
|
|
140
|
+
readonly next: PlaybookEffectBoundary;
|
|
141
|
+
},
|
|
142
|
+
...{
|
|
143
|
+
readonly expected: PlaybookEffectBoundary;
|
|
144
|
+
readonly next: PlaybookEffectBoundary;
|
|
145
|
+
}[],
|
|
146
|
+
];
|
|
147
|
+
}
|
|
148
|
+
| {
|
|
149
|
+
readonly kind: 'append-logical-operations';
|
|
150
|
+
readonly operations: readonly [
|
|
151
|
+
Omit<PlaybookEffectLogicalOperation, 'sequence'>,
|
|
152
|
+
...Omit<PlaybookEffectLogicalOperation, 'sequence'>[],
|
|
153
|
+
];
|
|
154
|
+
}
|
|
155
|
+
| {
|
|
156
|
+
readonly kind: 'replace-logical-operations';
|
|
157
|
+
readonly replacements: readonly [
|
|
158
|
+
{
|
|
159
|
+
readonly expected: PlaybookEffectLogicalOperation;
|
|
160
|
+
readonly next: PlaybookEffectLogicalOperation;
|
|
161
|
+
},
|
|
162
|
+
...{
|
|
163
|
+
readonly expected: PlaybookEffectLogicalOperation;
|
|
164
|
+
readonly next: PlaybookEffectLogicalOperation;
|
|
165
|
+
}[],
|
|
166
|
+
];
|
|
167
|
+
};
|
|
168
|
+
export type PlaybookEffectLedgerCommandBatch = readonly [
|
|
169
|
+
PlaybookEffectLedgerCommand,
|
|
170
|
+
...PlaybookEffectLedgerCommand[],
|
|
171
|
+
];
|
|
172
|
+
export interface PlaybookEffectLedgerCapability {
|
|
173
|
+
snapshot(): PlaybookEffectLedger;
|
|
174
|
+
writeAhead(
|
|
175
|
+
commands: PlaybookEffectLedgerCommandBatch,
|
|
176
|
+
): Promise<PlaybookEffectLedger>;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
interface RepositoryOperationSettlement<T> {
|
|
180
|
+
readonly status: 'fulfilled';
|
|
181
|
+
readonly value: T;
|
|
182
|
+
}
|
|
183
|
+
interface RepositoryOperationRejection {
|
|
184
|
+
readonly status: 'rejected';
|
|
185
|
+
readonly reason: unknown;
|
|
186
|
+
}
|
|
187
|
+
export interface RepositoryExclusiveCompletion<T> {
|
|
188
|
+
readonly boundary: PlaybookEffectBoundary;
|
|
189
|
+
readonly operation:
|
|
190
|
+
| RepositoryOperationSettlement<T>
|
|
191
|
+
| RepositoryOperationRejection;
|
|
192
|
+
readonly receipt: PlaybookRepositoryReceipt;
|
|
193
|
+
readonly outcomeReceipt: PlaybookRepositoryReceipt;
|
|
194
|
+
}
|
|
195
|
+
interface RepositoryDeferredBinding {
|
|
196
|
+
readonly operationId: string;
|
|
197
|
+
readonly pendingQuestion: PlaybookPendingBossQuestion;
|
|
198
|
+
readonly playerContinuation: JsonValue;
|
|
199
|
+
}
|
|
200
|
+
export interface RepositoryCompletionEvidence {
|
|
201
|
+
readonly finalText?: string;
|
|
202
|
+
readonly semanticCandidate?: JsonValue;
|
|
203
|
+
readonly deferred?: RepositoryDeferredBinding;
|
|
204
|
+
readonly unresolved?: true;
|
|
205
|
+
}
|
|
206
|
+
export interface RepositoryExclusiveResult<T> {
|
|
207
|
+
readonly operation:
|
|
208
|
+
| RepositoryOperationSettlement<T>
|
|
209
|
+
| RepositoryOperationRejection;
|
|
210
|
+
readonly receipt: PlaybookRepositoryReceipt;
|
|
211
|
+
readonly effectLedger: PlaybookEffectLedger;
|
|
212
|
+
readonly deferredStatus?: 'bound' | 'unresolved';
|
|
213
|
+
}
|
|
214
|
+
interface RepositoryDeferredContinuationResult<T> {
|
|
215
|
+
readonly status: 'continued';
|
|
216
|
+
readonly operation:
|
|
217
|
+
| RepositoryOperationSettlement<T>
|
|
218
|
+
| RepositoryOperationRejection;
|
|
219
|
+
readonly receipt: PlaybookRepositoryReceipt;
|
|
220
|
+
readonly logicalReceipt?: PlaybookRepositoryReceipt;
|
|
221
|
+
readonly effectLedger: PlaybookEffectLedger;
|
|
222
|
+
readonly deferredStatus?: 'bound' | 'unresolved';
|
|
223
|
+
}
|
|
224
|
+
interface RepositoryDeferredCheckpointMismatch {
|
|
225
|
+
readonly status: 'checkpoint-mismatch' | 'ineligible';
|
|
226
|
+
readonly effectLedger: PlaybookEffectLedger;
|
|
227
|
+
}
|
|
228
|
+
interface RepositoryDeferredParked {
|
|
229
|
+
readonly status: 'parked';
|
|
230
|
+
readonly effectLedger: PlaybookEffectLedger;
|
|
231
|
+
}
|
|
232
|
+
interface RepositoryDeferredRestoreResult {
|
|
233
|
+
readonly status: 'restored' | 'checkpoint-mismatch' | 'ineligible';
|
|
234
|
+
readonly effectLedger: PlaybookEffectLedger;
|
|
235
|
+
}
|
|
236
|
+
export interface RepositoryCapability {
|
|
237
|
+
runExclusive<T>(options: {
|
|
238
|
+
readonly signal?: AbortSignal;
|
|
239
|
+
readonly effectBoundary: EffectBoundarySeed;
|
|
240
|
+
readonly operation: (context: {
|
|
241
|
+
readonly baseline: PlaybookRepositoryObservation;
|
|
242
|
+
readonly identity: RepositoryIdentity;
|
|
243
|
+
}) => Promise<T>;
|
|
244
|
+
readonly completeEffectBoundary: (
|
|
245
|
+
completion: RepositoryExclusiveCompletion<T>,
|
|
246
|
+
) => RepositoryCompletionEvidence | Promise<RepositoryCompletionEvidence>;
|
|
247
|
+
}): Promise<RepositoryExclusiveResult<T>>;
|
|
248
|
+
runDeferred<T>(options: {
|
|
249
|
+
readonly mode: 'continue';
|
|
250
|
+
readonly signal?: AbortSignal;
|
|
251
|
+
readonly operationId: string;
|
|
252
|
+
readonly effectBoundary: EffectBoundarySeed;
|
|
253
|
+
readonly operation: (context: {
|
|
254
|
+
readonly baseline: PlaybookRepositoryObservation;
|
|
255
|
+
readonly identity: RepositoryIdentity;
|
|
256
|
+
readonly playerContinuation: JsonValue;
|
|
257
|
+
}) => Promise<T>;
|
|
258
|
+
readonly completeEffectBoundary: (
|
|
259
|
+
completion: RepositoryExclusiveCompletion<T>,
|
|
260
|
+
) => RepositoryCompletionEvidence | Promise<RepositoryCompletionEvidence>;
|
|
261
|
+
}): Promise<
|
|
262
|
+
RepositoryDeferredContinuationResult<T> | RepositoryDeferredCheckpointMismatch
|
|
263
|
+
>;
|
|
264
|
+
runDeferred(options: {
|
|
265
|
+
readonly mode: 'park' | 'restore';
|
|
266
|
+
readonly signal?: AbortSignal;
|
|
267
|
+
readonly operationId: string;
|
|
268
|
+
}): Promise<RepositoryDeferredParked | RepositoryDeferredRestoreResult>;
|
|
269
|
+
}
|
|
270
|
+
export interface HostCapabilities {
|
|
271
|
+
readonly repository: RepositoryCapability;
|
|
272
|
+
readonly effectLedger: PlaybookEffectLedgerCapability;
|
|
273
|
+
}
|
|
274
|
+
export interface WorktreeRepositoryCapability extends RepositoryCapability {
|
|
275
|
+
readonly identity: RepositoryIdentity;
|
|
276
|
+
observe(): Promise<PlaybookRepositoryObservation>;
|
|
277
|
+
}
|
|
278
|
+
export interface WorktreeHostCapabilities extends HostCapabilities {
|
|
279
|
+
readonly repository: WorktreeRepositoryCapability;
|
|
280
|
+
}
|
|
281
|
+
export interface WorktreeHostCapabilitiesOptions {
|
|
282
|
+
readonly cwd: string;
|
|
283
|
+
readonly playbookId: string;
|
|
284
|
+
readonly requiredRoleIds: readonly string[];
|
|
285
|
+
readonly concurrentRoleSets?: readonly (readonly string[])[];
|
|
286
|
+
readonly effectLedger?: PlaybookEffectLedger;
|
|
287
|
+
}
|
|
288
|
+
export declare function createWorktreeHostCapabilities(
|
|
289
|
+
options: WorktreeHostCapabilitiesOptions,
|
|
290
|
+
): Promise<WorktreeHostCapabilities>;
|
|
291
|
+
export declare function createFailClosedHostCapabilities(): HostCapabilities;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
|
|
3
|
+
|
|
4
|
+
// DR-046: the public `@sublang/playbook/host-capabilities` facade. Every
|
|
5
|
+
// member is the CLI host's own repository-effect implementation; this module
|
|
6
|
+
// only narrows the accepted arguments to the declared surface.
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
REPOSITORY_RECEIPT_CLASSIFICATIONS,
|
|
10
|
+
captureRepositoryReceipt as capture,
|
|
11
|
+
classifyRepositoryReceipt as classify,
|
|
12
|
+
createFailClosedHostCapabilities,
|
|
13
|
+
createWorktreeHostCapabilities,
|
|
14
|
+
observeGitRepository as observe,
|
|
15
|
+
} from './bin/repository-effects.js';
|
|
16
|
+
|
|
17
|
+
export {
|
|
18
|
+
REPOSITORY_RECEIPT_CLASSIFICATIONS,
|
|
19
|
+
createFailClosedHostCapabilities,
|
|
20
|
+
createWorktreeHostCapabilities,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export async function observeGitRepository(cwd) {
|
|
24
|
+
return observe(cwd);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function classifyRepositoryReceipt(baseline, after, options) {
|
|
28
|
+
return classify(baseline, after, receiptOptions(options));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function captureRepositoryReceipt(baseline, options) {
|
|
32
|
+
return capture(baseline, receiptOptions(options));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function receiptOptions(options) {
|
|
36
|
+
if (options === null || typeof options !== 'object' || Array.isArray(options)) {
|
|
37
|
+
throw new TypeError('repository receipt options must be an object');
|
|
38
|
+
}
|
|
39
|
+
return { allowedDispositions: options.allowedDispositions };
|
|
40
|
+
}
|
|
@@ -64,6 +64,10 @@ const ROLE_ID_PATTERN = /^[a-z][a-z0-9_-]*$/;
|
|
|
64
64
|
const HOST_CAPABILITIES_OPTION_KEY = 'hostCapabilities';
|
|
65
65
|
const UNRESOLVED_EFFECT_RECONCILIATION_ACTION_ID = 'reconcile:unresolved-effect';
|
|
66
66
|
const UNRESOLVED_EFFECT_ABANDONMENT_ACTION_ID = 'abandon:unresolved-effect';
|
|
67
|
+
// The fixed machine-syntax guard of a player's Boss-question suspension
|
|
68
|
+
// (slc/link.md §Boss-reply suspension): the accepted outcome that parks on
|
|
69
|
+
// Boss rather than sparing Boss a relay.
|
|
70
|
+
const BOSS_QUESTION_OUTCOME = 'needsBossReply';
|
|
67
71
|
const RESUMPTION_DUPLICATE_EFFECT_WARNING = 'Warning: resumption may duplicate external effects attempted after the retained boundary; verify the current world before continuing.';
|
|
68
72
|
function parseRegisteredCommand(prompt) {
|
|
69
73
|
const match = /^\/([A-Za-z][A-Za-z0-9_-]*)(?:\s+([\s\S]*))?$/.exec(prompt.trim());
|
|
@@ -2550,6 +2554,12 @@ export function createPlaybookCaptainShell(options, deps = {}) {
|
|
|
2550
2554
|
if (summary.acceptedOutcomeTraceKeys.has(traceKey))
|
|
2551
2555
|
return;
|
|
2552
2556
|
summary.acceptedOutcomeTraceKeys.add(traceKey);
|
|
2557
|
+
// CAPTAIN-19/20: a saved interruption is a player reply Boss did not
|
|
2558
|
+
// have to relay. A Boss-question suspension is the one accepted outcome
|
|
2559
|
+
// that parks on Boss instead, so it saves nothing and counts nothing —
|
|
2560
|
+
// a turn that only parked must not claim it saved an interruption.
|
|
2561
|
+
if (receipt.acceptedOutcome === BOSS_QUESTION_OUTCOME)
|
|
2562
|
+
return;
|
|
2553
2563
|
summary.counts.interruptions++;
|
|
2554
2564
|
if (frame.entry.summaryPolicy?.copyPasteGuardNames.includes(receipt.acceptedOutcome)) {
|
|
2555
2565
|
summary.counts.copyPastes++;
|
|
@@ -554,6 +554,10 @@ const HOST_CAPABILITIES_OPTION_KEY = 'hostCapabilities';
|
|
|
554
554
|
const UNRESOLVED_EFFECT_RECONCILIATION_ACTION_ID =
|
|
555
555
|
'reconcile:unresolved-effect';
|
|
556
556
|
const UNRESOLVED_EFFECT_ABANDONMENT_ACTION_ID = 'abandon:unresolved-effect';
|
|
557
|
+
// The fixed machine-syntax guard of a player's Boss-question suspension
|
|
558
|
+
// (slc/link.md §Boss-reply suspension): the accepted outcome that parks on
|
|
559
|
+
// Boss rather than sparing Boss a relay.
|
|
560
|
+
const BOSS_QUESTION_OUTCOME = 'needsBossReply';
|
|
557
561
|
const RESUMPTION_DUPLICATE_EFFECT_WARNING =
|
|
558
562
|
'Warning: resumption may duplicate external effects attempted after the retained boundary; verify the current world before continuing.';
|
|
559
563
|
|
|
@@ -4293,6 +4297,11 @@ export function createPlaybookCaptainShell(
|
|
|
4293
4297
|
const traceKey = `${frame.sessionId}:${trace.sequence}`;
|
|
4294
4298
|
if (summary.acceptedOutcomeTraceKeys.has(traceKey)) return;
|
|
4295
4299
|
summary.acceptedOutcomeTraceKeys.add(traceKey);
|
|
4300
|
+
// CAPTAIN-19/20: a saved interruption is a player reply Boss did not
|
|
4301
|
+
// have to relay. A Boss-question suspension is the one accepted outcome
|
|
4302
|
+
// that parks on Boss instead, so it saves nothing and counts nothing —
|
|
4303
|
+
// a turn that only parked must not claim it saved an interruption.
|
|
4304
|
+
if (receipt.acceptedOutcome === BOSS_QUESTION_OUTCOME) return;
|
|
4296
4305
|
summary.counts.interruptions++;
|
|
4297
4306
|
if (
|
|
4298
4307
|
frame.entry.summaryPolicy?.copyPasteGuardNames.includes(
|
|
@@ -19,7 +19,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
19
19
|
import PQueue from 'p-queue';
|
|
20
20
|
import { createActor, fromPromise } from 'xstate';
|
|
21
21
|
import { createAcceptedOutcomeConsumer, } from '../../../src/accepted-outcome.js';
|
|
22
|
-
import { assertJsonSafe, assertPlaybookEffectLedger, assertPlaybookRuntimeSnapshot, combineAbortSignals, createNestedPlaybookBridge, detachPersistedMachineSnapshot, normalizeError, normalizePlaybookSnapshot, PlaybookSemanticCandidateStructureError, reconcilePlaybookSemanticEvidence, snapshotJsonValue, snapshotPlaybookSession, validatePlayerResult, waitForPlaybookQuiescence, } from '../../../src/xstate-runtime.js';
|
|
22
|
+
import { assertJsonSafe, assertPlaybookEffectLedger, assertPlaybookRuntimeSnapshot, combineAbortSignals, createNestedPlaybookBridge, detachPersistedMachineSnapshot, normalizeError, normalizePlaybookSnapshot, PlaybookSemanticCandidateStructureError, reconcilePlaybookSemanticEvidence, renderGovernedOutcomeContract, snapshotJsonValue, snapshotPlaybookSession, validatePlayerResult, waitForPlaybookQuiescence, } from '../../../src/xstate-runtime.js';
|
|
23
23
|
import decideMachine from './decide.fsm.js';
|
|
24
24
|
function snapshotDecideRuntimeOptions(value) {
|
|
25
25
|
const captured = snapshotJsonValue(value, 'DECIDE runtime options');
|
|
@@ -429,15 +429,19 @@ function buildAdjudicatorPrompt(input, playerOutput, correction) {
|
|
|
429
429
|
lines.push('"""');
|
|
430
430
|
lines.push('');
|
|
431
431
|
lines.push('Guards (choose exactly one; the descriptions are authoritative and must be applied as written):');
|
|
432
|
+
// The shared engine's renderer (slc/link.md §Captain adjudication): each
|
|
433
|
+
// arm's meaning verbatim, with its `Output shall include` clause — authored
|
|
434
|
+
// for the complete actor output — replaced by the reply contract the
|
|
435
|
+
// outcome authority gives the judge. Rendering the clause verbatim asked
|
|
436
|
+
// the judge for presentation-owned `coderProposal` or `question`, which the
|
|
437
|
+
// reconciler rejects, spending the single correction on a self-inflicted
|
|
438
|
+
// defect.
|
|
432
439
|
for (const [guard, description] of Object.entries(input.result)) {
|
|
433
|
-
|
|
434
|
-
.filter(([, authority]) => authority === 'semantic')
|
|
435
|
-
.map(([field]) => field);
|
|
436
|
-
lines.push(`- ${guard}: semantic fields: ${semanticFields.length === 0 ? '(none)' : semanticFields.join(', ')}; ${description}`);
|
|
440
|
+
lines.push(...renderGovernedOutcomeContract(guard, description, outcomes[guard]));
|
|
437
441
|
}
|
|
438
442
|
lines.push('');
|
|
439
|
-
lines.push(
|
|
440
|
-
lines.push('
|
|
443
|
+
lines.push("Pick exactly one declared `guard` and reply with exactly that outcome's reply shape above: `guard` plus its semantic-owned fields and nothing else.");
|
|
444
|
+
lines.push('A field listed as runtime-supplied is owned by presentation, effect, or runtime evidence; the runtime fills it itself, and a reply that includes one is structurally invalid.');
|
|
441
445
|
if (correction !== undefined) {
|
|
442
446
|
lines.push('');
|
|
443
447
|
lines.push('Your first reply was structurally invalid:');
|
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
normalizePlaybookSnapshot,
|
|
39
39
|
PlaybookSemanticCandidateStructureError,
|
|
40
40
|
reconcilePlaybookSemanticEvidence,
|
|
41
|
+
renderGovernedOutcomeContract,
|
|
41
42
|
snapshotJsonValue,
|
|
42
43
|
snapshotPlaybookSession,
|
|
43
44
|
validatePlayerResult,
|
|
@@ -660,22 +661,24 @@ function buildAdjudicatorPrompt(
|
|
|
660
661
|
lines.push(
|
|
661
662
|
'Guards (choose exactly one; the descriptions are authoritative and must be applied as written):',
|
|
662
663
|
);
|
|
664
|
+
// The shared engine's renderer (slc/link.md §Captain adjudication): each
|
|
665
|
+
// arm's meaning verbatim, with its `Output shall include` clause — authored
|
|
666
|
+
// for the complete actor output — replaced by the reply contract the
|
|
667
|
+
// outcome authority gives the judge. Rendering the clause verbatim asked
|
|
668
|
+
// the judge for presentation-owned `coderProposal` or `question`, which the
|
|
669
|
+
// reconciler rejects, spending the single correction on a self-inflicted
|
|
670
|
+
// defect.
|
|
663
671
|
for (const [guard, description] of Object.entries(input.result)) {
|
|
664
|
-
const semanticFields = Object.entries(outcomes[guard]?.fields ?? {})
|
|
665
|
-
.filter(([, authority]) => authority === 'semantic')
|
|
666
|
-
.map(([field]) => field);
|
|
667
672
|
lines.push(
|
|
668
|
-
|
|
669
|
-
semanticFields.length === 0 ? '(none)' : semanticFields.join(', ')
|
|
670
|
-
}; ${description}`,
|
|
673
|
+
...renderGovernedOutcomeContract(guard, description, outcomes[guard]),
|
|
671
674
|
);
|
|
672
675
|
}
|
|
673
676
|
lines.push('');
|
|
674
677
|
lines.push(
|
|
675
|
-
|
|
678
|
+
"Pick exactly one declared `guard` and reply with exactly that outcome's reply shape above: `guard` plus its semantic-owned fields and nothing else.",
|
|
676
679
|
);
|
|
677
680
|
lines.push(
|
|
678
|
-
'
|
|
681
|
+
'A field listed as runtime-supplied is owned by presentation, effect, or runtime evidence; the runtime fills it itself, and a reply that includes one is structurally invalid.',
|
|
679
682
|
);
|
|
680
683
|
if (correction !== undefined) {
|
|
681
684
|
lines.push('');
|
package/slc/gears2fsm.md
CHANGED
|
@@ -540,6 +540,10 @@ The artifact shall not bake them into machine input, options, or context; model
|
|
|
540
540
|
Host-owned configuration such as an enabled-playbook catalog shall remain
|
|
541
541
|
immutable machine input/context for the session. Boss events and actor outputs
|
|
542
542
|
shall not carry, replace, append to, or otherwise overwrite that catalog.
|
|
543
|
+
A placeholder whose value Source assigns to the host — for example the
|
|
544
|
+
`<definition>` a phase host supplies to a compiled phase — is such host-owned
|
|
545
|
+
configuration: a required machine `input` field carried into typed context and
|
|
546
|
+
the acting actor's input, never a Boss-event or actor-output payload.
|
|
543
547
|
Every machine with a dynamic call shall receive its own registered or authored
|
|
544
548
|
playbook id as immutable machine input/context named `selfPlaybookId`, and its
|
|
545
549
|
dynamic-call guard shall reject that target. The leaf-level `stateId` name is
|
|
@@ -776,6 +780,22 @@ compile as `{ response: string }`, not `{ response?: string }`; reaching the
|
|
|
776
780
|
final state without a non-empty response shall be guarded out before the
|
|
777
781
|
machine output is constructed.
|
|
778
782
|
|
|
783
|
+
## Compiled execution
|
|
784
|
+
|
|
785
|
+
This section governs compiled execution of this phase; the rules above remain the transformation's normative content for both execution paths.
|
|
786
|
+
|
|
787
|
+
Where the phase host supplies `<definition>` as the exact bytes of the definition file the request names, when a transformation request names a `gears` Source (`.md`) and an `fsm` Target (`.ts`), Captain shall carry out the GEARS-to-FSM transformation as specified:
|
|
788
|
+
|
|
789
|
+
> Follow the definition relayed between the `--- DEFINITION ---` and `--- END DEFINITION ---` lines exactly, adding no rules of your own: read the named Source and write the named Target as the definition specifies.
|
|
790
|
+
> If the Source cannot be transformed under the definition, do not guess: leave the Target unwritten and report the concrete reason.
|
|
791
|
+
> --- DEFINITION ---
|
|
792
|
+
> \<definition\>
|
|
793
|
+
> --- END DEFINITION ---
|
|
794
|
+
|
|
795
|
+
Results:
|
|
796
|
+
- `compiled`: Captain wrote the named Target as the relayed definition specifies.
|
|
797
|
+
- `rejected`: Captain reported that the Source cannot be transformed under the relayed definition and left the Target unwritten.
|
|
798
|
+
|
|
779
799
|
## References
|
|
780
800
|
|
|
781
801
|
[1]: https://stately.ai/docs/xstate 'XState Official Documentation'
|
package/slc/link.md
CHANGED
|
@@ -1079,6 +1079,15 @@ runtime-owned payload field; `guard` shall name exactly one outcome declared
|
|
|
1079
1079
|
by both the live result map and `outcomeAuthority`; and every semantic-owned
|
|
1080
1080
|
field shall satisfy the result map's required-field type before any actor
|
|
1081
1081
|
output is delivered.
|
|
1082
|
+
The judge prompt shall render each governed outcome's description with its
|
|
1083
|
+
meaning verbatim and its `Output shall include` clause replaced by that reply
|
|
1084
|
+
contract — exactly `guard` plus the semantic-owned fields, each keeping its
|
|
1085
|
+
authored placeholder or guidance, with every presentation-, effect-, or
|
|
1086
|
+
runtime-owned field named as runtime-supplied to omit — so the judge is never asked for a
|
|
1087
|
+
field it does not own; the artifact's description text stays unchanged.
|
|
1088
|
+
The shared engine shall export that rendering as `renderGovernedOutcomeContract`
|
|
1089
|
+
on `@sublang/playbook/xstate-runtime`, and a bespoke linked runtime shall
|
|
1090
|
+
render its judge prompt through it rather than restate the contract.
|
|
1082
1091
|
The reconciler shall construct the complete actor output rather than accept a
|
|
1083
1092
|
cross-authority object from the judge: every presentation-owned payload field
|
|
1084
1093
|
shall receive the canonical `finalText.trim()` value; every effect-owned
|
|
@@ -1170,7 +1179,9 @@ Two default adjudication strategies, in selection order:
|
|
|
1170
1179
|
to decide only from the supplied actor output and declared outcomes, and
|
|
1171
1180
|
require exactly one JSON object with no prose. The judge prompt shall not
|
|
1172
1181
|
interpret the player's output, paraphrase it, or alter the FSM's `result`
|
|
1173
|
-
text — it carries the description verbatim
|
|
1182
|
+
text — it carries the description verbatim, except that a governed
|
|
1183
|
+
schema-3 outcome's `Output shall include` clause is rendered as the
|
|
1184
|
+
authority-derived reply contract of §Captain adjudication.
|
|
1174
1185
|
- **Marker-parse** (delegated-player alternative): a deterministic parser that
|
|
1175
1186
|
scans the player output for a terminal control line such as
|
|
1176
1187
|
`FSM-RESULT: { "guard": "...", ... }`. Useful when player adapters can
|
|
@@ -2451,6 +2462,22 @@ This spec is silent on the choice; the contract is the same in any location.
|
|
|
2451
2462
|
|
|
2452
2463
|
New behavior in any of these areas requires a separate slc spec.
|
|
2453
2464
|
|
|
2465
|
+
## Compiled execution
|
|
2466
|
+
|
|
2467
|
+
This section governs compiled execution of this phase; the rules above remain the transformation's normative content for both execution paths.
|
|
2468
|
+
|
|
2469
|
+
Where the phase host supplies `<definition>` as the exact bytes of the definition file the request names, when a transformation request names an `fsm` Source (`.ts`) and a `playbook` Target (`.ts`), Captain shall carry out the FSM-to-runtime linking as specified:
|
|
2470
|
+
|
|
2471
|
+
> Follow the definition relayed between the `--- DEFINITION ---` and `--- END DEFINITION ---` lines exactly, adding no rules of your own: read the named Source and write the named Target as the definition specifies.
|
|
2472
|
+
> If the Source cannot be transformed under the definition, do not guess: leave the Target unwritten and report the concrete reason.
|
|
2473
|
+
> --- DEFINITION ---
|
|
2474
|
+
> \<definition\>
|
|
2475
|
+
> --- END DEFINITION ---
|
|
2476
|
+
|
|
2477
|
+
Results:
|
|
2478
|
+
- `compiled`: Captain wrote the named Target as the relayed definition specifies.
|
|
2479
|
+
- `rejected`: Captain reported that the Source cannot be transformed under the relayed definition and left the Target unwritten.
|
|
2480
|
+
|
|
2454
2481
|
## References
|
|
2455
2482
|
|
|
2456
2483
|
[1]: text2gears.md "First phase: text → GEARS spec items."
|
package/slc/text2gears.md
CHANGED
|
@@ -321,7 +321,9 @@ exact English form regardless of Source language.
|
|
|
321
321
|
## Transformation-spec sources
|
|
322
322
|
|
|
323
323
|
A Source may itself be the normative specification of a transformation — e.g., a compiler phase definition, as when a meta pipeline compiles this file.
|
|
324
|
-
Such a Source declares no roles and prompts none;
|
|
324
|
+
Such a Source declares no roles and prompts none; Captain performs the specified transformation on request.
|
|
325
|
+
Where such a Source carries a `## Compiled execution` section, text2gears shall compile it from that section alone: the section is the Source's complete behavior — its acting item, prompt, and `Results:` contract — and the remaining definition text is relayed content, not behaviors to transcribe, so the composition below applies only to a Source without that section.
|
|
326
|
+
That section's blockquote is complete as authored: text2gears shall emit it verbatim and shall append no relay line to it — the `<definition>` placeholder is its only runtime value, and the undelivered-value rule above does not add `<boss-intent>` or any other placeholder to it.
|
|
325
327
|
Compose Captain-acting spec items for it: when a transformation request names the specification's source and target, Captain shall carry out the transformation as specified.
|
|
326
328
|
Prompts shall carry the specification's normative requirements as instructions to Captain — deduplicated, one point per line — without inventing roles, triggers, or requirements the specification does not state.
|
|
327
329
|
|
|
@@ -352,6 +354,22 @@ Partition items by every variable that determines prompt content — including a
|
|
|
352
354
|
Drop disjunctive branches incompatible with the rest of an item's condition or prompt.
|
|
353
355
|
Dead branches mislead readers and downstream phases.
|
|
354
356
|
|
|
357
|
+
## Compiled execution
|
|
358
|
+
|
|
359
|
+
This section governs compiled execution of this phase; the rules above remain the transformation's normative content for both execution paths.
|
|
360
|
+
|
|
361
|
+
Where the phase host supplies `<definition>` as the exact bytes of the definition file the request names, when a transformation request names a `text` Source (`.md`) and a `gears` Target (`.md`), Captain shall carry out the text-to-GEARS transformation as specified:
|
|
362
|
+
|
|
363
|
+
> Follow the definition relayed between the `--- DEFINITION ---` and `--- END DEFINITION ---` lines exactly, adding no rules of your own: read the named Source and write the named Target as the definition specifies.
|
|
364
|
+
> If the Source cannot be transformed under the definition, do not guess: leave the Target unwritten and report the concrete reason.
|
|
365
|
+
> --- DEFINITION ---
|
|
366
|
+
> \<definition\>
|
|
367
|
+
> --- END DEFINITION ---
|
|
368
|
+
|
|
369
|
+
Results:
|
|
370
|
+
- `compiled`: Captain wrote the named Target as the relayed definition specifies.
|
|
371
|
+
- `rejected`: Captain reported that the Source cannot be transformed under the relayed definition and left the Target unwritten.
|
|
372
|
+
|
|
355
373
|
## References
|
|
356
374
|
|
|
357
375
|
[1]: GEARS definition shipped by the installed `@sublang/spex` package: `@sublang/spex/scaffold/specs/meta.md` (English) and `@sublang/spex/scaffold/i18n/zh/specs/meta.md` (Chinese); canonical renditions [GEARS: AI-Ready Spec Syntax](https://sublang.ai/ref/gears-ai-ready-spec-syntax) (en) and [GEARS:面向 AI 的规约语法](https://sublang.ai/zh/ref/gears-ai-ready-spec-syntax) (zh)
|
|
@@ -412,6 +412,21 @@ export declare function defaultComposeCaptainPrompt(input: PlaybookCaptainInput,
|
|
|
412
412
|
export declare function defaultExtractRequiredFields(description: string): string[];
|
|
413
413
|
/** Default delegated-player adjudicator prompt. */
|
|
414
414
|
export declare function defaultBuildJudgePrompt(input: PlaybookPlayerInput, finalText: string): string;
|
|
415
|
+
/**
|
|
416
|
+
* Judge-facing rendering of one governed outcome (DR-040 §1). The artifact's
|
|
417
|
+
* description is not altered: its meaning is carried through verbatim, while
|
|
418
|
+
* its `Output shall include` clause — authored for the complete actor output
|
|
419
|
+
* — is replaced by the reply contract `outcomeAuthority` gives the judge:
|
|
420
|
+
* exactly `guard` plus the outcome's semantic-owned fields, each keeping the
|
|
421
|
+
* placeholder or guidance the clause authors for it, and every
|
|
422
|
+
* presentation-, effect-, or runtime-owned field named as runtime-supplied
|
|
423
|
+
* so the judge omits it. Rendering the clause verbatim asked the judge for
|
|
424
|
+
* `question`, `planningResult`, or `evaluatedRevision`, which the reconciler
|
|
425
|
+
* rejects as a structural error, spending the single correction on a
|
|
426
|
+
* self-inflicted defect. Exported so a bespoke linked runtime (DECIDE's
|
|
427
|
+
* parallel machinery) renders the identical contract instead of restating it.
|
|
428
|
+
*/
|
|
429
|
+
export declare function renderGovernedOutcomeContract(guard: string, description: string, outcome: XStateGovernedOutcomeSpec | undefined): string[];
|
|
415
430
|
export interface PlayerAdjudicationSpec {
|
|
416
431
|
buildJudgePrompt?: (input: PlaybookPlayerInput, finalText: string) => string;
|
|
417
432
|
extractRequiredFields?: (description: string) => string[];
|