stitchkit 0.59.2 → 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/agent-runtime-openrouter.js +0 -1
- package/dist/agent-runtime.js +0 -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-grammy.js +0 -1
- 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 -1241
- package/dist/cli.js +0 -1
- package/dist/files.js +0 -1
- package/dist/index-2nzpcfbp.js +560 -0
- package/dist/index-5c8n2123.js +617 -0
- package/dist/index-mgf6cdkm.js +66 -0
- package/dist/{index-he4psyve.js → index-s8w2y7kr.js} +7 -8
- package/dist/node.js +8 -9
- package/dist/observability/index.js +0 -1
- package/dist/remote.js +0 -1
- package/dist/server/index.js +8 -9
- package/dist/server/socket-io.d.ts.map +1 -1
- 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 -71
- package/dist/tools.js +0 -1
- package/llms-full.txt +290 -1
- package/llms.txt +1 -0
- package/package.json +11 -2
- package/dist/index-1bx83sw4.js +0 -4
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,
|
|
@@ -25,7 +30,6 @@ import"./index-y2rb7dwx.js";
|
|
|
25
30
|
import {
|
|
26
31
|
isRecord
|
|
27
32
|
} from "./index-smpbdg6k.js";
|
|
28
|
-
import"./index-1bx83sw4.js";
|
|
29
33
|
|
|
30
34
|
// src/agent-runtime/testing.ts
|
|
31
35
|
function createAgentRaceBarrier(timeoutMs = 5000) {
|
|
@@ -379,73 +383,649 @@ async function runAgentStoreConformance(config) {
|
|
|
379
383
|
throw new Error("Abandon recovery did not atomically terminalize its assistant record");
|
|
380
384
|
}
|
|
381
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
|
+
}
|
|
382
962
|
// src/testing/surface-conformance.ts
|
|
383
|
-
import { z as
|
|
963
|
+
import { z as z3 } from "zod";
|
|
384
964
|
|
|
385
965
|
// src/testing/surface-manifest.ts
|
|
386
966
|
import { createHash } from "node:crypto";
|
|
387
|
-
import { z } from "zod";
|
|
388
|
-
var HttpMethodSchema =
|
|
389
|
-
var ToolTransportSchema =
|
|
390
|
-
var SurfaceTransportSchema =
|
|
391
|
-
var SurfaceSchemaDigestsSchema =
|
|
392
|
-
params:
|
|
393
|
-
input:
|
|
394
|
-
output:
|
|
395
|
-
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()
|
|
396
976
|
});
|
|
397
|
-
var SurfaceManifestOperationSchema =
|
|
398
|
-
kind:
|
|
399
|
-
service:
|
|
400
|
-
action:
|
|
977
|
+
var SurfaceManifestOperationSchema = z2.object({
|
|
978
|
+
kind: z2.enum(["contract", "runtime"]),
|
|
979
|
+
service: z2.string(),
|
|
980
|
+
action: z2.string(),
|
|
401
981
|
method: HttpMethodSchema,
|
|
402
|
-
scope:
|
|
403
|
-
description:
|
|
982
|
+
scope: z2.string().nullable(),
|
|
983
|
+
description: z2.string(),
|
|
404
984
|
schemas: SurfaceSchemaDigestsSchema,
|
|
405
|
-
http:
|
|
985
|
+
http: z2.array(z2.object({ method: HttpMethodSchema, path: z2.string() }))
|
|
406
986
|
});
|
|
407
|
-
var SurfaceManifestToolSchema =
|
|
408
|
-
kind:
|
|
409
|
-
service:
|
|
410
|
-
action:
|
|
411
|
-
name:
|
|
412
|
-
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()
|
|
413
993
|
});
|
|
414
|
-
var SurfaceManifestToolSurfaceSchema =
|
|
994
|
+
var SurfaceManifestToolSurfaceSchema = z2.object({
|
|
415
995
|
transport: ToolTransportSchema,
|
|
416
|
-
surface:
|
|
417
|
-
tools:
|
|
996
|
+
surface: z2.string().nullable(),
|
|
997
|
+
tools: z2.array(SurfaceManifestToolSchema)
|
|
418
998
|
});
|
|
419
|
-
var SurfaceManifestCliCommandSchema =
|
|
420
|
-
name:
|
|
421
|
-
description:
|
|
422
|
-
input:
|
|
423
|
-
output:
|
|
999
|
+
var SurfaceManifestCliCommandSchema = z2.object({
|
|
1000
|
+
name: z2.string(),
|
|
1001
|
+
description: z2.string(),
|
|
1002
|
+
input: z2.string(),
|
|
1003
|
+
output: z2.string().nullable()
|
|
424
1004
|
});
|
|
425
|
-
var SurfaceManifestExtensionSchema =
|
|
426
|
-
name:
|
|
1005
|
+
var SurfaceManifestExtensionSchema = z2.object({
|
|
1006
|
+
name: z2.string(),
|
|
427
1007
|
transport: SurfaceTransportSchema
|
|
428
1008
|
});
|
|
429
|
-
var SurfaceRealtimeSchemaPairSchema =
|
|
430
|
-
input:
|
|
431
|
-
output:
|
|
1009
|
+
var SurfaceRealtimeSchemaPairSchema = z2.object({
|
|
1010
|
+
input: z2.string(),
|
|
1011
|
+
output: z2.string()
|
|
432
1012
|
});
|
|
433
|
-
var SurfaceManifestRealtimeEventSchema =
|
|
434
|
-
contract:
|
|
435
|
-
direction:
|
|
436
|
-
event:
|
|
1013
|
+
var SurfaceManifestRealtimeEventSchema = z2.object({
|
|
1014
|
+
contract: z2.string(),
|
|
1015
|
+
direction: z2.enum(["serverToClient", "clientToServer"]),
|
|
1016
|
+
event: z2.string(),
|
|
437
1017
|
args: SurfaceRealtimeSchemaPairSchema,
|
|
438
1018
|
acknowledgement: SurfaceRealtimeSchemaPairSchema.nullable()
|
|
439
1019
|
});
|
|
440
|
-
var SurfaceManifestSchema =
|
|
441
|
-
manifestVersion:
|
|
442
|
-
digestVersion:
|
|
443
|
-
operations:
|
|
444
|
-
toolSurfaces:
|
|
445
|
-
realtimeContracts:
|
|
446
|
-
realtime:
|
|
447
|
-
cliOnly:
|
|
448
|
-
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)
|
|
449
1029
|
});
|
|
450
1030
|
function compareCodepoints(left, right) {
|
|
451
1031
|
if (left < right)
|
|
@@ -730,47 +1310,47 @@ actual: ${actual}`);
|
|
|
730
1310
|
}
|
|
731
1311
|
|
|
732
1312
|
// src/testing/surface-conformance.ts
|
|
733
|
-
var RealtimeRejectionObservationSchema =
|
|
734
|
-
direction:
|
|
1313
|
+
var RealtimeRejectionObservationSchema = z3.object({
|
|
1314
|
+
direction: z3.enum([
|
|
735
1315
|
"client-inbound",
|
|
736
1316
|
"client-outbound",
|
|
737
1317
|
"server-inbound",
|
|
738
1318
|
"server-outbound"
|
|
739
1319
|
]),
|
|
740
|
-
phase:
|
|
741
|
-
reason:
|
|
1320
|
+
phase: z3.enum(["arguments", "acknowledgement"]),
|
|
1321
|
+
reason: z3.enum([
|
|
742
1322
|
"unknown-event",
|
|
743
1323
|
"invalid-arguments",
|
|
744
1324
|
"invalid-acknowledgement-value",
|
|
745
1325
|
"missing-acknowledgement"
|
|
746
1326
|
]),
|
|
747
|
-
fault:
|
|
1327
|
+
fault: z3.enum(["peer", "local"])
|
|
748
1328
|
});
|
|
749
1329
|
var ObservationFields = {
|
|
750
|
-
code:
|
|
751
|
-
data:
|
|
752
|
-
diagnostic:
|
|
753
|
-
handlerCalls:
|
|
1330
|
+
code: z3.string().optional(),
|
|
1331
|
+
data: z3.unknown().optional(),
|
|
1332
|
+
diagnostic: z3.string().optional(),
|
|
1333
|
+
handlerCalls: z3.number().int().nonnegative().optional()
|
|
754
1334
|
};
|
|
755
|
-
var RealtimeDisconnectObservationSchema =
|
|
756
|
-
phase:
|
|
1335
|
+
var RealtimeDisconnectObservationSchema = z3.object({
|
|
1336
|
+
phase: z3.enum(["before-invoke", "in-flight"])
|
|
757
1337
|
});
|
|
758
|
-
var TransportObservationSchema =
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
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"),
|
|
765
1345
|
...ObservationFields,
|
|
766
1346
|
rejection: RealtimeRejectionObservationSchema
|
|
767
1347
|
}),
|
|
768
|
-
|
|
769
|
-
outcome:
|
|
1348
|
+
z3.object({
|
|
1349
|
+
outcome: z3.literal("disconnected"),
|
|
770
1350
|
...ObservationFields,
|
|
771
1351
|
disconnect: RealtimeDisconnectObservationSchema
|
|
772
1352
|
}),
|
|
773
|
-
|
|
1353
|
+
z3.object({ outcome: z3.literal("timeout"), ...ObservationFields })
|
|
774
1354
|
]);
|
|
775
1355
|
function scenarioOutcome(scenario) {
|
|
776
1356
|
if (scenario === "event" || scenario === "acknowledgement")
|
|
@@ -1082,6 +1662,12 @@ function createHandlerTestClients(config) {
|
|
|
1082
1662
|
return createClients(config.contracts, createTestClientConfig(config), config.contractConfig);
|
|
1083
1663
|
}
|
|
1084
1664
|
export {
|
|
1665
|
+
ManagedResourceConformanceError,
|
|
1666
|
+
ManagedResourceConformancePhaseSchema,
|
|
1667
|
+
ManagedResourceConformanceScenarioIdSchema,
|
|
1668
|
+
ManagedResourceConformanceScenarioSchema,
|
|
1669
|
+
ManagedResourceConformanceTraceEntrySchema,
|
|
1670
|
+
ManagedResourceConformanceTraceOutcomeSchema,
|
|
1085
1671
|
RealtimeDisconnectObservationSchema,
|
|
1086
1672
|
RealtimeRejectionObservationSchema,
|
|
1087
1673
|
SurfaceManifestExtensionSchema,
|
|
@@ -1104,6 +1690,7 @@ export {
|
|
|
1104
1690
|
createRealtimeProbeDriver,
|
|
1105
1691
|
defineRealtimeProbe,
|
|
1106
1692
|
runAgentStoreConformance,
|
|
1693
|
+
runManagedResourceConformance,
|
|
1107
1694
|
runSurfaceProbes,
|
|
1108
1695
|
serializeSurfaceValue
|
|
1109
1696
|
};
|