stitchkit 0.59.3 → 0.59.4
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 +7 -1
- package/dist/application/health.d.ts +12 -0
- package/dist/application/health.d.ts.map +1 -1
- package/dist/application/opentelemetry.d.ts +25 -0
- package/dist/application/opentelemetry.d.ts.map +1 -0
- package/dist/application-opentelemetry.d.ts +2 -0
- package/dist/application-opentelemetry.d.ts.map +1 -0
- package/dist/application-opentelemetry.js +248 -0
- package/dist/application.d.ts +1 -1
- package/dist/application.d.ts.map +1 -1
- package/dist/application.js +58 -1240
- package/dist/index-2nzpcfbp.js +560 -0
- package/dist/index-5c8n2123.js +617 -0
- package/dist/index-mgf6cdkm.js +66 -0
- package/dist/node.js +8 -8
- package/dist/server/index.js +8 -8
- package/dist/testing/managed-resource-conformance-contract.d.ts +114 -0
- package/dist/testing/managed-resource-conformance-contract.d.ts.map +1 -0
- package/dist/testing/managed-resource-conformance-driver.d.ts +20 -0
- package/dist/testing/managed-resource-conformance-driver.d.ts.map +1 -0
- package/dist/testing/managed-resource-conformance-scenarios.d.ts +33 -0
- package/dist/testing/managed-resource-conformance-scenarios.d.ts.map +1 -0
- package/dist/testing/managed-resource-conformance.d.ts +5 -0
- package/dist/testing/managed-resource-conformance.d.ts.map +1 -0
- package/dist/testing.d.ts +1 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +658 -70
- package/llms-full.txt +290 -1
- package/llms.txt +1 -0
- package/package.json +11 -2
- package/dist/{index-0tszzfxp.js → index-s8w2y7kr.js} +3 -3
package/dist/testing.js
CHANGED
|
@@ -2,11 +2,16 @@ import {
|
|
|
2
2
|
AgentMessageSchema,
|
|
3
3
|
AgentRunSchema
|
|
4
4
|
} from "./index-vtjgx3vv.js";
|
|
5
|
+
import {
|
|
6
|
+
createApplication
|
|
7
|
+
} from "./index-5c8n2123.js";
|
|
8
|
+
import"./index-mgf6cdkm.js";
|
|
5
9
|
import {
|
|
6
10
|
RealtimeRequestDisconnectedError,
|
|
7
11
|
RealtimeRequestInvalidAcknowledgementError,
|
|
8
12
|
RealtimeRequestTimeoutError
|
|
9
13
|
} from "./index-557by2db.js";
|
|
14
|
+
import"./index-dk6e56g0.js";
|
|
10
15
|
import {
|
|
11
16
|
mcpProjectionCandidate,
|
|
12
17
|
prepareProjectedMcpTools,
|
|
@@ -378,73 +383,649 @@ async function runAgentStoreConformance(config) {
|
|
|
378
383
|
throw new Error("Abandon recovery did not atomically terminalize its assistant record");
|
|
379
384
|
}
|
|
380
385
|
}
|
|
386
|
+
// src/testing/managed-resource-conformance-contract.ts
|
|
387
|
+
import { z } from "zod";
|
|
388
|
+
var ManagedResourceConformanceScenarioIdSchema = z.enum([
|
|
389
|
+
"clean-shutdown",
|
|
390
|
+
"partial-start-rollback",
|
|
391
|
+
"readiness-rejection",
|
|
392
|
+
"completion-before-ready",
|
|
393
|
+
"required-late-completion",
|
|
394
|
+
"optional-late-completion",
|
|
395
|
+
"activation-rejection",
|
|
396
|
+
"shutdown-during-startup",
|
|
397
|
+
"force-after-stalled-close"
|
|
398
|
+
]);
|
|
399
|
+
var ManagedResourceConformanceScenarioSchema = z.discriminatedUnion("id", [
|
|
400
|
+
z.object({ id: z.literal("clean-shutdown"), required: z.literal(true) }).readonly(),
|
|
401
|
+
z.object({ id: z.literal("partial-start-rollback"), required: z.literal(true) }).readonly(),
|
|
402
|
+
z.object({ id: z.literal("readiness-rejection"), required: z.literal(true) }).readonly(),
|
|
403
|
+
z.object({ id: z.literal("completion-before-ready"), required: z.literal(true) }).readonly(),
|
|
404
|
+
z.object({ id: z.literal("required-late-completion"), required: z.literal(true) }).readonly(),
|
|
405
|
+
z.object({ id: z.literal("optional-late-completion"), required: z.literal(false) }).readonly(),
|
|
406
|
+
z.object({ id: z.literal("activation-rejection"), required: z.literal(true) }).readonly(),
|
|
407
|
+
z.object({ id: z.literal("shutdown-during-startup"), required: z.literal(true) }).readonly(),
|
|
408
|
+
z.object({ id: z.literal("force-after-stalled-close"), required: z.literal(true) }).readonly()
|
|
409
|
+
]);
|
|
410
|
+
var ManagedResourceConformancePhaseSchema = z.enum([
|
|
411
|
+
"start",
|
|
412
|
+
"readiness",
|
|
413
|
+
"completion",
|
|
414
|
+
"activate",
|
|
415
|
+
"stop-admission",
|
|
416
|
+
"drain",
|
|
417
|
+
"close",
|
|
418
|
+
"force",
|
|
419
|
+
"dispose"
|
|
420
|
+
]);
|
|
421
|
+
var ManagedResourceConformanceTraceOutcomeSchema = z.enum([
|
|
422
|
+
"enter",
|
|
423
|
+
"resolve",
|
|
424
|
+
"reject"
|
|
425
|
+
]);
|
|
426
|
+
var ManagedResourceConformanceTraceEntrySchema = z.object({
|
|
427
|
+
sequence: z.number().int().nonnegative(),
|
|
428
|
+
phase: ManagedResourceConformancePhaseSchema,
|
|
429
|
+
outcome: ManagedResourceConformanceTraceOutcomeSchema
|
|
430
|
+
}).strict().readonly();
|
|
431
|
+
|
|
432
|
+
class ManagedResourceConformanceError extends Error {
|
|
433
|
+
scenarioId;
|
|
434
|
+
expected;
|
|
435
|
+
trace;
|
|
436
|
+
code = "MANAGED_RESOURCE_CONFORMANCE_FAILED";
|
|
437
|
+
constructor(scenarioId, expected, trace, detail) {
|
|
438
|
+
const observed = trace.length === 0 ? "(empty)" : trace.map((entry) => `${entry.phase}:${entry.outcome}`).join(" -> ");
|
|
439
|
+
super(`[stitchkit] managed resource conformance "${scenarioId}" failed: ${detail}; ` + `expected ${expected.join(" -> ")}; observed ${observed}`);
|
|
440
|
+
this.scenarioId = scenarioId;
|
|
441
|
+
this.expected = expected;
|
|
442
|
+
this.trace = trace;
|
|
443
|
+
this.name = "ManagedResourceConformanceError";
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
// src/testing/managed-resource-conformance-driver.ts
|
|
448
|
+
function controlledPromise() {
|
|
449
|
+
const deferred = Promise.withResolvers();
|
|
450
|
+
let settled = false;
|
|
451
|
+
deferred.promise.catch(() => {
|
|
452
|
+
return;
|
|
453
|
+
});
|
|
454
|
+
return {
|
|
455
|
+
promise: deferred.promise,
|
|
456
|
+
resolve() {
|
|
457
|
+
if (settled)
|
|
458
|
+
return false;
|
|
459
|
+
settled = true;
|
|
460
|
+
deferred.resolve();
|
|
461
|
+
return true;
|
|
462
|
+
},
|
|
463
|
+
reject(error) {
|
|
464
|
+
if (settled)
|
|
465
|
+
return false;
|
|
466
|
+
settled = true;
|
|
467
|
+
deferred.reject(error);
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
class ManagedResourceConformanceTrace {
|
|
474
|
+
entries = [];
|
|
475
|
+
waiters = new Set;
|
|
476
|
+
record(phase, outcome) {
|
|
477
|
+
const entry = ManagedResourceConformanceTraceEntrySchema.parse({
|
|
478
|
+
sequence: this.entries.length,
|
|
479
|
+
phase,
|
|
480
|
+
outcome
|
|
481
|
+
});
|
|
482
|
+
this.entries.push(entry);
|
|
483
|
+
for (const waiter of this.waiters) {
|
|
484
|
+
if (waiter.phase !== phase || waiter.outcome !== outcome)
|
|
485
|
+
continue;
|
|
486
|
+
this.waiters.delete(waiter);
|
|
487
|
+
waiter.resolve();
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
has(phase, outcome) {
|
|
491
|
+
return this.entries.some((entry) => entry.phase === phase && entry.outcome === outcome);
|
|
492
|
+
}
|
|
493
|
+
count(phase, outcome) {
|
|
494
|
+
return this.entries.filter((entry) => entry.phase === phase && entry.outcome === outcome).length;
|
|
495
|
+
}
|
|
496
|
+
waitFor(phase, outcome = "enter") {
|
|
497
|
+
if (this.has(phase, outcome))
|
|
498
|
+
return Promise.resolve();
|
|
499
|
+
return new Promise((resolve) => this.waiters.add({ phase, outcome, resolve }));
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
function createManagedResourceConformanceGates(trace) {
|
|
503
|
+
const startup = controlledPromise();
|
|
504
|
+
const readiness = controlledPromise();
|
|
505
|
+
const completion = controlledPromise();
|
|
506
|
+
const activation = controlledPromise();
|
|
507
|
+
const close = controlledPromise();
|
|
508
|
+
const force = controlledPromise();
|
|
509
|
+
const gateFor = (phase) => {
|
|
510
|
+
if (phase === "start")
|
|
511
|
+
return startup;
|
|
512
|
+
if (phase === "readiness")
|
|
513
|
+
return readiness;
|
|
514
|
+
if (phase === "completion")
|
|
515
|
+
return completion;
|
|
516
|
+
if (phase === "activate")
|
|
517
|
+
return activation;
|
|
518
|
+
if (phase === "close")
|
|
519
|
+
return close;
|
|
520
|
+
return force;
|
|
521
|
+
};
|
|
522
|
+
const resolve = (phase) => {
|
|
523
|
+
if (gateFor(phase).resolve())
|
|
524
|
+
trace.record(phase, "resolve");
|
|
525
|
+
};
|
|
526
|
+
return {
|
|
527
|
+
controls: {
|
|
528
|
+
startup: startup.promise,
|
|
529
|
+
readiness: readiness.promise,
|
|
530
|
+
completion: completion.promise,
|
|
531
|
+
activation: activation.promise,
|
|
532
|
+
close: close.promise,
|
|
533
|
+
force: force.promise
|
|
534
|
+
},
|
|
535
|
+
resolve,
|
|
536
|
+
reject(phase, error) {
|
|
537
|
+
if (gateFor(phase).reject(error))
|
|
538
|
+
trace.record(phase, "reject");
|
|
539
|
+
},
|
|
540
|
+
releaseAll() {
|
|
541
|
+
resolve("start");
|
|
542
|
+
resolve("readiness");
|
|
543
|
+
resolve("completion");
|
|
544
|
+
resolve("activate");
|
|
545
|
+
resolve("close");
|
|
546
|
+
resolve("force");
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
async function observePhase(trace, phase, invoke) {
|
|
551
|
+
trace.record(phase, "enter");
|
|
552
|
+
try {
|
|
553
|
+
const value = await invoke();
|
|
554
|
+
trace.record(phase, "resolve");
|
|
555
|
+
return value;
|
|
556
|
+
} catch (error) {
|
|
557
|
+
trace.record(phase, "reject");
|
|
558
|
+
throw error;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
function optionalPhase(resource, trace, phase, invoke) {
|
|
562
|
+
if (!invoke)
|
|
563
|
+
return {};
|
|
564
|
+
return {
|
|
565
|
+
[phase]: (context) => observePhase(trace, phase, () => invoke.call(resource, context))
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
function observeManagedResource(resource, required, trace) {
|
|
569
|
+
const start = async (context) => {
|
|
570
|
+
trace.record("start", "enter");
|
|
571
|
+
try {
|
|
572
|
+
const value = await resource.start(context);
|
|
573
|
+
trace.record("start", "resolve");
|
|
574
|
+
if (value === undefined)
|
|
575
|
+
return;
|
|
576
|
+
return value;
|
|
577
|
+
} catch (error) {
|
|
578
|
+
trace.record("start", "reject");
|
|
579
|
+
throw error;
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
return {
|
|
583
|
+
id: resource.id,
|
|
584
|
+
required,
|
|
585
|
+
start,
|
|
586
|
+
...optionalPhase(resource, trace, "activate", resource.activate),
|
|
587
|
+
...resource.stopAdmission ? {
|
|
588
|
+
stopAdmission: (context) => observePhase(trace, "stop-admission", () => resource.stopAdmission?.call(resource, context))
|
|
589
|
+
} : {},
|
|
590
|
+
...optionalPhase(resource, trace, "drain", resource.drain),
|
|
591
|
+
...optionalPhase(resource, trace, "close", resource.close),
|
|
592
|
+
...optionalPhase(resource, trace, "force", resource.force)
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
// src/testing/managed-resource-conformance-scenarios.ts
|
|
597
|
+
var MANAGED_RESOURCE_CONFORMANCE_SCENARIOS = ManagedResourceConformanceScenarioSchema.array().parse([
|
|
598
|
+
{ id: "clean-shutdown", required: true },
|
|
599
|
+
{ id: "partial-start-rollback", required: true },
|
|
600
|
+
{ id: "readiness-rejection", required: true },
|
|
601
|
+
{ id: "completion-before-ready", required: true },
|
|
602
|
+
{ id: "required-late-completion", required: true },
|
|
603
|
+
{ id: "optional-late-completion", required: false },
|
|
604
|
+
{ id: "activation-rejection", required: true },
|
|
605
|
+
{ id: "shutdown-during-startup", required: true },
|
|
606
|
+
{ id: "force-after-stalled-close", required: true }
|
|
607
|
+
]);
|
|
608
|
+
var EXPECTED_TRACE = {
|
|
609
|
+
"clean-shutdown": [
|
|
610
|
+
"start:enter",
|
|
611
|
+
"start:resolve",
|
|
612
|
+
"readiness:resolve",
|
|
613
|
+
"activate:enter",
|
|
614
|
+
"activate:resolve",
|
|
615
|
+
"stop-admission:enter",
|
|
616
|
+
"drain:enter",
|
|
617
|
+
"close:enter",
|
|
618
|
+
"close:resolve"
|
|
619
|
+
],
|
|
620
|
+
"partial-start-rollback": ["start:enter", "start:reject", "close:enter", "close:resolve"],
|
|
621
|
+
"readiness-rejection": [
|
|
622
|
+
"start:enter",
|
|
623
|
+
"start:resolve",
|
|
624
|
+
"readiness:reject",
|
|
625
|
+
"close:enter",
|
|
626
|
+
"close:resolve"
|
|
627
|
+
],
|
|
628
|
+
"completion-before-ready": [
|
|
629
|
+
"start:enter",
|
|
630
|
+
"start:resolve",
|
|
631
|
+
"completion:reject",
|
|
632
|
+
"close:enter",
|
|
633
|
+
"close:resolve"
|
|
634
|
+
],
|
|
635
|
+
"required-late-completion": [
|
|
636
|
+
"activate:resolve",
|
|
637
|
+
"completion:reject",
|
|
638
|
+
"stop-admission:enter",
|
|
639
|
+
"drain:enter",
|
|
640
|
+
"close:enter"
|
|
641
|
+
],
|
|
642
|
+
"optional-late-completion": [
|
|
643
|
+
"activate:resolve",
|
|
644
|
+
"completion:reject",
|
|
645
|
+
"stop-admission:enter",
|
|
646
|
+
"drain:enter",
|
|
647
|
+
"close:enter"
|
|
648
|
+
],
|
|
649
|
+
"activation-rejection": [
|
|
650
|
+
"readiness:resolve",
|
|
651
|
+
"activate:enter",
|
|
652
|
+
"activate:reject",
|
|
653
|
+
"close:enter",
|
|
654
|
+
"close:resolve"
|
|
655
|
+
],
|
|
656
|
+
"shutdown-during-startup": ["start:enter", "start:resolve", "close:enter", "close:resolve"],
|
|
657
|
+
"force-after-stalled-close": [
|
|
658
|
+
"stop-admission:enter",
|
|
659
|
+
"drain:enter",
|
|
660
|
+
"close:enter",
|
|
661
|
+
"force:enter",
|
|
662
|
+
"force:resolve"
|
|
663
|
+
]
|
|
664
|
+
};
|
|
665
|
+
function outcomeOf(promise) {
|
|
666
|
+
return promise.then(() => ({ status: "resolved" }), (error) => ({ status: "rejected", error }));
|
|
667
|
+
}
|
|
668
|
+
function requireCondition(condition, detail) {
|
|
669
|
+
if (!condition)
|
|
670
|
+
throw new Error(detail);
|
|
671
|
+
}
|
|
672
|
+
function observedTrace(trace) {
|
|
673
|
+
return trace.entries.map((entry) => `${entry.phase}:${entry.outcome}`);
|
|
674
|
+
}
|
|
675
|
+
function requireTrace(runtime) {
|
|
676
|
+
const expected = EXPECTED_TRACE[runtime.scenario.id];
|
|
677
|
+
const observed = observedTrace(runtime.trace);
|
|
678
|
+
let cursor = 0;
|
|
679
|
+
for (const entry of observed) {
|
|
680
|
+
if (entry === expected[cursor])
|
|
681
|
+
cursor += 1;
|
|
682
|
+
}
|
|
683
|
+
requireCondition(cursor === expected.length, `phase trace omitted or reordered "${expected[cursor] ?? "unknown"}"`);
|
|
684
|
+
}
|
|
685
|
+
async function phaseOrSettlement(trace, phase, settlement) {
|
|
686
|
+
return Promise.race([
|
|
687
|
+
trace.waitFor(phase).then(() => "phase"),
|
|
688
|
+
settlement.then(() => "settled", () => "settled")
|
|
689
|
+
]);
|
|
690
|
+
}
|
|
691
|
+
async function startSuccessfully(runtime) {
|
|
692
|
+
const starting = runtime.application.start();
|
|
693
|
+
const startOutcome = outcomeOf(starting);
|
|
694
|
+
await runtime.trace.waitFor("start");
|
|
695
|
+
runtime.gates.resolve("start");
|
|
696
|
+
runtime.gates.resolve("readiness");
|
|
697
|
+
const activation = await Promise.race([
|
|
698
|
+
runtime.trace.waitFor("activate").then(() => ({ status: "resolved" })),
|
|
699
|
+
startOutcome
|
|
700
|
+
]);
|
|
701
|
+
if (activation.status === "rejected")
|
|
702
|
+
throw activation.error;
|
|
703
|
+
runtime.gates.resolve("activate");
|
|
704
|
+
const outcome = await startOutcome;
|
|
705
|
+
requireCondition(outcome.status === "resolved", "application did not complete successful startup");
|
|
706
|
+
}
|
|
707
|
+
async function closeNormally(runtime) {
|
|
708
|
+
const shuttingDown = runtime.application.shutdown();
|
|
709
|
+
const close = await phaseOrSettlement(runtime.trace, "close", shuttingDown);
|
|
710
|
+
if (close === "phase")
|
|
711
|
+
runtime.gates.resolve("close");
|
|
712
|
+
const outcome = await outcomeOf(shuttingDown);
|
|
713
|
+
requireCondition(outcome.status === "resolved", "application shutdown rejected");
|
|
714
|
+
}
|
|
715
|
+
async function expectStartupFailure(runtime, starting) {
|
|
716
|
+
const close = await phaseOrSettlement(runtime.trace, "close", starting);
|
|
717
|
+
if (close === "phase")
|
|
718
|
+
runtime.gates.resolve("close");
|
|
719
|
+
const outcome = await outcomeOf(starting);
|
|
720
|
+
if (outcome.status === "resolved") {
|
|
721
|
+
await closeNormally(runtime);
|
|
722
|
+
throw new Error("application startup unexpectedly resolved");
|
|
723
|
+
}
|
|
724
|
+
requireCondition(runtime.trace.count("close", "enter") === 1, "attempted resource was not closed once");
|
|
725
|
+
await runtime.application.shutdown();
|
|
726
|
+
}
|
|
727
|
+
function waitForSnapshot(application, predicate) {
|
|
728
|
+
const deferred = Promise.withResolvers();
|
|
729
|
+
let unsubscribe = () => {
|
|
730
|
+
return;
|
|
731
|
+
};
|
|
732
|
+
unsubscribe = application.subscribe((snapshot) => {
|
|
733
|
+
if (!predicate(snapshot))
|
|
734
|
+
return;
|
|
735
|
+
unsubscribe();
|
|
736
|
+
deferred.resolve(snapshot);
|
|
737
|
+
});
|
|
738
|
+
return deferred.promise;
|
|
739
|
+
}
|
|
740
|
+
async function cleanShutdown(runtime) {
|
|
741
|
+
await startSuccessfully(runtime);
|
|
742
|
+
await closeNormally(runtime);
|
|
743
|
+
requireCondition(runtime.application.getSnapshot().lifecycle === "stopped", "clean shutdown did not stop the application");
|
|
744
|
+
}
|
|
745
|
+
async function partialStartRollback(runtime) {
|
|
746
|
+
const starting = runtime.application.start();
|
|
747
|
+
await runtime.trace.waitFor("start");
|
|
748
|
+
runtime.gates.reject("start", new Error("controlled partial startup rejection"));
|
|
749
|
+
await expectStartupFailure(runtime, starting);
|
|
750
|
+
}
|
|
751
|
+
async function readinessRejection(runtime) {
|
|
752
|
+
const starting = runtime.application.start();
|
|
753
|
+
await runtime.trace.waitFor("start");
|
|
754
|
+
runtime.gates.resolve("start");
|
|
755
|
+
await runtime.trace.waitFor("start", "resolve");
|
|
756
|
+
runtime.gates.reject("readiness", new Error("controlled readiness rejection"));
|
|
757
|
+
runtime.gates.resolve("activate");
|
|
758
|
+
await expectStartupFailure(runtime, starting);
|
|
759
|
+
}
|
|
760
|
+
async function completionBeforeReady(runtime) {
|
|
761
|
+
const starting = runtime.application.start();
|
|
762
|
+
await runtime.trace.waitFor("start");
|
|
763
|
+
runtime.gates.resolve("start");
|
|
764
|
+
await runtime.trace.waitFor("start", "resolve");
|
|
765
|
+
runtime.gates.reject("completion", new Error("controlled early completion rejection"));
|
|
766
|
+
runtime.gates.resolve("readiness");
|
|
767
|
+
runtime.gates.resolve("activate");
|
|
768
|
+
await expectStartupFailure(runtime, starting);
|
|
769
|
+
}
|
|
770
|
+
async function lateCompletion(runtime) {
|
|
771
|
+
await startSuccessfully(runtime);
|
|
772
|
+
const expectedHealth = runtime.scenario.required ? "unhealthy" : "degraded";
|
|
773
|
+
const changed = waitForSnapshot(runtime.application, (snapshot2) => snapshot2.health === expectedHealth);
|
|
774
|
+
runtime.gates.reject("completion", new Error("controlled late completion rejection"));
|
|
775
|
+
const snapshot = await changed;
|
|
776
|
+
requireCondition(snapshot.lifecycle === "ready", "late completion changed lifecycle ownership");
|
|
777
|
+
requireCondition(snapshot.health === expectedHealth, "late completion projected the wrong health");
|
|
778
|
+
requireCondition(snapshot.ready === !runtime.scenario.required, "late completion projected the wrong readiness");
|
|
779
|
+
requireCondition(snapshot.admission.accepting === !runtime.scenario.required, "late completion projected the wrong admission state");
|
|
780
|
+
await closeNormally(runtime);
|
|
781
|
+
}
|
|
782
|
+
async function activationRejection(runtime) {
|
|
783
|
+
const starting = runtime.application.start();
|
|
784
|
+
await runtime.trace.waitFor("start");
|
|
785
|
+
runtime.gates.resolve("start");
|
|
786
|
+
await runtime.trace.waitFor("start", "resolve");
|
|
787
|
+
runtime.gates.resolve("readiness");
|
|
788
|
+
await runtime.trace.waitFor("activate");
|
|
789
|
+
runtime.gates.reject("activate", new Error("controlled activation rejection"));
|
|
790
|
+
await expectStartupFailure(runtime, starting);
|
|
791
|
+
}
|
|
792
|
+
async function shutdownDuringStartup(runtime) {
|
|
793
|
+
const starting = runtime.application.start();
|
|
794
|
+
await runtime.trace.waitFor("start");
|
|
795
|
+
const shuttingDown = runtime.application.shutdown();
|
|
796
|
+
runtime.gates.resolve("start");
|
|
797
|
+
const close = await phaseOrSettlement(runtime.trace, "close", shuttingDown);
|
|
798
|
+
if (close === "phase")
|
|
799
|
+
runtime.gates.resolve("close");
|
|
800
|
+
const [startOutcome, shutdownOutcome] = await Promise.all([
|
|
801
|
+
outcomeOf(starting),
|
|
802
|
+
outcomeOf(shuttingDown)
|
|
803
|
+
]);
|
|
804
|
+
requireCondition(startOutcome.status === "rejected", "startup won the shutdown race");
|
|
805
|
+
requireCondition(shutdownOutcome.status === "resolved", "shutdown race rejected");
|
|
806
|
+
requireCondition(!runtime.trace.has("activate", "enter"), "resource activated after shutdown won");
|
|
807
|
+
}
|
|
808
|
+
async function forceAfterStalledClose(runtime) {
|
|
809
|
+
await startSuccessfully(runtime);
|
|
810
|
+
const controller = new AbortController;
|
|
811
|
+
const shuttingDown = runtime.application.shutdown({
|
|
812
|
+
gracePeriodMs: 60000,
|
|
813
|
+
forceTimeoutMs: 60000,
|
|
814
|
+
signal: controller.signal
|
|
815
|
+
});
|
|
816
|
+
await runtime.trace.waitFor("close");
|
|
817
|
+
controller.abort();
|
|
818
|
+
const force = await phaseOrSettlement(runtime.trace, "force", shuttingDown);
|
|
819
|
+
if (force === "phase")
|
|
820
|
+
runtime.gates.resolve("force");
|
|
821
|
+
const result = await shuttingDown;
|
|
822
|
+
requireCondition(runtime.trace.count("close", "enter") === 1, "close was not invoked exactly once");
|
|
823
|
+
requireCondition(runtime.trace.count("force", "enter") === 1, "unfinished close did not invoke force");
|
|
824
|
+
requireCondition(result.outcome === "forced", "forced shutdown reported a clean outcome");
|
|
825
|
+
requireCondition(result.reason === "signal", "forced shutdown lost its signal reason");
|
|
826
|
+
runtime.gates.resolve("close");
|
|
827
|
+
}
|
|
828
|
+
function expectedManagedResourceConformanceTrace(scenarioId) {
|
|
829
|
+
return EXPECTED_TRACE[scenarioId];
|
|
830
|
+
}
|
|
831
|
+
async function executeManagedResourceConformanceScenario(scenario, resource, gates, trace) {
|
|
832
|
+
const application = createApplication({
|
|
833
|
+
id: `conformance-${scenario.id}`,
|
|
834
|
+
resources: [observeManagedResource(resource, scenario.required, trace)]
|
|
835
|
+
});
|
|
836
|
+
const runtime = { scenario, application, gates, trace };
|
|
837
|
+
if (scenario.id === "clean-shutdown")
|
|
838
|
+
await cleanShutdown(runtime);
|
|
839
|
+
else if (scenario.id === "partial-start-rollback")
|
|
840
|
+
await partialStartRollback(runtime);
|
|
841
|
+
else if (scenario.id === "readiness-rejection")
|
|
842
|
+
await readinessRejection(runtime);
|
|
843
|
+
else if (scenario.id === "completion-before-ready")
|
|
844
|
+
await completionBeforeReady(runtime);
|
|
845
|
+
else if (scenario.id === "required-late-completion" || scenario.id === "optional-late-completion") {
|
|
846
|
+
await lateCompletion(runtime);
|
|
847
|
+
} else if (scenario.id === "activation-rejection")
|
|
848
|
+
await activationRejection(runtime);
|
|
849
|
+
else if (scenario.id === "shutdown-during-startup")
|
|
850
|
+
await shutdownDuringStartup(runtime);
|
|
851
|
+
else
|
|
852
|
+
await forceAfterStalledClose(runtime);
|
|
853
|
+
requireTrace(runtime);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// src/testing/managed-resource-conformance.ts
|
|
857
|
+
var DEFAULT_WATCHDOG_TIMEOUT_MS = 2000;
|
|
858
|
+
|
|
859
|
+
class ManagedResourceConformanceWatchdogError extends Error {
|
|
860
|
+
phase;
|
|
861
|
+
constructor(phase) {
|
|
862
|
+
super(`emergency watchdog expired during ${phase}`);
|
|
863
|
+
this.phase = phase;
|
|
864
|
+
this.name = "ManagedResourceConformanceWatchdogError";
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
async function withWatchdog(promise, timeoutMs, phase) {
|
|
868
|
+
let timer;
|
|
869
|
+
const watchdog = new Promise((_resolve, reject) => {
|
|
870
|
+
timer = setTimeout(() => reject(new ManagedResourceConformanceWatchdogError(phase)), timeoutMs);
|
|
871
|
+
});
|
|
872
|
+
try {
|
|
873
|
+
return await Promise.race([promise, watchdog]);
|
|
874
|
+
} finally {
|
|
875
|
+
if (timer !== undefined)
|
|
876
|
+
clearTimeout(timer);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
function detailOf(error) {
|
|
880
|
+
if (error instanceof ManagedResourceConformanceWatchdogError)
|
|
881
|
+
return error.message;
|
|
882
|
+
if (error instanceof Error)
|
|
883
|
+
return error.message;
|
|
884
|
+
return "unknown conformance failure";
|
|
885
|
+
}
|
|
886
|
+
async function runScenario(config, scenario, watchdogTimeoutMs) {
|
|
887
|
+
const trace = new ManagedResourceConformanceTrace;
|
|
888
|
+
const gates = createManagedResourceConformanceGates(trace);
|
|
889
|
+
const fixture = await withWatchdog(Promise.resolve().then(() => config.createFixture({ scenario, controls: gates.controls })), watchdogTimeoutMs, "factory").catch((error) => {
|
|
890
|
+
throw new ManagedResourceConformanceError(scenario.id, expectedManagedResourceConformanceTrace(scenario.id), trace.entries, detailOf(error));
|
|
891
|
+
});
|
|
892
|
+
let failure;
|
|
893
|
+
const execution = executeManagedResourceConformanceScenario(scenario, fixture.resource, gates, trace);
|
|
894
|
+
execution.catch(() => {
|
|
895
|
+
return;
|
|
896
|
+
});
|
|
897
|
+
let settlementIncomplete = false;
|
|
898
|
+
try {
|
|
899
|
+
await withWatchdog(execution, watchdogTimeoutMs, "scenario");
|
|
900
|
+
} catch (error) {
|
|
901
|
+
failure = { value: error };
|
|
902
|
+
} finally {
|
|
903
|
+
gates.releaseAll();
|
|
904
|
+
if (failure?.value instanceof ManagedResourceConformanceWatchdogError) {
|
|
905
|
+
try {
|
|
906
|
+
await withWatchdog(execution, watchdogTimeoutMs, "settlement");
|
|
907
|
+
} catch (error) {
|
|
908
|
+
if (error instanceof ManagedResourceConformanceWatchdogError) {
|
|
909
|
+
settlementIncomplete = true;
|
|
910
|
+
} else {
|
|
911
|
+
failure = { value: error };
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
trace.record("dispose", "enter");
|
|
916
|
+
try {
|
|
917
|
+
await withWatchdog(Promise.resolve(fixture.dispose()), watchdogTimeoutMs, "dispose");
|
|
918
|
+
trace.record("dispose", "resolve");
|
|
919
|
+
} catch (error) {
|
|
920
|
+
trace.record("dispose", "reject");
|
|
921
|
+
if (failure === undefined)
|
|
922
|
+
failure = { value: error };
|
|
923
|
+
}
|
|
924
|
+
if (settlementIncomplete) {
|
|
925
|
+
try {
|
|
926
|
+
await withWatchdog(execution, watchdogTimeoutMs, "settlement");
|
|
927
|
+
settlementIncomplete = false;
|
|
928
|
+
} catch (error) {
|
|
929
|
+
if (error instanceof ManagedResourceConformanceWatchdogError) {
|
|
930
|
+
const original = failure?.value;
|
|
931
|
+
failure = {
|
|
932
|
+
value: new Error(`${detailOf(original)}; execution remained live after fixture disposal`)
|
|
933
|
+
};
|
|
934
|
+
} else {
|
|
935
|
+
settlementIncomplete = false;
|
|
936
|
+
failure = { value: error };
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
if (failure !== undefined) {
|
|
942
|
+
if (failure.value instanceof ManagedResourceConformanceError)
|
|
943
|
+
throw failure.value;
|
|
944
|
+
throw new ManagedResourceConformanceError(scenario.id, expectedManagedResourceConformanceTrace(scenario.id), trace.entries, detailOf(failure.value));
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
async function runManagedResourceConformance(config) {
|
|
948
|
+
const watchdogTimeoutMs = config.watchdogTimeoutMs ?? DEFAULT_WATCHDOG_TIMEOUT_MS;
|
|
949
|
+
if (!Number.isSafeInteger(watchdogTimeoutMs) || watchdogTimeoutMs <= 0) {
|
|
950
|
+
throw new TypeError("[stitchkit] managed resource conformance watchdogTimeoutMs must be positive");
|
|
951
|
+
}
|
|
952
|
+
const selected = new Set((config.scenarios ?? MANAGED_RESOURCE_CONFORMANCE_SCENARIOS.map((scenario) => scenario.id)).map((scenario) => ManagedResourceConformanceScenarioIdSchema.parse(scenario)));
|
|
953
|
+
if (selected.size === 0) {
|
|
954
|
+
throw new TypeError("[stitchkit] managed resource conformance scenarios must not be empty");
|
|
955
|
+
}
|
|
956
|
+
for (const scenario of MANAGED_RESOURCE_CONFORMANCE_SCENARIOS) {
|
|
957
|
+
if (!selected.has(scenario.id))
|
|
958
|
+
continue;
|
|
959
|
+
await runScenario(config, scenario, watchdogTimeoutMs);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
381
962
|
// src/testing/surface-conformance.ts
|
|
382
|
-
import { z as
|
|
963
|
+
import { z as z3 } from "zod";
|
|
383
964
|
|
|
384
965
|
// src/testing/surface-manifest.ts
|
|
385
966
|
import { createHash } from "node:crypto";
|
|
386
|
-
import { z } from "zod";
|
|
387
|
-
var HttpMethodSchema =
|
|
388
|
-
var ToolTransportSchema =
|
|
389
|
-
var SurfaceTransportSchema =
|
|
390
|
-
var SurfaceSchemaDigestsSchema =
|
|
391
|
-
params:
|
|
392
|
-
input:
|
|
393
|
-
output:
|
|
394
|
-
multipart:
|
|
967
|
+
import { z as z2 } from "zod";
|
|
968
|
+
var HttpMethodSchema = z2.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]);
|
|
969
|
+
var ToolTransportSchema = z2.enum(["MCP", "AGENT", "CLI"]);
|
|
970
|
+
var SurfaceTransportSchema = z2.enum(["HTTP", "MCP", "AGENT", "CLI", "REALTIME"]);
|
|
971
|
+
var SurfaceSchemaDigestsSchema = z2.object({
|
|
972
|
+
params: z2.string().nullable(),
|
|
973
|
+
input: z2.string().nullable(),
|
|
974
|
+
output: z2.string().nullable(),
|
|
975
|
+
multipart: z2.string().nullable()
|
|
395
976
|
});
|
|
396
|
-
var SurfaceManifestOperationSchema =
|
|
397
|
-
kind:
|
|
398
|
-
service:
|
|
399
|
-
action:
|
|
977
|
+
var SurfaceManifestOperationSchema = z2.object({
|
|
978
|
+
kind: z2.enum(["contract", "runtime"]),
|
|
979
|
+
service: z2.string(),
|
|
980
|
+
action: z2.string(),
|
|
400
981
|
method: HttpMethodSchema,
|
|
401
|
-
scope:
|
|
402
|
-
description:
|
|
982
|
+
scope: z2.string().nullable(),
|
|
983
|
+
description: z2.string(),
|
|
403
984
|
schemas: SurfaceSchemaDigestsSchema,
|
|
404
|
-
http:
|
|
985
|
+
http: z2.array(z2.object({ method: HttpMethodSchema, path: z2.string() }))
|
|
405
986
|
});
|
|
406
|
-
var SurfaceManifestToolSchema =
|
|
407
|
-
kind:
|
|
408
|
-
service:
|
|
409
|
-
action:
|
|
410
|
-
name:
|
|
411
|
-
input:
|
|
987
|
+
var SurfaceManifestToolSchema = z2.object({
|
|
988
|
+
kind: z2.enum(["contract", "runtime"]),
|
|
989
|
+
service: z2.string(),
|
|
990
|
+
action: z2.string(),
|
|
991
|
+
name: z2.string(),
|
|
992
|
+
input: z2.string()
|
|
412
993
|
});
|
|
413
|
-
var SurfaceManifestToolSurfaceSchema =
|
|
994
|
+
var SurfaceManifestToolSurfaceSchema = z2.object({
|
|
414
995
|
transport: ToolTransportSchema,
|
|
415
|
-
surface:
|
|
416
|
-
tools:
|
|
996
|
+
surface: z2.string().nullable(),
|
|
997
|
+
tools: z2.array(SurfaceManifestToolSchema)
|
|
417
998
|
});
|
|
418
|
-
var SurfaceManifestCliCommandSchema =
|
|
419
|
-
name:
|
|
420
|
-
description:
|
|
421
|
-
input:
|
|
422
|
-
output:
|
|
999
|
+
var SurfaceManifestCliCommandSchema = z2.object({
|
|
1000
|
+
name: z2.string(),
|
|
1001
|
+
description: z2.string(),
|
|
1002
|
+
input: z2.string(),
|
|
1003
|
+
output: z2.string().nullable()
|
|
423
1004
|
});
|
|
424
|
-
var SurfaceManifestExtensionSchema =
|
|
425
|
-
name:
|
|
1005
|
+
var SurfaceManifestExtensionSchema = z2.object({
|
|
1006
|
+
name: z2.string(),
|
|
426
1007
|
transport: SurfaceTransportSchema
|
|
427
1008
|
});
|
|
428
|
-
var SurfaceRealtimeSchemaPairSchema =
|
|
429
|
-
input:
|
|
430
|
-
output:
|
|
1009
|
+
var SurfaceRealtimeSchemaPairSchema = z2.object({
|
|
1010
|
+
input: z2.string(),
|
|
1011
|
+
output: z2.string()
|
|
431
1012
|
});
|
|
432
|
-
var SurfaceManifestRealtimeEventSchema =
|
|
433
|
-
contract:
|
|
434
|
-
direction:
|
|
435
|
-
event:
|
|
1013
|
+
var SurfaceManifestRealtimeEventSchema = z2.object({
|
|
1014
|
+
contract: z2.string(),
|
|
1015
|
+
direction: z2.enum(["serverToClient", "clientToServer"]),
|
|
1016
|
+
event: z2.string(),
|
|
436
1017
|
args: SurfaceRealtimeSchemaPairSchema,
|
|
437
1018
|
acknowledgement: SurfaceRealtimeSchemaPairSchema.nullable()
|
|
438
1019
|
});
|
|
439
|
-
var SurfaceManifestSchema =
|
|
440
|
-
manifestVersion:
|
|
441
|
-
digestVersion:
|
|
442
|
-
operations:
|
|
443
|
-
toolSurfaces:
|
|
444
|
-
realtimeContracts:
|
|
445
|
-
realtime:
|
|
446
|
-
cliOnly:
|
|
447
|
-
extensions:
|
|
1020
|
+
var SurfaceManifestSchema = z2.object({
|
|
1021
|
+
manifestVersion: z2.literal(2),
|
|
1022
|
+
digestVersion: z2.literal(1),
|
|
1023
|
+
operations: z2.array(SurfaceManifestOperationSchema),
|
|
1024
|
+
toolSurfaces: z2.array(SurfaceManifestToolSurfaceSchema),
|
|
1025
|
+
realtimeContracts: z2.array(z2.string()),
|
|
1026
|
+
realtime: z2.array(SurfaceManifestRealtimeEventSchema),
|
|
1027
|
+
cliOnly: z2.array(SurfaceManifestCliCommandSchema),
|
|
1028
|
+
extensions: z2.array(SurfaceManifestExtensionSchema)
|
|
448
1029
|
});
|
|
449
1030
|
function compareCodepoints(left, right) {
|
|
450
1031
|
if (left < right)
|
|
@@ -729,47 +1310,47 @@ actual: ${actual}`);
|
|
|
729
1310
|
}
|
|
730
1311
|
|
|
731
1312
|
// src/testing/surface-conformance.ts
|
|
732
|
-
var RealtimeRejectionObservationSchema =
|
|
733
|
-
direction:
|
|
1313
|
+
var RealtimeRejectionObservationSchema = z3.object({
|
|
1314
|
+
direction: z3.enum([
|
|
734
1315
|
"client-inbound",
|
|
735
1316
|
"client-outbound",
|
|
736
1317
|
"server-inbound",
|
|
737
1318
|
"server-outbound"
|
|
738
1319
|
]),
|
|
739
|
-
phase:
|
|
740
|
-
reason:
|
|
1320
|
+
phase: z3.enum(["arguments", "acknowledgement"]),
|
|
1321
|
+
reason: z3.enum([
|
|
741
1322
|
"unknown-event",
|
|
742
1323
|
"invalid-arguments",
|
|
743
1324
|
"invalid-acknowledgement-value",
|
|
744
1325
|
"missing-acknowledgement"
|
|
745
1326
|
]),
|
|
746
|
-
fault:
|
|
1327
|
+
fault: z3.enum(["peer", "local"])
|
|
747
1328
|
});
|
|
748
1329
|
var ObservationFields = {
|
|
749
|
-
code:
|
|
750
|
-
data:
|
|
751
|
-
diagnostic:
|
|
752
|
-
handlerCalls:
|
|
1330
|
+
code: z3.string().optional(),
|
|
1331
|
+
data: z3.unknown().optional(),
|
|
1332
|
+
diagnostic: z3.string().optional(),
|
|
1333
|
+
handlerCalls: z3.number().int().nonnegative().optional()
|
|
753
1334
|
};
|
|
754
|
-
var RealtimeDisconnectObservationSchema =
|
|
755
|
-
phase:
|
|
1335
|
+
var RealtimeDisconnectObservationSchema = z3.object({
|
|
1336
|
+
phase: z3.enum(["before-invoke", "in-flight"])
|
|
756
1337
|
});
|
|
757
|
-
var TransportObservationSchema =
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
outcome:
|
|
1338
|
+
var TransportObservationSchema = z3.discriminatedUnion("outcome", [
|
|
1339
|
+
z3.object({ outcome: z3.literal("success"), ...ObservationFields }),
|
|
1340
|
+
z3.object({ outcome: z3.literal("validation_error"), ...ObservationFields }),
|
|
1341
|
+
z3.object({ outcome: z3.literal("domain_error"), ...ObservationFields }),
|
|
1342
|
+
z3.object({ outcome: z3.literal("aborted"), ...ObservationFields }),
|
|
1343
|
+
z3.object({
|
|
1344
|
+
outcome: z3.literal("realtime_rejected"),
|
|
764
1345
|
...ObservationFields,
|
|
765
1346
|
rejection: RealtimeRejectionObservationSchema
|
|
766
1347
|
}),
|
|
767
|
-
|
|
768
|
-
outcome:
|
|
1348
|
+
z3.object({
|
|
1349
|
+
outcome: z3.literal("disconnected"),
|
|
769
1350
|
...ObservationFields,
|
|
770
1351
|
disconnect: RealtimeDisconnectObservationSchema
|
|
771
1352
|
}),
|
|
772
|
-
|
|
1353
|
+
z3.object({ outcome: z3.literal("timeout"), ...ObservationFields })
|
|
773
1354
|
]);
|
|
774
1355
|
function scenarioOutcome(scenario) {
|
|
775
1356
|
if (scenario === "event" || scenario === "acknowledgement")
|
|
@@ -1081,6 +1662,12 @@ function createHandlerTestClients(config) {
|
|
|
1081
1662
|
return createClients(config.contracts, createTestClientConfig(config), config.contractConfig);
|
|
1082
1663
|
}
|
|
1083
1664
|
export {
|
|
1665
|
+
ManagedResourceConformanceError,
|
|
1666
|
+
ManagedResourceConformancePhaseSchema,
|
|
1667
|
+
ManagedResourceConformanceScenarioIdSchema,
|
|
1668
|
+
ManagedResourceConformanceScenarioSchema,
|
|
1669
|
+
ManagedResourceConformanceTraceEntrySchema,
|
|
1670
|
+
ManagedResourceConformanceTraceOutcomeSchema,
|
|
1084
1671
|
RealtimeDisconnectObservationSchema,
|
|
1085
1672
|
RealtimeRejectionObservationSchema,
|
|
1086
1673
|
SurfaceManifestExtensionSchema,
|
|
@@ -1103,6 +1690,7 @@ export {
|
|
|
1103
1690
|
createRealtimeProbeDriver,
|
|
1104
1691
|
defineRealtimeProbe,
|
|
1105
1692
|
runAgentStoreConformance,
|
|
1693
|
+
runManagedResourceConformance,
|
|
1106
1694
|
runSurfaceProbes,
|
|
1107
1695
|
serializeSurfaceValue
|
|
1108
1696
|
};
|