@effect-agent/testing 0.1.0-beta.38 → 0.1.0-beta.40
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/dist/certification.d.mts +102 -0
- package/dist/certification.mjs +561 -0
- package/dist/certification.mjs.map +1 -0
- package/dist/chaos.d.mts +124 -0
- package/dist/chaos.mjs +739 -0
- package/dist/chaos.mjs.map +1 -0
- package/dist/code-executor.d.mts +48 -0
- package/dist/code-executor.mjs +565 -0
- package/dist/code-executor.mjs.map +1 -0
- package/dist/deterministic-layers-CKyYxBhN.mjs +354 -0
- package/dist/deterministic-layers-CKyYxBhN.mjs.map +1 -0
- package/dist/docs-researcher.d.mts +254 -0
- package/dist/docs-researcher.mjs +479 -0
- package/dist/docs-researcher.mjs.map +1 -0
- package/dist/index.d.mts +2 -2947
- package/dist/index.mjs +2 -4913
- package/dist/scripted-model-C2y0ztuj.mjs +167 -0
- package/dist/scripted-model-C2y0ztuj.mjs.map +1 -0
- package/dist/scripted-model-dOa_e0-n.d.mts +677 -0
- package/dist/travel-planner.d.mts +1767 -0
- package/dist/travel-planner.mjs +2103 -0
- package/dist/travel-planner.mjs.map +1 -0
- package/package.json +34 -11
- package/src/certification.ts +71 -61
- package/src/chaos.ts +58 -71
- package/src/code-executor.ts +3 -0
- package/src/docs-researcher.ts +2 -0
- package/src/fixtures/docs-researcher/definition.ts +7 -7
- package/src/fixtures/docs-researcher/harness.ts +12 -14
- package/src/fixtures/docs-researcher/index.ts +1 -1
- package/src/fixtures/travel-planner/definition.ts +1 -1
- package/src/fixtures/travel-planner/deterministic-layers.ts +7 -4
- package/src/fixtures/travel-planner/phase2.ts +1 -1
- package/src/fixtures/travel-planner/phase3.ts +14 -18
- package/src/fixtures/travel-planner/phase4.ts +7 -7
- package/src/fixtures/travel-planner/phase5.ts +5 -5
- package/src/fixtures/travel-planner/phase6.ts +30 -29
- package/src/fixtures/travel-planner/subagents-durable.ts +6 -6
- package/src/fixtures/travel-planner/subagents.ts +4 -4
- package/src/index.ts +1 -10
- package/src/travel-planner.ts +2 -0
- package/dist/index.mjs.map +0 -1
- package/src/code-executor-conformance.d.ts +0 -30
- package/src/fixtures/warehouse/index.ts +0 -412
package/src/certification.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
import {
|
|
8
8
|
Agent,
|
|
9
9
|
AgentPolicy,
|
|
10
|
-
|
|
10
|
+
ThreadId,
|
|
11
11
|
IdGenerator,
|
|
12
12
|
RunId,
|
|
13
13
|
ToolCallId,
|
|
@@ -17,19 +17,16 @@ import {
|
|
|
17
17
|
} from "@effect-agent/core";
|
|
18
18
|
import { DurableStep, DurableStepError } from "@effect-agent/engine";
|
|
19
19
|
import {
|
|
20
|
-
AgentBindingResolver,
|
|
21
20
|
ApprovalDecisionCommand,
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
ThreadExportRequest,
|
|
22
|
+
ThreadStore,
|
|
24
23
|
DefinitionDigests,
|
|
25
24
|
DeploymentId,
|
|
26
25
|
Digest,
|
|
27
26
|
DurableAgentRuntime,
|
|
28
27
|
DurableRuntimeConfig,
|
|
29
|
-
DurableRuntimeFailpoint,
|
|
30
28
|
DurableRuntimeFailpointError,
|
|
31
29
|
DurableRuntimeFailpointLocation,
|
|
32
|
-
DurableRuntimeFailpointTestControl,
|
|
33
30
|
DurableWorkerBinding,
|
|
34
31
|
IdempotencyKey,
|
|
35
32
|
LoadCheckpointRequest,
|
|
@@ -42,16 +39,17 @@ import {
|
|
|
42
39
|
UnknownResolutionCommand,
|
|
43
40
|
WakeScheduler,
|
|
44
41
|
DEFAULT_OWNERSHIP_LEASE_DURATION,
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
childThreadIdFor,
|
|
43
|
+
verifyThreadInvariants,
|
|
47
44
|
type BatchId,
|
|
48
45
|
type DurableSubmitFailure,
|
|
49
46
|
type DurableSubmitOptions,
|
|
50
47
|
type Receipt,
|
|
51
48
|
type ResolvedBinding,
|
|
52
49
|
type SubmissionSnapshot,
|
|
53
|
-
} from "@effect-agent/
|
|
50
|
+
} from "@effect-agent/thread";
|
|
54
51
|
import {
|
|
52
|
+
DurableRuntimeFailpointTestControl,
|
|
55
53
|
type CertificationCaseResult,
|
|
56
54
|
CertificationReport,
|
|
57
55
|
CertificationSweepResult,
|
|
@@ -59,7 +57,7 @@ import {
|
|
|
59
57
|
CertifiedAdapterIdentity,
|
|
60
58
|
certifyPorts,
|
|
61
59
|
type CertificationScenario,
|
|
62
|
-
} from "@effect-agent/
|
|
60
|
+
} from "@effect-agent/thread/testing";
|
|
63
61
|
import type { Crypto } from "effect";
|
|
64
62
|
import {
|
|
65
63
|
Cause,
|
|
@@ -89,7 +87,7 @@ import { LanguageModel, Model, Tool, Toolkit, type Response } from "effect/unsta
|
|
|
89
87
|
* (plain / uncertain-tool / durable-steps / approval / join / delegation). After the injected
|
|
90
88
|
* fault the runner asserts the state stays CLASSIFIABLE (recovery + the public unblocking
|
|
91
89
|
* operations `resolveUnknown`/`resolveApproval` are the only levers used) and that the
|
|
92
|
-
* re-drive CONVERGES to `
|
|
90
|
+
* re-drive CONVERGES to `verifyThreadInvariants` with `requireAllSettled` — including a
|
|
93
91
|
* fully discharged digest-chain check, because the runner captures per-batch producer
|
|
94
92
|
* identity at append time.
|
|
95
93
|
* - **Tier 3 — real loss lever**: recorded honestly. A durable adapter either supplies a
|
|
@@ -128,7 +126,7 @@ export interface CertifyDurableAdaptersOptions<LedgerE = never, StoreE = never>
|
|
|
128
126
|
* does); the certification's own environment supplies nothing else.
|
|
129
127
|
*/
|
|
130
128
|
readonly submissionLedger: Layer.Layer<SubmissionLedger, LedgerE, Crypto.Crypto>;
|
|
131
|
-
readonly
|
|
129
|
+
readonly threadStore: Layer.Layer<ThreadStore, StoreE, Crypto.Crypto>;
|
|
132
130
|
/** Defaults to `WakeScheduler.layerNoop`; the runner re-drives lanes explicitly. */
|
|
133
131
|
readonly wakeScheduler?: Layer.Layer<WakeScheduler> | undefined;
|
|
134
132
|
/** Executes Tier 3 in this run; takes precedence over `tierThreeEvidence`. */
|
|
@@ -173,6 +171,10 @@ export const TIER2_UNREACHED_LOCATIONS: ReadonlyArray<DurableRuntimeFailpointLoc
|
|
|
173
171
|
// RUN-026 rows in `packages/testing/test/durable-runtime.test.ts`
|
|
174
172
|
// (compaction failpoint idempotence across re-drive).
|
|
175
173
|
"compaction:after-canonical-append",
|
|
174
|
+
// These shapes use neither programmatic Tools nor grace finalization. Their reservations
|
|
175
|
+
// are exercised before/after append in the public durable-runtime regression suite.
|
|
176
|
+
"policy:before-reservation-append",
|
|
177
|
+
"policy:after-reservation-append",
|
|
176
178
|
"resolve:after-intent",
|
|
177
179
|
"subagent:after-child-abort-intent",
|
|
178
180
|
];
|
|
@@ -205,7 +207,7 @@ const CHILD_DIGESTS = DefinitionDigests.make({
|
|
|
205
207
|
tools: Schema.decodeSync(Digest)(CHILD_DIGEST_STRINGS.tools),
|
|
206
208
|
});
|
|
207
209
|
const PRINCIPAL = Schema.decodeSync(Principal)("principal-certification");
|
|
208
|
-
const
|
|
210
|
+
const decodeThreadId = Schema.decodeSync(ThreadId);
|
|
209
211
|
const decodeIdempotencyKey = Schema.decodeSync(IdempotencyKey);
|
|
210
212
|
const decodeToolCallId = Schema.decodeSync(ToolCallId);
|
|
211
213
|
|
|
@@ -280,7 +282,7 @@ const QuestionInput = Schema.Struct({ question: Schema.String });
|
|
|
280
282
|
const AnswerOutput = Schema.Struct({ answer: Schema.String });
|
|
281
283
|
|
|
282
284
|
/** plain / join: no tools — the pure Turn/submission/join seams. */
|
|
283
|
-
const plainDefinition = Agent.
|
|
285
|
+
const plainDefinition = Agent.make("certify-plain", {
|
|
284
286
|
input: QuestionInput,
|
|
285
287
|
output: AnswerOutput,
|
|
286
288
|
instructions: "Answer as JSON.",
|
|
@@ -294,7 +296,7 @@ const Book = Tool.make("book", {
|
|
|
294
296
|
success: Schema.Struct({ confirmation: Schema.String }),
|
|
295
297
|
});
|
|
296
298
|
const bookToolkit = Toolkit.make(Book);
|
|
297
|
-
const uncertainDefinition = Agent.
|
|
299
|
+
const uncertainDefinition = Agent.make("certify-uncertain", {
|
|
298
300
|
input: QuestionInput,
|
|
299
301
|
output: AnswerOutput,
|
|
300
302
|
instructions: "Book it.",
|
|
@@ -310,7 +312,7 @@ const Itinerary = Tool.make("itinerary", {
|
|
|
310
312
|
dependencies: [DurableStep],
|
|
311
313
|
});
|
|
312
314
|
const itineraryToolkit = Toolkit.make(Itinerary);
|
|
313
|
-
const stepsDefinition = Agent.
|
|
315
|
+
const stepsDefinition = Agent.make("certify-steps", {
|
|
314
316
|
input: QuestionInput,
|
|
315
317
|
output: AnswerOutput,
|
|
316
318
|
instructions: "Reserve the itinerary.",
|
|
@@ -325,7 +327,7 @@ const BookApproval = Tool.make("book", {
|
|
|
325
327
|
needsApproval: true,
|
|
326
328
|
});
|
|
327
329
|
const approvalToolkit = Toolkit.make(BookApproval);
|
|
328
|
-
const approvalDefinition = Agent.
|
|
330
|
+
const approvalDefinition = Agent.make("certify-approval", {
|
|
329
331
|
input: QuestionInput,
|
|
330
332
|
output: AnswerOutput,
|
|
331
333
|
instructions: "Book after approval.",
|
|
@@ -334,7 +336,7 @@ const approvalDefinition = Agent.define("certify-approval", {
|
|
|
334
336
|
});
|
|
335
337
|
|
|
336
338
|
/** delegation: durable attached child plus an ordinary uncertain sibling in ONE batch. */
|
|
337
|
-
const childDefinition = Agent.
|
|
339
|
+
const childDefinition = Agent.make("certify-child", {
|
|
338
340
|
input: QuestionInput,
|
|
339
341
|
output: AnswerOutput,
|
|
340
342
|
instructions: "Answer as JSON.",
|
|
@@ -374,7 +376,7 @@ const Lookup = Tool.make("lookup", {
|
|
|
374
376
|
success: Schema.Struct({ value: Schema.String }),
|
|
375
377
|
});
|
|
376
378
|
|
|
377
|
-
const coordinatorDefinition = Agent.
|
|
379
|
+
const coordinatorDefinition = Agent.make("certify-coordinator", {
|
|
378
380
|
input: Schema.Struct({ mission: Schema.String }),
|
|
379
381
|
output: Schema.Struct({ report: Schema.String }),
|
|
380
382
|
instructions: "Delegate and look up, then answer as JSON.",
|
|
@@ -402,7 +404,7 @@ const identifiers = Layer.effect(
|
|
|
402
404
|
Effect.map((value) => decode(`${prefix}-${value}`)),
|
|
403
405
|
);
|
|
404
406
|
return {
|
|
405
|
-
|
|
407
|
+
nextThreadId: next(decodeThreadId, "certify-fixture-thread"),
|
|
406
408
|
nextRunId: next(Schema.decodeSync(RunId), "certify-fixture-run"),
|
|
407
409
|
nextTurnId: next(Schema.decodeSync(TurnId), "certify-fixture-turn"),
|
|
408
410
|
};
|
|
@@ -416,15 +418,15 @@ const delegationSupport = Layer.mergeAll(SubagentReservationsMemoryLive, identif
|
|
|
416
418
|
// ---------------------------------------------------------------------------
|
|
417
419
|
|
|
418
420
|
interface CertificationCell {
|
|
419
|
-
readonly
|
|
421
|
+
readonly bindings: ReadonlyArray<ResolvedBinding>;
|
|
420
422
|
/** Idempotent submission batch — safe to replay verbatim after a submit-boundary fault. */
|
|
421
423
|
readonly submit: Effect.Effect<ReadonlyArray<Receipt>, DurableSubmitFailure, DurableAgentRuntime>;
|
|
422
424
|
/** All lanes of the cell in drive order, computed from the (possibly replayed) receipts. */
|
|
423
|
-
readonly lanes: (receipts: ReadonlyArray<Receipt>) => ReadonlyArray<
|
|
425
|
+
readonly lanes: (receipts: ReadonlyArray<Receipt>) => ReadonlyArray<ThreadId>;
|
|
424
426
|
}
|
|
425
427
|
|
|
426
|
-
const submitOptionsFor = (slug: string,
|
|
427
|
-
|
|
428
|
+
const submitOptionsFor = (slug: string, threadId: ThreadId): DurableSubmitOptions => ({
|
|
429
|
+
threadId,
|
|
428
430
|
principal: PRINCIPAL,
|
|
429
431
|
idempotencyKey: decodeIdempotencyKey(`certify-key-${slug}`),
|
|
430
432
|
definitions: DIGESTS,
|
|
@@ -436,20 +438,20 @@ const makeSingleAgentCell = (
|
|
|
436
438
|
resolved: ResolvedBinding,
|
|
437
439
|
slug: string,
|
|
438
440
|
): CertificationCell => {
|
|
439
|
-
const
|
|
441
|
+
const threadId = decodeThreadId(`certify-${slug}`);
|
|
440
442
|
const submit = Effect.gen(function* () {
|
|
441
443
|
const runtime = yield* DurableAgentRuntime;
|
|
442
444
|
const receipt = yield* runtime.submit(
|
|
443
445
|
{ definition: { id: definition.id, input: definition.input } },
|
|
444
446
|
{ question: `certify ${slug}` },
|
|
445
|
-
submitOptionsFor(slug,
|
|
447
|
+
submitOptionsFor(slug, threadId),
|
|
446
448
|
);
|
|
447
449
|
return [receipt];
|
|
448
450
|
});
|
|
449
451
|
return {
|
|
450
|
-
|
|
452
|
+
bindings: [resolved],
|
|
451
453
|
submit,
|
|
452
|
-
lanes: () => [
|
|
454
|
+
lanes: () => [threadId],
|
|
453
455
|
};
|
|
454
456
|
};
|
|
455
457
|
|
|
@@ -537,7 +539,7 @@ const makeCell = Effect.fn("Certification.makeCell")(function* (
|
|
|
537
539
|
promptShapeModel("certify-join", '{"answer":"host answer"}'),
|
|
538
540
|
);
|
|
539
541
|
const resolved = yield* DurableWorkerBinding.make(binding, DIGESTS);
|
|
540
|
-
const
|
|
542
|
+
const threadId = decodeThreadId(`certify-${slug}`);
|
|
541
543
|
const submitOne = (key: string, question: string) =>
|
|
542
544
|
Effect.gen(function* () {
|
|
543
545
|
const runtime = yield* DurableAgentRuntime;
|
|
@@ -545,7 +547,7 @@ const makeCell = Effect.fn("Certification.makeCell")(function* (
|
|
|
545
547
|
{ definition: { id: plainDefinition.id, input: plainDefinition.input } },
|
|
546
548
|
{ question },
|
|
547
549
|
{
|
|
548
|
-
|
|
550
|
+
threadId,
|
|
549
551
|
principal: PRINCIPAL,
|
|
550
552
|
idempotencyKey: decodeIdempotencyKey(key),
|
|
551
553
|
definitions: DIGESTS,
|
|
@@ -553,13 +555,13 @@ const makeCell = Effect.fn("Certification.makeCell")(function* (
|
|
|
553
555
|
);
|
|
554
556
|
});
|
|
555
557
|
const cell: CertificationCell = {
|
|
556
|
-
|
|
558
|
+
bindings: [resolved],
|
|
557
559
|
submit: Effect.gen(function* () {
|
|
558
560
|
const host = yield* submitOne(`certify-key-${slug}-host`, "host question");
|
|
559
561
|
const queued = yield* submitOne(`certify-key-${slug}-queued`, "queued question");
|
|
560
562
|
return [host, queued];
|
|
561
563
|
}),
|
|
562
|
-
lanes: () => [
|
|
564
|
+
lanes: () => [threadId],
|
|
563
565
|
};
|
|
564
566
|
return cell;
|
|
565
567
|
}
|
|
@@ -590,23 +592,23 @@ const makeCell = Effect.fn("Certification.makeCell")(function* (
|
|
|
590
592
|
Effect.provide(Layer.mergeAll(delegationLayer, lookupLayer)),
|
|
591
593
|
);
|
|
592
594
|
const childResolved = yield* DurableWorkerBinding.make(childBinding, CHILD_DIGESTS);
|
|
593
|
-
const
|
|
595
|
+
const threadId = decodeThreadId(`certify-${slug}`);
|
|
594
596
|
const cell: CertificationCell = {
|
|
595
|
-
|
|
597
|
+
bindings: [parentResolved, childResolved],
|
|
596
598
|
submit: Effect.gen(function* () {
|
|
597
599
|
const runtime = yield* DurableAgentRuntime;
|
|
598
600
|
const receipt = yield* runtime.submit(
|
|
599
601
|
{ definition: { id: coordinatorDefinition.id, input: coordinatorDefinition.input } },
|
|
600
602
|
{ mission: "plan" },
|
|
601
|
-
submitOptionsFor(slug,
|
|
603
|
+
submitOptionsFor(slug, threadId),
|
|
602
604
|
);
|
|
603
605
|
return [receipt];
|
|
604
606
|
}),
|
|
605
607
|
lanes: (receipts) => {
|
|
606
608
|
const parent = receipts.at(0);
|
|
607
609
|
return parent === undefined
|
|
608
|
-
? [
|
|
609
|
-
: [
|
|
610
|
+
? [threadId]
|
|
611
|
+
: [threadId, childThreadIdFor(parent.submissionId, DELEGATE_CALL)];
|
|
610
612
|
},
|
|
611
613
|
};
|
|
612
614
|
return cell;
|
|
@@ -624,16 +626,16 @@ const MAX_REDRIVE_ROUNDS = 8;
|
|
|
624
626
|
* the digest chain is fully recomputed instead of skipped.
|
|
625
627
|
*/
|
|
626
628
|
const verifyLane = Effect.fn("Certification.verifyLane")(function* (
|
|
627
|
-
lane:
|
|
629
|
+
lane: ThreadId,
|
|
628
630
|
batchProducers: ReadonlyMap<BatchId, ProducerId>,
|
|
629
631
|
) {
|
|
630
|
-
const store = yield*
|
|
632
|
+
const store = yield* ThreadStore;
|
|
631
633
|
const ledger = yield* SubmissionLedger;
|
|
632
|
-
const exported = yield* store.export(
|
|
634
|
+
const exported = yield* store.export(ThreadExportRequest.make({ threadId: lane }));
|
|
633
635
|
const rows = new Map<SubmissionId, SubmissionSnapshot>();
|
|
634
636
|
const nonterminal = yield* Stream.runCollect(ledger.scanNonterminal);
|
|
635
637
|
for (const submission of nonterminal) {
|
|
636
|
-
if (submission.
|
|
638
|
+
if (submission.threadId === lane) rows.set(submission.submissionId, submission);
|
|
637
639
|
}
|
|
638
640
|
const named = new Set<SubmissionId>();
|
|
639
641
|
for (const envelope of exported.records) {
|
|
@@ -643,24 +645,26 @@ const verifyLane = Effect.fn("Certification.verifyLane")(function* (
|
|
|
643
645
|
payload._tag === "SubmissionSettled" ||
|
|
644
646
|
payload._tag === "AbortRequested"
|
|
645
647
|
) {
|
|
646
|
-
named.add(payload.submissionId);
|
|
648
|
+
if (payload.submissionId !== undefined) named.add(payload.submissionId);
|
|
647
649
|
}
|
|
648
650
|
}
|
|
649
651
|
for (const submissionId of named) {
|
|
650
652
|
if (rows.has(submissionId)) continue;
|
|
651
653
|
const found = yield* ledger.lookup(SubmissionLookupById.make({ submissionId }));
|
|
652
|
-
if (Option.isSome(found) && found.value.
|
|
654
|
+
if (Option.isSome(found) && found.value.threadId === lane) {
|
|
653
655
|
rows.set(submissionId, found.value);
|
|
654
656
|
}
|
|
655
657
|
}
|
|
656
|
-
const checkpoint =
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
658
|
+
const checkpoint =
|
|
659
|
+
store.checkpoints === undefined
|
|
660
|
+
? Option.none()
|
|
661
|
+
: yield* store.checkpoints.load(LoadCheckpointRequest.make({ threadId: lane }));
|
|
662
|
+
return yield* verifyThreadInvariants({
|
|
660
663
|
export: exported,
|
|
661
664
|
submissions: [...rows.values()],
|
|
662
665
|
batchProducers,
|
|
663
666
|
checkpoint: Option.getOrUndefined(checkpoint),
|
|
667
|
+
checkpointsSupported: store.checkpoints !== undefined,
|
|
664
668
|
requireAllSettled: true,
|
|
665
669
|
});
|
|
666
670
|
});
|
|
@@ -734,10 +738,7 @@ const runSweepCell = Effect.fn("Certification.runSweepCell")(function* (
|
|
|
734
738
|
}
|
|
735
739
|
const lanes = cell.lanes(receipts);
|
|
736
740
|
|
|
737
|
-
const driveLane = (lane:
|
|
738
|
-
runtime
|
|
739
|
-
.processConversationResolved(lane)
|
|
740
|
-
.pipe(Effect.provideService(AgentBindingResolver, cell.resolver));
|
|
741
|
+
const driveLane = (lane: ThreadId) => runtime.processThreadResolved(lane, cell.bindings);
|
|
741
742
|
|
|
742
743
|
const allSettled = Effect.gen(function* () {
|
|
743
744
|
for (const receipt of receipts) {
|
|
@@ -750,7 +751,7 @@ const runSweepCell = Effect.fn("Certification.runSweepCell")(function* (
|
|
|
750
751
|
});
|
|
751
752
|
|
|
752
753
|
// Re-drive to convergence using ONLY public operations: worker drives, recovery passes,
|
|
753
|
-
// and the authorized DUR-017/approval unblocking paths chosen from `
|
|
754
|
+
// and the authorized DUR-017/approval unblocking paths chosen from `explainThread`.
|
|
754
755
|
let converged = false;
|
|
755
756
|
for (let round = 0; round < MAX_REDRIVE_ROUNDS && !converged; round++) {
|
|
756
757
|
// Expire any lease a faulted Attempt left behind (D5): virtual time is the
|
|
@@ -761,7 +762,7 @@ const runSweepCell = Effect.fn("Certification.runSweepCell")(function* (
|
|
|
761
762
|
yield* Effect.exit(driveLane(lane));
|
|
762
763
|
}
|
|
763
764
|
for (const lane of lanes) {
|
|
764
|
-
const explains = yield* Effect.exit(runtime.
|
|
765
|
+
const explains = yield* Effect.exit(runtime.explainThread(lane));
|
|
765
766
|
if (Exit.isFailure(explains)) continue;
|
|
766
767
|
for (const explanation of explains.value) {
|
|
767
768
|
for (const unknown of explanation.evidence.unknownCalls) {
|
|
@@ -873,9 +874,12 @@ export const resolveTierThree = Effect.fn("Certification.resolveTierThree")(func
|
|
|
873
874
|
if (options.crashLever !== undefined) {
|
|
874
875
|
const cases = yield* options.crashLever;
|
|
875
876
|
return CertificationTierThreeReport.make({
|
|
876
|
-
status: "exercised",
|
|
877
|
+
status: cases.length === 0 ? "not-exercised" : "exercised",
|
|
877
878
|
evidence: options.tierThreeEvidence ?? [],
|
|
878
879
|
cases,
|
|
880
|
+
...(cases.length === 0
|
|
881
|
+
? { detail: "the supplied crash lever executed no real-loss cases" }
|
|
882
|
+
: {}),
|
|
879
883
|
});
|
|
880
884
|
}
|
|
881
885
|
if (options.tierThreeEvidence !== undefined && options.tierThreeEvidence.length > 0) {
|
|
@@ -914,12 +918,12 @@ export const certifyDurableAdapters = <LedgerE = never, StoreE = never>(
|
|
|
914
918
|
): Effect.Effect<CertificationReport, LedgerE | StoreE, Crypto.Crypto> => {
|
|
915
919
|
const batchProducers = new Map<BatchId, ProducerId>();
|
|
916
920
|
// Interpose the candidate store with an append-time capture of each batch's producer
|
|
917
|
-
// identity — the one value the
|
|
921
|
+
// identity — the one value the ThreadStore port deliberately does not export — so
|
|
918
922
|
// Tier 2's invariant verification recomputes the FULL digest chain instead of skipping it.
|
|
919
|
-
const capturingStore = Layer.effect(
|
|
923
|
+
const capturingStore = Layer.effect(ThreadStore)(
|
|
920
924
|
Effect.gen(function* () {
|
|
921
|
-
const inner = yield*
|
|
922
|
-
return
|
|
925
|
+
const inner = yield* ThreadStore;
|
|
926
|
+
return ThreadStore.of({
|
|
923
927
|
...inner,
|
|
924
928
|
append: (request) =>
|
|
925
929
|
Effect.sync(() => {
|
|
@@ -927,13 +931,13 @@ export const certifyDurableAdapters = <LedgerE = never, StoreE = never>(
|
|
|
927
931
|
}).pipe(Effect.andThen(inner.append(request))),
|
|
928
932
|
});
|
|
929
933
|
}),
|
|
930
|
-
).pipe(Layer.provide(options.
|
|
934
|
+
).pipe(Layer.provide(options.threadStore));
|
|
931
935
|
|
|
932
936
|
const support = Layer.mergeAll(
|
|
933
937
|
options.submissionLedger,
|
|
934
938
|
capturingStore,
|
|
935
939
|
options.wakeScheduler ?? WakeScheduler.layerNoop,
|
|
936
|
-
|
|
940
|
+
DurableRuntimeFailpointTestControl.layer,
|
|
937
941
|
ToolReconciler.uncertain,
|
|
938
942
|
DurableRuntimeConfig.layer({
|
|
939
943
|
deploymentId: Schema.decodeSync(DeploymentId)("deployment-certification"),
|
|
@@ -976,7 +980,7 @@ export const certifyDurableAdapters = <LedgerE = never, StoreE = never>(
|
|
|
976
980
|
tier3.cases.every((result) => result.status === "passed");
|
|
977
981
|
|
|
978
982
|
return CertificationReport.make({
|
|
979
|
-
format: "effect-agent/certification@
|
|
983
|
+
format: "effect-agent/certification@2",
|
|
980
984
|
adapter: CertifiedAdapterIdentity.make({
|
|
981
985
|
name: options.adapter.name,
|
|
982
986
|
...(options.adapter.version === undefined ? {} : { version: options.adapter.version }),
|
|
@@ -987,6 +991,12 @@ export const certifyDurableAdapters = <LedgerE = never, StoreE = never>(
|
|
|
987
991
|
tier2,
|
|
988
992
|
tier3,
|
|
989
993
|
ok,
|
|
994
|
+
fullyCertified:
|
|
995
|
+
ok &&
|
|
996
|
+
capabilities.durability !== "non-durable" &&
|
|
997
|
+
tier3.status === "exercised" &&
|
|
998
|
+
tier3.cases.length > 0 &&
|
|
999
|
+
tier3.cases.every((result) => result.suite === "real-loss"),
|
|
990
1000
|
});
|
|
991
1001
|
});
|
|
992
1002
|
|