@sublang/playbook 7.0.0 → 8.0.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/README.md +17 -4
- package/docs/cli.md +74 -29
- package/docs/configuration.md +209 -112
- package/docs/embedding.md +71 -25
- package/package.json +4 -3
- package/reference/sdlc/captain.playbook/captain.playbook.js +3 -3
- package/reference/sdlc/captain.playbook/captain.playbook.ts +3 -3
- package/reference/sdlc/code.md +1 -1
- package/reference/sdlc/code.playbook/bin/interactive-session.js +816 -0
- package/reference/sdlc/code.playbook/bin/launch-config.js +1078 -116
- package/reference/sdlc/code.playbook/bin/playbook.js +489 -34
- package/reference/sdlc/code.playbook/bin/run.js +283 -298
- package/reference/sdlc/code.playbook/bin/session-store.js +818 -26
- package/reference/sdlc/code.playbook/code.fsm.d.ts +5 -5
- package/reference/sdlc/code.playbook/code.fsm.introspect.js +2 -2
- package/reference/sdlc/code.playbook/code.fsm.introspect.ts +2 -2
- package/reference/sdlc/code.playbook/code.fsm.js +7 -11
- package/reference/sdlc/code.playbook/code.fsm.ts +9 -17
- package/reference/sdlc/code.playbook/code.gears.md +1 -1
- package/reference/sdlc/code.playbook/code.playbook.d.ts +2 -1
- package/reference/sdlc/code.playbook/code.playbook.js +12 -13
- package/reference/sdlc/code.playbook/code.playbook.ts +22 -15
- package/reference/sdlc/code.playbook/code.registry.d.ts +5 -13
- package/reference/sdlc/code.playbook/code.registry.js +3 -10
- package/reference/sdlc/code.playbook/code.registry.ts +7 -32
- package/reference/sdlc/code.playbook/playbook-captain.d.ts +39 -14
- package/reference/sdlc/code.playbook/playbook-captain.js +970 -289
- package/reference/sdlc/code.playbook/playbook-captain.ts +1403 -396
- package/reference/sdlc/code.playbook/playbook.config.template.yaml +41 -49
- package/reference/sdlc/decide.md +4 -4
- package/reference/sdlc/decide.playbook/decide.fsm.d.ts +9 -9
- package/reference/sdlc/decide.playbook/decide.fsm.js +21 -14
- package/reference/sdlc/decide.playbook/decide.fsm.ts +27 -23
- package/reference/sdlc/decide.playbook/decide.gears.md +3 -5
- package/reference/sdlc/decide.playbook/decide.playbook.d.ts +9 -13
- package/reference/sdlc/decide.playbook/decide.playbook.js +171 -134
- package/reference/sdlc/decide.playbook/decide.playbook.ts +238 -162
- package/reference/sdlc/decide.playbook/decide.registry.d.ts +5 -13
- package/reference/sdlc/decide.playbook/decide.registry.js +3 -9
- package/reference/sdlc/decide.playbook/decide.registry.ts +7 -31
- package/reference/sdlc/review.md +4 -5
- package/reference/sdlc/review.playbook/review.fsm.d.ts +9 -11
- package/reference/sdlc/review.playbook/review.fsm.js +30 -24
- package/reference/sdlc/review.playbook/review.fsm.ts +39 -35
- package/reference/sdlc/review.playbook/review.gears.md +6 -5
- package/reference/sdlc/review.playbook/review.playbook.d.ts +2 -1
- package/reference/sdlc/review.playbook/review.playbook.js +16 -21
- package/reference/sdlc/review.playbook/review.playbook.ts +26 -26
- package/reference/sdlc/review.playbook/review.registry.d.ts +5 -13
- package/reference/sdlc/review.playbook/review.registry.js +3 -16
- package/reference/sdlc/review.playbook/review.registry.ts +7 -38
- package/slc/gears2fsm.md +27 -23
- package/slc/link.md +113 -93
- package/slc/text2gears.md +19 -18
- package/src/runtime.d.ts +20 -16
- package/src/runtime.ts +19 -23
- package/src/xstate-playbook-runtime.d.ts +21 -17
- package/src/xstate-playbook-runtime.js +241 -149
- package/src/xstate-playbook-runtime.ts +331 -178
- package/src/xstate-runtime.js +63 -24
- package/src/xstate-runtime.ts +96 -28
package/src/xstate-runtime.js
CHANGED
|
@@ -268,6 +268,25 @@ function capturedSessionStore(descriptors) {
|
|
|
268
268
|
restore: method('restore'),
|
|
269
269
|
});
|
|
270
270
|
}
|
|
271
|
+
function capturedRoleBindings(descriptors) {
|
|
272
|
+
const captured = snapshotJsonValue(capturedDataValue(descriptors, 'roleBindings', 'playbook session roleBindings'), 'playbook session roleBindings');
|
|
273
|
+
if (!isRecord(captured)) {
|
|
274
|
+
throw new TypeError('playbook session roleBindings must be an object');
|
|
275
|
+
}
|
|
276
|
+
const bindings = {};
|
|
277
|
+
for (const [roleId, value] of Object.entries(captured)) {
|
|
278
|
+
requireNonEmptyString(roleId, 'playbook session roleBindings role id');
|
|
279
|
+
if (!isRecord(value)) {
|
|
280
|
+
throw new TypeError(`playbook session roleBindings.${roleId} must be an object`);
|
|
281
|
+
}
|
|
282
|
+
rejectUnknownKeys(value, ['playerId', 'promptIdentity'], `playbook session roleBindings.${roleId}`);
|
|
283
|
+
defineEnumerableDataProperty(bindings, roleId, Object.freeze({
|
|
284
|
+
playerId: requireNonEmptyString(value.playerId, `playbook session roleBindings.${roleId}.playerId`),
|
|
285
|
+
promptIdentity: requireNonEmptyString(value.promptIdentity, `playbook session roleBindings.${roleId}.promptIdentity`),
|
|
286
|
+
}));
|
|
287
|
+
}
|
|
288
|
+
return Object.freeze(bindings);
|
|
289
|
+
}
|
|
271
290
|
/** Validate session causality and detach its immutable identity from the host. */
|
|
272
291
|
export function snapshotPlaybookSession(session) {
|
|
273
292
|
if (!isRecord(session)) {
|
|
@@ -287,6 +306,7 @@ export function snapshotPlaybookSession(session) {
|
|
|
287
306
|
const hasParentSessionId = Object.prototype.hasOwnProperty.call(sessionDescriptors, 'parentSessionId');
|
|
288
307
|
const hasParentCallId = Object.prototype.hasOwnProperty.call(sessionDescriptors, 'parentCallId');
|
|
289
308
|
const hasPlayerSessions = Object.prototype.hasOwnProperty.call(sessionDescriptors, 'playerSessions');
|
|
309
|
+
const hasRoleBindings = Object.prototype.hasOwnProperty.call(sessionDescriptors, 'roleBindings');
|
|
290
310
|
let parentSessionId;
|
|
291
311
|
let parentCallId;
|
|
292
312
|
if (depth === 0) {
|
|
@@ -320,6 +340,9 @@ export function snapshotPlaybookSession(session) {
|
|
|
320
340
|
const playerSessions = hasPlayerSessions
|
|
321
341
|
? capturedSessionStore(sessionDescriptors)
|
|
322
342
|
: undefined;
|
|
343
|
+
const roleBindings = hasRoleBindings
|
|
344
|
+
? capturedRoleBindings(sessionDescriptors)
|
|
345
|
+
: undefined;
|
|
323
346
|
return Object.freeze({
|
|
324
347
|
sessionId,
|
|
325
348
|
playbookId,
|
|
@@ -327,6 +350,7 @@ export function snapshotPlaybookSession(session) {
|
|
|
327
350
|
...(parentSessionId === undefined ? {} : { parentSessionId }),
|
|
328
351
|
...(parentCallId === undefined ? {} : { parentCallId }),
|
|
329
352
|
depth,
|
|
353
|
+
...(roleBindings === undefined ? {} : { roleBindings }),
|
|
330
354
|
...(playerSessions === undefined ? {} : { playerSessions }),
|
|
331
355
|
ports,
|
|
332
356
|
});
|
|
@@ -548,9 +572,10 @@ function snapshotSuspendedCall(value, path = 'runtime snapshot suspendedCall') {
|
|
|
548
572
|
}
|
|
549
573
|
return Object.freeze(call);
|
|
550
574
|
}
|
|
551
|
-
// DR-014 §1 / DR-031 §5: validate and detach a host-supplied
|
|
552
|
-
// snapshot before restore touches any state. A suspended
|
|
553
|
-
// rejected unless the restore path explicitly promises to seed and
|
|
575
|
+
// DR-014 §1 / DR-031 §5 / DR-032: validate and detach a host-supplied
|
|
576
|
+
// schema-3 runtime snapshot before restore touches any state. A suspended
|
|
577
|
+
// call is rejected unless the restore path explicitly promises to seed and
|
|
578
|
+
// claim it; older schemas are rejected rather than guessing role identity.
|
|
554
579
|
export function assertPlaybookRuntimeSnapshot(value, expectedPlaybookId, options = {}) {
|
|
555
580
|
const snapshot = snapshotJsonValue(value, 'runtime snapshot');
|
|
556
581
|
if (!isRecord(snapshot)) {
|
|
@@ -566,25 +591,21 @@ export function assertPlaybookRuntimeSnapshot(value, expectedPlaybookId, options
|
|
|
566
591
|
throw new TypeError('runtime snapshot validation options.allowSuspendedCall must be boolean');
|
|
567
592
|
}
|
|
568
593
|
const allowSuspendedCall = capturedOptions.allowSuspendedCall ?? false;
|
|
569
|
-
if (snapshot.schemaVersion !==
|
|
570
|
-
throw new TypeError(`runtime snapshot schemaVersion ${String(snapshot.schemaVersion)} is not supported (expected
|
|
594
|
+
if (snapshot.schemaVersion !== 3) {
|
|
595
|
+
throw new TypeError(`runtime snapshot schemaVersion ${String(snapshot.schemaVersion)} is not supported (expected 3)`);
|
|
571
596
|
}
|
|
572
|
-
const schemaVersion = snapshot.schemaVersion;
|
|
573
597
|
rejectUnknownKeys(snapshot, [
|
|
574
598
|
'schemaVersion',
|
|
575
599
|
'playbookId',
|
|
576
600
|
'machine',
|
|
577
|
-
'
|
|
601
|
+
'roleResumeTokens',
|
|
578
602
|
'sequences',
|
|
579
603
|
'state',
|
|
580
604
|
'pendingBossQuestions',
|
|
581
605
|
'suspendedCall',
|
|
582
606
|
], 'runtime snapshot');
|
|
583
|
-
if (schemaVersion === 1 && own(snapshot, 'suspendedCall')) {
|
|
584
|
-
throw new TypeError('runtime snapshot schemaVersion 1 must not carry suspendedCall');
|
|
585
|
-
}
|
|
586
607
|
let suspendedCall;
|
|
587
|
-
if (
|
|
608
|
+
if (own(snapshot, 'suspendedCall')) {
|
|
588
609
|
suspendedCall = snapshotSuspendedCall(snapshot.suspendedCall);
|
|
589
610
|
if (!allowSuspendedCall) {
|
|
590
611
|
throw new TypeError('runtime snapshot suspendedCall requires a restore path that explicitly allows it');
|
|
@@ -598,12 +619,12 @@ export function assertPlaybookRuntimeSnapshot(value, expectedPlaybookId, options
|
|
|
598
619
|
throw new TypeError('runtime snapshot machine must be an object');
|
|
599
620
|
}
|
|
600
621
|
const machine = snapshot.machine;
|
|
601
|
-
if (!isRecord(snapshot.
|
|
602
|
-
throw new TypeError('runtime snapshot
|
|
622
|
+
if (!isRecord(snapshot.roleResumeTokens)) {
|
|
623
|
+
throw new TypeError('runtime snapshot roleResumeTokens must be an object');
|
|
603
624
|
}
|
|
604
|
-
const
|
|
605
|
-
for (const [
|
|
606
|
-
defineEnumerableDataProperty(
|
|
625
|
+
const roleResumeTokens = {};
|
|
626
|
+
for (const [roleId, token] of Object.entries(snapshot.roleResumeTokens)) {
|
|
627
|
+
defineEnumerableDataProperty(roleResumeTokens, requireNonEmptyString(roleId, 'runtime snapshot roleResumeTokens role id'), requireNonEmptyString(token, `runtime snapshot roleResumeTokens.${roleId}`));
|
|
607
628
|
}
|
|
608
629
|
if (!isRecord(snapshot.sequences)) {
|
|
609
630
|
throw new TypeError('runtime snapshot sequences must be an object');
|
|
@@ -627,7 +648,7 @@ export function assertPlaybookRuntimeSnapshot(value, expectedPlaybookId, options
|
|
|
627
648
|
validateState(snapshot.state, 'runtime snapshot state');
|
|
628
649
|
const state = snapshot.state;
|
|
629
650
|
if (state.tags.includes(SUSPENDED_TAG) && suspendedCall === undefined) {
|
|
630
|
-
throw new TypeError(`runtime snapshot state tagged ${SUSPENDED_TAG} requires
|
|
651
|
+
throw new TypeError(`runtime snapshot state tagged ${SUSPENDED_TAG} requires suspendedCall`);
|
|
631
652
|
}
|
|
632
653
|
if (suspendedCall) {
|
|
633
654
|
if (sequences.playbookCall === 0) {
|
|
@@ -654,10 +675,28 @@ export function assertPlaybookRuntimeSnapshot(value, expectedPlaybookId, options
|
|
|
654
675
|
const path = `runtime snapshot pendingBossQuestions[${index}]`;
|
|
655
676
|
if (!isRecord(entry))
|
|
656
677
|
throw new TypeError(`${path} must be an object`);
|
|
657
|
-
rejectUnknownKeys(entry, ['questionId', '
|
|
678
|
+
rejectUnknownKeys(entry, ['questionId', 'asker', 'question', 'sourceItem'], path);
|
|
679
|
+
if (!isRecord(entry.asker)) {
|
|
680
|
+
throw new TypeError(`${path}.asker must be an object`);
|
|
681
|
+
}
|
|
682
|
+
let asker;
|
|
683
|
+
if (entry.asker.kind === 'captain') {
|
|
684
|
+
rejectUnknownKeys(entry.asker, ['kind'], `${path}.asker`);
|
|
685
|
+
asker = Object.freeze({ kind: 'captain' });
|
|
686
|
+
}
|
|
687
|
+
else if (entry.asker.kind === 'role') {
|
|
688
|
+
rejectUnknownKeys(entry.asker, ['kind', 'roleId'], `${path}.asker`);
|
|
689
|
+
asker = Object.freeze({
|
|
690
|
+
kind: 'role',
|
|
691
|
+
roleId: requireNonEmptyString(entry.asker.roleId, `${path}.asker.roleId`),
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
else {
|
|
695
|
+
throw new TypeError(`${path}.asker.kind must be "captain" or "role"`);
|
|
696
|
+
}
|
|
658
697
|
const question = {
|
|
659
698
|
questionId: requireNonEmptyString(entry.questionId, `${path}.questionId`),
|
|
660
|
-
|
|
699
|
+
asker,
|
|
661
700
|
question: requireNonEmptyString(entry.question, `${path}.question`),
|
|
662
701
|
...(entry.sourceItem === undefined
|
|
663
702
|
? {}
|
|
@@ -670,16 +709,13 @@ export function assertPlaybookRuntimeSnapshot(value, expectedPlaybookId, options
|
|
|
670
709
|
const fields = {
|
|
671
710
|
playbookId,
|
|
672
711
|
machine,
|
|
673
|
-
|
|
712
|
+
roleResumeTokens: Object.freeze(roleResumeTokens),
|
|
674
713
|
sequences: Object.freeze(sequences),
|
|
675
714
|
state,
|
|
676
715
|
pendingBossQuestions: Object.freeze(pendingBossQuestions),
|
|
677
716
|
};
|
|
678
|
-
if (schemaVersion === 1) {
|
|
679
|
-
return Object.freeze({ schemaVersion: 1, ...fields });
|
|
680
|
-
}
|
|
681
717
|
return Object.freeze({
|
|
682
|
-
schemaVersion:
|
|
718
|
+
schemaVersion: 3,
|
|
683
719
|
...fields,
|
|
684
720
|
...(suspendedCall === undefined ? {} : { suspendedCall }),
|
|
685
721
|
});
|
|
@@ -773,6 +809,9 @@ export function validatePlayerResult(value, path = 'player result') {
|
|
|
773
809
|
rejectUnknownKeys(result, ['status', 'resumeToken', 'finalText', 'error'], path);
|
|
774
810
|
validateRunStatus(result.status, `${path}.status`);
|
|
775
811
|
validateOptionalString(result, 'resumeToken', path);
|
|
812
|
+
if (result.resumeToken !== undefined) {
|
|
813
|
+
requireNonEmptyString(result.resumeToken, `${path}.resumeToken`);
|
|
814
|
+
}
|
|
776
815
|
validateOptionalString(result, 'finalText', path);
|
|
777
816
|
validateOptionalString(result, 'error', path);
|
|
778
817
|
return result;
|
package/src/xstate-runtime.ts
CHANGED
|
@@ -386,6 +386,50 @@ function capturedSessionStore(
|
|
|
386
386
|
});
|
|
387
387
|
}
|
|
388
388
|
|
|
389
|
+
function capturedRoleBindings(
|
|
390
|
+
descriptors: PropertyDescriptorMap,
|
|
391
|
+
): NonNullable<PlaybookSession['roleBindings']> {
|
|
392
|
+
const captured = snapshotJsonValue(
|
|
393
|
+
capturedDataValue(
|
|
394
|
+
descriptors,
|
|
395
|
+
'roleBindings',
|
|
396
|
+
'playbook session roleBindings',
|
|
397
|
+
),
|
|
398
|
+
'playbook session roleBindings',
|
|
399
|
+
);
|
|
400
|
+
if (!isRecord(captured)) {
|
|
401
|
+
throw new TypeError('playbook session roleBindings must be an object');
|
|
402
|
+
}
|
|
403
|
+
const bindings: Record<
|
|
404
|
+
string,
|
|
405
|
+
{ readonly playerId: string; readonly promptIdentity: string }
|
|
406
|
+
> = {};
|
|
407
|
+
for (const [roleId, value] of Object.entries(captured)) {
|
|
408
|
+
requireNonEmptyString(roleId, 'playbook session roleBindings role id');
|
|
409
|
+
if (!isRecord(value)) {
|
|
410
|
+
throw new TypeError(
|
|
411
|
+
`playbook session roleBindings.${roleId} must be an object`,
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
rejectUnknownKeys(
|
|
415
|
+
value,
|
|
416
|
+
['playerId', 'promptIdentity'],
|
|
417
|
+
`playbook session roleBindings.${roleId}`,
|
|
418
|
+
);
|
|
419
|
+
defineEnumerableDataProperty(bindings, roleId, Object.freeze({
|
|
420
|
+
playerId: requireNonEmptyString(
|
|
421
|
+
value.playerId,
|
|
422
|
+
`playbook session roleBindings.${roleId}.playerId`,
|
|
423
|
+
),
|
|
424
|
+
promptIdentity: requireNonEmptyString(
|
|
425
|
+
value.promptIdentity,
|
|
426
|
+
`playbook session roleBindings.${roleId}.promptIdentity`,
|
|
427
|
+
),
|
|
428
|
+
}));
|
|
429
|
+
}
|
|
430
|
+
return Object.freeze(bindings);
|
|
431
|
+
}
|
|
432
|
+
|
|
389
433
|
/** Validate session causality and detach its immutable identity from the host. */
|
|
390
434
|
export function snapshotPlaybookSession(
|
|
391
435
|
session: PlaybookSession,
|
|
@@ -453,6 +497,10 @@ export function snapshotPlaybookSession(
|
|
|
453
497
|
sessionDescriptors,
|
|
454
498
|
'playerSessions',
|
|
455
499
|
);
|
|
500
|
+
const hasRoleBindings = Object.prototype.hasOwnProperty.call(
|
|
501
|
+
sessionDescriptors,
|
|
502
|
+
'roleBindings',
|
|
503
|
+
);
|
|
456
504
|
let parentSessionId: string | undefined;
|
|
457
505
|
let parentCallId: string | undefined;
|
|
458
506
|
if (depth === 0) {
|
|
@@ -501,6 +549,9 @@ export function snapshotPlaybookSession(
|
|
|
501
549
|
const playerSessions = hasPlayerSessions
|
|
502
550
|
? capturedSessionStore(sessionDescriptors)
|
|
503
551
|
: undefined;
|
|
552
|
+
const roleBindings = hasRoleBindings
|
|
553
|
+
? capturedRoleBindings(sessionDescriptors)
|
|
554
|
+
: undefined;
|
|
504
555
|
return Object.freeze({
|
|
505
556
|
sessionId,
|
|
506
557
|
playbookId,
|
|
@@ -508,6 +559,7 @@ export function snapshotPlaybookSession(
|
|
|
508
559
|
...(parentSessionId === undefined ? {} : { parentSessionId }),
|
|
509
560
|
...(parentCallId === undefined ? {} : { parentCallId }),
|
|
510
561
|
depth,
|
|
562
|
+
...(roleBindings === undefined ? {} : { roleBindings }),
|
|
511
563
|
...(playerSessions === undefined ? {} : { playerSessions }),
|
|
512
564
|
ports,
|
|
513
565
|
});
|
|
@@ -805,9 +857,10 @@ function snapshotSuspendedCall(
|
|
|
805
857
|
return Object.freeze(call);
|
|
806
858
|
}
|
|
807
859
|
|
|
808
|
-
// DR-014 §1 / DR-031 §5: validate and detach a host-supplied
|
|
809
|
-
// snapshot before restore touches any state. A suspended
|
|
810
|
-
// rejected unless the restore path explicitly promises to seed and
|
|
860
|
+
// DR-014 §1 / DR-031 §5 / DR-032: validate and detach a host-supplied
|
|
861
|
+
// schema-3 runtime snapshot before restore touches any state. A suspended
|
|
862
|
+
// call is rejected unless the restore path explicitly promises to seed and
|
|
863
|
+
// claim it; older schemas are rejected rather than guessing role identity.
|
|
811
864
|
export function assertPlaybookRuntimeSnapshot(
|
|
812
865
|
value: unknown,
|
|
813
866
|
expectedPlaybookId: string,
|
|
@@ -838,19 +891,18 @@ export function assertPlaybookRuntimeSnapshot(
|
|
|
838
891
|
);
|
|
839
892
|
}
|
|
840
893
|
const allowSuspendedCall = capturedOptions.allowSuspendedCall ?? false;
|
|
841
|
-
if (snapshot.schemaVersion !==
|
|
894
|
+
if (snapshot.schemaVersion !== 3) {
|
|
842
895
|
throw new TypeError(
|
|
843
|
-
`runtime snapshot schemaVersion ${String(snapshot.schemaVersion)} is not supported (expected
|
|
896
|
+
`runtime snapshot schemaVersion ${String(snapshot.schemaVersion)} is not supported (expected 3)`,
|
|
844
897
|
);
|
|
845
898
|
}
|
|
846
|
-
const schemaVersion = snapshot.schemaVersion;
|
|
847
899
|
rejectUnknownKeys(
|
|
848
900
|
snapshot,
|
|
849
901
|
[
|
|
850
902
|
'schemaVersion',
|
|
851
903
|
'playbookId',
|
|
852
904
|
'machine',
|
|
853
|
-
'
|
|
905
|
+
'roleResumeTokens',
|
|
854
906
|
'sequences',
|
|
855
907
|
'state',
|
|
856
908
|
'pendingBossQuestions',
|
|
@@ -858,13 +910,8 @@ export function assertPlaybookRuntimeSnapshot(
|
|
|
858
910
|
],
|
|
859
911
|
'runtime snapshot',
|
|
860
912
|
);
|
|
861
|
-
if (schemaVersion === 1 && own(snapshot, 'suspendedCall')) {
|
|
862
|
-
throw new TypeError(
|
|
863
|
-
'runtime snapshot schemaVersion 1 must not carry suspendedCall',
|
|
864
|
-
);
|
|
865
|
-
}
|
|
866
913
|
let suspendedCall: PlaybookSuspendedCall | undefined;
|
|
867
|
-
if (
|
|
914
|
+
if (own(snapshot, 'suspendedCall')) {
|
|
868
915
|
suspendedCall = snapshotSuspendedCall(snapshot.suspendedCall);
|
|
869
916
|
if (!allowSuspendedCall) {
|
|
870
917
|
throw new TypeError(
|
|
@@ -885,19 +932,19 @@ export function assertPlaybookRuntimeSnapshot(
|
|
|
885
932
|
throw new TypeError('runtime snapshot machine must be an object');
|
|
886
933
|
}
|
|
887
934
|
const machine = snapshot.machine;
|
|
888
|
-
if (!isRecord(snapshot.
|
|
935
|
+
if (!isRecord(snapshot.roleResumeTokens)) {
|
|
889
936
|
throw new TypeError(
|
|
890
|
-
'runtime snapshot
|
|
937
|
+
'runtime snapshot roleResumeTokens must be an object',
|
|
891
938
|
);
|
|
892
939
|
}
|
|
893
|
-
const
|
|
894
|
-
for (const [
|
|
940
|
+
const roleResumeTokens: Record<string, string> = {};
|
|
941
|
+
for (const [roleId, token] of Object.entries(snapshot.roleResumeTokens)) {
|
|
895
942
|
defineEnumerableDataProperty(
|
|
896
|
-
|
|
897
|
-
|
|
943
|
+
roleResumeTokens,
|
|
944
|
+
requireNonEmptyString(roleId, 'runtime snapshot roleResumeTokens role id'),
|
|
898
945
|
requireNonEmptyString(
|
|
899
946
|
token,
|
|
900
|
-
`runtime snapshot
|
|
947
|
+
`runtime snapshot roleResumeTokens.${roleId}`,
|
|
901
948
|
),
|
|
902
949
|
);
|
|
903
950
|
}
|
|
@@ -932,7 +979,7 @@ export function assertPlaybookRuntimeSnapshot(
|
|
|
932
979
|
const state = snapshot.state as unknown as PlaybookState;
|
|
933
980
|
if (state.tags.includes(SUSPENDED_TAG) && suspendedCall === undefined) {
|
|
934
981
|
throw new TypeError(
|
|
935
|
-
`runtime snapshot state tagged ${SUSPENDED_TAG} requires
|
|
982
|
+
`runtime snapshot state tagged ${SUSPENDED_TAG} requires suspendedCall`,
|
|
936
983
|
);
|
|
937
984
|
}
|
|
938
985
|
if (suspendedCall) {
|
|
@@ -976,15 +1023,36 @@ export function assertPlaybookRuntimeSnapshot(
|
|
|
976
1023
|
if (!isRecord(entry)) throw new TypeError(`${path} must be an object`);
|
|
977
1024
|
rejectUnknownKeys(
|
|
978
1025
|
entry,
|
|
979
|
-
['questionId', '
|
|
1026
|
+
['questionId', 'asker', 'question', 'sourceItem'],
|
|
980
1027
|
path,
|
|
981
1028
|
);
|
|
1029
|
+
if (!isRecord(entry.asker)) {
|
|
1030
|
+
throw new TypeError(`${path}.asker must be an object`);
|
|
1031
|
+
}
|
|
1032
|
+
let asker: PlaybookPendingBossQuestion['asker'];
|
|
1033
|
+
if (entry.asker.kind === 'captain') {
|
|
1034
|
+
rejectUnknownKeys(entry.asker, ['kind'], `${path}.asker`);
|
|
1035
|
+
asker = Object.freeze({ kind: 'captain' });
|
|
1036
|
+
} else if (entry.asker.kind === 'role') {
|
|
1037
|
+
rejectUnknownKeys(entry.asker, ['kind', 'roleId'], `${path}.asker`);
|
|
1038
|
+
asker = Object.freeze({
|
|
1039
|
+
kind: 'role',
|
|
1040
|
+
roleId: requireNonEmptyString(
|
|
1041
|
+
entry.asker.roleId,
|
|
1042
|
+
`${path}.asker.roleId`,
|
|
1043
|
+
),
|
|
1044
|
+
});
|
|
1045
|
+
} else {
|
|
1046
|
+
throw new TypeError(
|
|
1047
|
+
`${path}.asker.kind must be "captain" or "role"`,
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
982
1050
|
const question: PlaybookPendingBossQuestion = {
|
|
983
1051
|
questionId: requireNonEmptyString(
|
|
984
1052
|
entry.questionId,
|
|
985
1053
|
`${path}.questionId`,
|
|
986
1054
|
),
|
|
987
|
-
|
|
1055
|
+
asker,
|
|
988
1056
|
question: requireNonEmptyString(entry.question, `${path}.question`),
|
|
989
1057
|
...(entry.sourceItem === undefined
|
|
990
1058
|
? {}
|
|
@@ -1001,16 +1069,13 @@ export function assertPlaybookRuntimeSnapshot(
|
|
|
1001
1069
|
const fields = {
|
|
1002
1070
|
playbookId,
|
|
1003
1071
|
machine,
|
|
1004
|
-
|
|
1072
|
+
roleResumeTokens: Object.freeze(roleResumeTokens),
|
|
1005
1073
|
sequences: Object.freeze(sequences),
|
|
1006
1074
|
state,
|
|
1007
1075
|
pendingBossQuestions: Object.freeze(pendingBossQuestions),
|
|
1008
1076
|
};
|
|
1009
|
-
if (schemaVersion === 1) {
|
|
1010
|
-
return Object.freeze({ schemaVersion: 1, ...fields });
|
|
1011
|
-
}
|
|
1012
1077
|
return Object.freeze({
|
|
1013
|
-
schemaVersion:
|
|
1078
|
+
schemaVersion: 3,
|
|
1014
1079
|
...fields,
|
|
1015
1080
|
...(suspendedCall === undefined ? {} : { suspendedCall }),
|
|
1016
1081
|
});
|
|
@@ -1228,6 +1293,9 @@ export function validatePlayerResult(
|
|
|
1228
1293
|
);
|
|
1229
1294
|
validateRunStatus(result.status, `${path}.status`);
|
|
1230
1295
|
validateOptionalString(result, 'resumeToken', path);
|
|
1296
|
+
if (result.resumeToken !== undefined) {
|
|
1297
|
+
requireNonEmptyString(result.resumeToken, `${path}.resumeToken`);
|
|
1298
|
+
}
|
|
1231
1299
|
validateOptionalString(result, 'finalText', path);
|
|
1232
1300
|
validateOptionalString(result, 'error', path);
|
|
1233
1301
|
return result as unknown as PlayerResult;
|