@effect-agent/testing 0.1.0-beta.42 → 0.1.0-beta.45

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/package.json CHANGED
@@ -1,71 +1 @@
1
- {
2
- "name": "@effect-agent/testing",
3
- "version": "0.1.0-beta.42",
4
- "exports": {
5
- ".": {
6
- "types": "./dist/index.d.mts",
7
- "default": "./dist/index.mjs"
8
- },
9
- "./certification": {
10
- "types": "./dist/certification.d.mts",
11
- "default": "./dist/certification.mjs"
12
- },
13
- "./chaos": {
14
- "types": "./dist/chaos.d.mts",
15
- "default": "./dist/chaos.mjs"
16
- },
17
- "./code-executor": {
18
- "types": "./dist/code-executor.d.mts",
19
- "default": "./dist/code-executor.mjs"
20
- },
21
- "./fixtures/docs-researcher": {
22
- "types": "./dist/docs-researcher.d.mts",
23
- "default": "./dist/docs-researcher.mjs"
24
- },
25
- "./fixtures/travel-planner": {
26
- "types": "./dist/travel-planner.d.mts",
27
- "default": "./dist/travel-planner.mjs"
28
- }
29
- },
30
- "dependencies": {
31
- "@effect-agent/capabilities": "0.1.0-beta.42",
32
- "@effect-agent/core": "0.1.0-beta.42",
33
- "@effect-agent/engine": "0.1.0-beta.42",
34
- "@effect-agent/sandbox": "0.1.0-beta.42",
35
- "@effect-agent/thread": "0.1.0-beta.42"
36
- },
37
- "peerDependencies": {
38
- "effect": "^4.0.0-rc.111"
39
- },
40
- "description": "Scripted models, deterministic fixtures, and adapter conformance kits for testing Effect Agent applications.",
41
- "license": "MIT",
42
- "repository": {
43
- "type": "git",
44
- "url": "git+https://github.com/danieljvdm/effect-agent.git",
45
- "directory": "packages/testing"
46
- },
47
- "files": [
48
- "dist",
49
- "src"
50
- ],
51
- "type": "module",
52
- "publishConfig": {
53
- "access": "public"
54
- },
55
- "scripts": {
56
- "build": "vp pack",
57
- "check": "tsc --noEmit -p tsconfig.json",
58
- "test": "vp test --passWithNoTests"
59
- },
60
- "devDependencies": {
61
- "@effect-agent/platform-node": "0.1.0-beta.40",
62
- "@effect-agent/storage-memory": "0.1.0-beta.40",
63
- "@effect-agent/storage-sqlite": "0.1.0-beta.40",
64
- "@effect/platform-node": "4.0.0-rc.111",
65
- "@effect/sql-sqlite-node": "4.0.0-rc.111",
66
- "@effect/vitest": "4.0.0-rc.111",
67
- "effect": "4.0.0-rc.111",
68
- "typescript": "7.0.2",
69
- "vite-plus": "0.3.0"
70
- }
71
- }
1
+ {"name":"@effect-agent/testing","version":"0.1.0-beta.45","dependencies":{"@effect-agent/capabilities":"0.1.0-beta.45","@effect-agent/core":"0.1.0-beta.45","@effect-agent/engine":"0.1.0-beta.45","@effect-agent/sandbox":"0.1.0-beta.45","@effect-agent/thread":"0.1.0-beta.45"},"devDependencies":{"@effect-agent/platform-node":"0.1.0-beta.45","@effect-agent/storage-memory":"0.1.0-beta.45","@effect-agent/storage-sqlite":"0.1.0-beta.45","@effect/platform-node":"4.0.0-rc.112","@effect/sql-sqlite-node":"4.0.0-rc.112","@effect/vitest":"4.0.0-rc.112","effect":"4.0.0-rc.112","typescript":"7.0.2","vite-plus":"0.3.0"},"peerDependencies":{"effect":"^4.0.0-rc.112"},"exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./certification":{"types":"./dist/certification.d.mts","default":"./dist/certification.mjs"},"./chaos":{"types":"./dist/chaos.d.mts","default":"./dist/chaos.mjs"},"./code-executor":{"types":"./dist/code-executor.d.mts","default":"./dist/code-executor.mjs"},"./fixtures/docs-researcher":{"types":"./dist/docs-researcher.d.mts","default":"./dist/docs-researcher.mjs"},"./fixtures/travel-planner":{"types":"./dist/travel-planner.d.mts","default":"./dist/travel-planner.mjs"}},"description":"Scripted models, deterministic fixtures, and adapter conformance kits for testing Effect Agent applications.","license":"MIT","repository":{"type":"git","url":"git+https://github.com/danieljvdm/effect-agent.git","directory":"packages/testing"},"files":["dist","src"],"type":"module","publishConfig":{"access":"public"},"scripts":{"build":"vp pack","check":"tsc --noEmit -p tsconfig.json","test":"vp test --passWithNoTests"}}
@@ -15,7 +15,7 @@ import {
15
15
  type AgentId,
16
16
  type SubmissionId,
17
17
  } from "@effect-agent/core";
18
- import { DurableStep, DurableStepError } from "@effect-agent/engine";
18
+ import { DurableStep, DurableStepError, RunToolAuthorization } from "@effect-agent/engine";
19
19
  import {
20
20
  ApprovalDecisionCommand,
21
21
  ThreadExportRequest,
@@ -184,9 +184,11 @@ export const tier2NeverFiredLocations = (
184
184
  tier2: ReadonlyArray<CertificationSweepResult>,
185
185
  ): ReadonlyArray<DurableRuntimeFailpointLocation> => {
186
186
  const fired = new Set<DurableRuntimeFailpointLocation>();
187
+
187
188
  for (const row of tier2) {
188
189
  if (row.failpointFired) fired.add(row.location);
189
190
  }
191
+
190
192
  return DurableRuntimeFailpointLocation.literals.filter((location) => !fired.has(location)).sort();
191
193
  };
192
194
 
@@ -196,16 +198,19 @@ export const tier2NeverFiredLocations = (
196
198
 
197
199
  const SHA_A = Schema.decodeSync(Digest)("a".repeat(64));
198
200
  const DIGESTS = DefinitionDigests.make({ agent: SHA_A, model: SHA_A, tools: SHA_A });
201
+
199
202
  const CHILD_DIGEST_STRINGS = {
200
203
  agent: "b".repeat(64),
201
204
  model: "c".repeat(64),
202
205
  tools: "d".repeat(64),
203
206
  } as const;
207
+
204
208
  const CHILD_DIGESTS = DefinitionDigests.make({
205
209
  agent: Schema.decodeSync(Digest)(CHILD_DIGEST_STRINGS.agent),
206
210
  model: Schema.decodeSync(Digest)(CHILD_DIGEST_STRINGS.model),
207
211
  tools: Schema.decodeSync(Digest)(CHILD_DIGEST_STRINGS.tools),
208
212
  });
213
+
209
214
  const PRINCIPAL = Schema.decodeSync(Principal)("principal-certification");
210
215
  const decodeThreadId = Schema.decodeSync(ThreadId);
211
216
  const decodeIdempotencyKey = Schema.decodeSync(IdempotencyKey);
@@ -257,8 +262,10 @@ const promptShapeModel = (
257
262
  generateText: () => Effect.succeed([]),
258
263
  streamText: (request) => {
259
264
  const hasToolResult = request.prompt.content.some((message) => message.role === "tool");
265
+
260
266
  const parts =
261
267
  toolParts === undefined || hasToolResult ? finalParts(finalText) : toolParts;
268
+
262
269
  return Stream.fromIterable(parts);
263
270
  },
264
271
  }),
@@ -295,7 +302,9 @@ const Book = Tool.make("book", {
295
302
  parameters: Schema.Struct({ ref: Schema.String }),
296
303
  success: Schema.Struct({ confirmation: Schema.String }),
297
304
  });
305
+
298
306
  const bookToolkit = Toolkit.make(Book);
307
+
299
308
  const uncertainDefinition = Agent.make("certify-uncertain", {
300
309
  input: QuestionInput,
301
310
  output: AnswerOutput,
@@ -311,7 +320,9 @@ const Itinerary = Tool.make("itinerary", {
311
320
  failure: DurableStepError,
312
321
  dependencies: [DurableStep],
313
322
  });
323
+
314
324
  const itineraryToolkit = Toolkit.make(Itinerary);
325
+
315
326
  const stepsDefinition = Agent.make("certify-steps", {
316
327
  input: QuestionInput,
317
328
  output: AnswerOutput,
@@ -326,7 +337,9 @@ const BookApproval = Tool.make("book", {
326
337
  success: Schema.Struct({ confirmation: Schema.String }),
327
338
  needsApproval: true,
328
339
  });
340
+
329
341
  const approvalToolkit = Toolkit.make(BookApproval);
342
+
330
343
  const approvalDefinition = Agent.make("certify-approval", {
331
344
  input: QuestionInput,
332
345
  output: AnswerOutput,
@@ -399,10 +412,12 @@ const identifiers = Layer.effect(
399
412
  IdGenerator,
400
413
  Effect.gen(function* () {
401
414
  const counter = yield* Ref.make(0);
415
+
402
416
  const next = <A>(decode: (value: string) => A, prefix: string) =>
403
417
  Ref.getAndUpdate(counter, (value) => value + 1).pipe(
404
418
  Effect.map((value) => decode(`${prefix}-${value}`)),
405
419
  );
420
+
406
421
  return {
407
422
  nextThreadId: next(decodeThreadId, "certify-fixture-thread"),
408
423
  nextRunId: next(Schema.decodeSync(RunId), "certify-fixture-run"),
@@ -439,15 +454,19 @@ const makeSingleAgentCell = (
439
454
  slug: string,
440
455
  ): CertificationCell => {
441
456
  const threadId = decodeThreadId(`certify-${slug}`);
457
+
442
458
  const submit = Effect.gen(function* () {
443
459
  const runtime = yield* DurableAgentRuntime;
460
+
444
461
  const receipt = yield* runtime.submit(
445
462
  { definition: { id: definition.id, input: definition.input } },
446
463
  { question: `certify ${slug}` },
447
464
  submitOptionsFor(slug, threadId),
448
465
  );
466
+
449
467
  return [receipt];
450
468
  });
469
+
451
470
  return {
452
471
  bindings: [resolved],
453
472
  submit,
@@ -465,7 +484,9 @@ const makeCell = Effect.fn("Certification.makeCell")(function* (
465
484
  plainDefinition,
466
485
  promptShapeModel("certify-plain", '{"answer":"done"}'),
467
486
  );
487
+
468
488
  const resolved = yield* DurableWorkerBinding.make(binding, DIGESTS);
489
+
469
490
  return makeSingleAgentCell(plainDefinition, resolved, slug);
470
491
  }
471
492
  case "uncertain-tool": {
@@ -477,12 +498,15 @@ const makeCell = Effect.fn("Certification.makeCell")(function* (
477
498
  toolTurn(toolCallPart("book-1", "book", { ref: `r-${slug}` })),
478
499
  ),
479
500
  );
501
+
480
502
  const toolLayer = bookToolkit.toLayer({
481
503
  book: ({ ref }) => Effect.succeed({ confirmation: `confirmed-${ref}` }),
482
504
  });
505
+
483
506
  const resolved = yield* DurableWorkerBinding.make(binding, DIGESTS).pipe(
484
507
  Effect.provide(toolLayer),
485
508
  );
509
+
486
510
  return makeSingleAgentCell(uncertainDefinition, resolved, slug);
487
511
  }
488
512
  case "durable-steps": {
@@ -494,26 +518,32 @@ const makeCell = Effect.fn("Certification.makeCell")(function* (
494
518
  toolTurn(toolCallPart("itinerary-1", "itinerary", { ref: `trip-${slug}` })),
495
519
  ),
496
520
  );
521
+
497
522
  const toolLayer = itineraryToolkit.toLayer({
498
523
  itinerary: ({ ref }) =>
499
524
  Effect.gen(function* () {
500
525
  const step = yield* DurableStep;
526
+
501
527
  const flight = yield* step.do(
502
528
  "reserve-flight",
503
529
  Schema.String,
504
530
  Effect.succeed(`flight-${ref}`),
505
531
  );
532
+
506
533
  const lodging = yield* step.do(
507
534
  "reserve-lodging",
508
535
  Schema.String,
509
536
  Effect.succeed(`lodging-${ref}`),
510
537
  );
538
+
511
539
  return { state: `${flight}+${lodging}` };
512
540
  }),
513
541
  });
542
+
514
543
  const resolved = yield* DurableWorkerBinding.make(binding, DIGESTS).pipe(
515
544
  Effect.provide(toolLayer),
516
545
  );
546
+
517
547
  return makeSingleAgentCell(stepsDefinition, resolved, slug);
518
548
  }
519
549
  case "approval": {
@@ -525,12 +555,15 @@ const makeCell = Effect.fn("Certification.makeCell")(function* (
525
555
  toolTurn(toolCallPart("book-1", "book", { ref: `r-${slug}` })),
526
556
  ),
527
557
  );
558
+
528
559
  const toolLayer = approvalToolkit.toLayer({
529
560
  book: ({ ref }) => Effect.succeed({ confirmation: `confirmed-${ref}` }),
530
561
  });
562
+
531
563
  const resolved = yield* DurableWorkerBinding.make(binding, DIGESTS).pipe(
532
564
  Effect.provide(toolLayer),
533
565
  );
566
+
534
567
  return makeSingleAgentCell(approvalDefinition, resolved, slug);
535
568
  }
536
569
  case "join": {
@@ -538,31 +571,39 @@ const makeCell = Effect.fn("Certification.makeCell")(function* (
538
571
  plainDefinition,
539
572
  promptShapeModel("certify-join", '{"answer":"host answer"}'),
540
573
  );
574
+
541
575
  const resolved = yield* DurableWorkerBinding.make(binding, DIGESTS);
542
576
  const threadId = decodeThreadId(`certify-${slug}`);
543
- const submitOne = (key: string, question: string) =>
544
- Effect.gen(function* () {
545
- const runtime = yield* DurableAgentRuntime;
546
- return yield* runtime.submit(
547
- { definition: { id: plainDefinition.id, input: plainDefinition.input } },
548
- { question },
549
- {
550
- threadId,
551
- principal: PRINCIPAL,
552
- idempotencyKey: decodeIdempotencyKey(key),
553
- definitions: DIGESTS,
554
- },
555
- );
556
- });
577
+
578
+ const submitOne = Effect.fn("Certification.submitOne")(function* (
579
+ key: string,
580
+ question: string,
581
+ ) {
582
+ const runtime = yield* DurableAgentRuntime;
583
+
584
+ return yield* runtime.submit(
585
+ { definition: { id: plainDefinition.id, input: plainDefinition.input } },
586
+ { question },
587
+ {
588
+ threadId,
589
+ principal: PRINCIPAL,
590
+ idempotencyKey: decodeIdempotencyKey(key),
591
+ definitions: DIGESTS,
592
+ },
593
+ );
594
+ });
595
+
557
596
  const cell: CertificationCell = {
558
597
  bindings: [resolved],
559
598
  submit: Effect.gen(function* () {
560
599
  const host = yield* submitOne(`certify-key-${slug}-host`, "host question");
561
600
  const queued = yield* submitOne(`certify-key-${slug}-queued`, "queued question");
601
+
562
602
  return [host, queued];
563
603
  }),
564
604
  lanes: () => [threadId],
565
605
  };
606
+
566
607
  return cell;
567
608
  }
568
609
  case "delegation": {
@@ -570,6 +611,7 @@ const makeCell = Effect.fn("Certification.makeCell")(function* (
570
611
  childDefinition,
571
612
  promptShapeModel("certify-child", '{"answer":"child-answer"}'),
572
613
  );
614
+
573
615
  const parentBinding = Agent.withModel(
574
616
  coordinatorDefinition,
575
617
  promptShapeModel(
@@ -581,36 +623,45 @@ const makeCell = Effect.fn("Certification.makeCell")(function* (
581
623
  ),
582
624
  ),
583
625
  );
626
+
584
627
  const delegationLayer = SubagentRuntime.layer(researchDelegation, childBinding, {
585
628
  mapChildFailure,
586
629
  durable: { targetDigests: CHILD_DIGEST_STRINGS },
587
630
  }).pipe(Layer.provide(delegationSupport));
631
+
588
632
  const lookupLayer = Toolkit.make(Lookup).toLayer({
589
633
  lookup: ({ key }) => Effect.succeed({ value: `found-${key}` }),
590
634
  });
635
+
591
636
  const parentResolved = yield* DurableWorkerBinding.make(parentBinding, DIGESTS).pipe(
592
637
  Effect.provide(Layer.mergeAll(delegationLayer, lookupLayer)),
593
638
  );
639
+
594
640
  const childResolved = yield* DurableWorkerBinding.make(childBinding, CHILD_DIGESTS);
595
641
  const threadId = decodeThreadId(`certify-${slug}`);
642
+
596
643
  const cell: CertificationCell = {
597
644
  bindings: [parentResolved, childResolved],
598
645
  submit: Effect.gen(function* () {
599
646
  const runtime = yield* DurableAgentRuntime;
647
+
600
648
  const receipt = yield* runtime.submit(
601
649
  { definition: { id: coordinatorDefinition.id, input: coordinatorDefinition.input } },
602
650
  { mission: "plan" },
603
651
  submitOptionsFor(slug, threadId),
604
652
  );
653
+
605
654
  return [receipt];
606
655
  }),
607
656
  lanes: (receipts) => {
608
657
  const parent = receipts.at(0);
658
+
609
659
  return parent === undefined
610
660
  ? [threadId]
611
661
  : [threadId, childThreadIdFor(parent.submissionId, DELEGATE_CALL)];
612
662
  },
613
663
  };
664
+
614
665
  return cell;
615
666
  }
616
667
  }
@@ -634,12 +685,15 @@ const verifyLane = Effect.fn("Certification.verifyLane")(function* (
634
685
  const exported = yield* store.export(ThreadExportRequest.make({ threadId: lane }));
635
686
  const rows = new Map<SubmissionId, SubmissionSnapshot>();
636
687
  const nonterminal = yield* Stream.runCollect(ledger.scanNonterminal);
688
+
637
689
  for (const submission of nonterminal) {
638
690
  if (submission.threadId === lane) rows.set(submission.submissionId, submission);
639
691
  }
640
692
  const named = new Set<SubmissionId>();
693
+
641
694
  for (const envelope of exported.records) {
642
695
  const payload = envelope.record.payload;
696
+
643
697
  if (
644
698
  payload._tag === "UserInputRecorded" ||
645
699
  payload._tag === "SubmissionSettled" ||
@@ -651,14 +705,17 @@ const verifyLane = Effect.fn("Certification.verifyLane")(function* (
651
705
  for (const submissionId of named) {
652
706
  if (rows.has(submissionId)) continue;
653
707
  const found = yield* ledger.lookup(SubmissionLookupById.make({ submissionId }));
708
+
654
709
  if (Option.isSome(found) && found.value.threadId === lane) {
655
710
  rows.set(submissionId, found.value);
656
711
  }
657
712
  }
713
+
658
714
  const checkpoint =
659
715
  store.checkpoints === undefined
660
716
  ? Option.none()
661
717
  : yield* store.checkpoints.load(LoadCheckpointRequest.make({ threadId: lane }));
718
+
662
719
  return yield* verifyThreadInvariants({
663
720
  export: exported,
664
721
  submissions: [...rows.values()],
@@ -671,13 +728,17 @@ const verifyLane = Effect.fn("Certification.verifyLane")(function* (
671
728
 
672
729
  const failureTagOf = <E>(cause: Cause.Cause<E>): string => {
673
730
  const failure = Cause.findErrorOption(cause);
731
+
674
732
  if (Option.isSome(failure)) {
675
733
  const error: unknown = failure.value;
734
+
676
735
  if (typeof error === "object" && error !== null && "_tag" in error) {
677
736
  return String(error._tag);
678
737
  }
738
+
679
739
  return String(error).slice(0, 256);
680
740
  }
741
+
681
742
  return "defect";
682
743
  };
683
744
 
@@ -688,7 +749,6 @@ const runSweepCell = Effect.fn("Certification.runSweepCell")(function* (
688
749
  batchProducers: ReadonlyMap<BatchId, ProducerId>,
689
750
  leaseAdvance: Duration.Duration,
690
751
  ) {
691
- const runtime = yield* DurableAgentRuntime;
692
752
  const ledger = yield* SubmissionLedger;
693
753
  const control = yield* DurableRuntimeFailpointTestControl;
694
754
  const slug = `${scenario}-${location.replaceAll(":", "-")}`;
@@ -705,9 +765,20 @@ const runSweepCell = Effect.fn("Certification.runSweepCell")(function* (
705
765
 
706
766
  const cell = yield* makeCell(scenario, slug);
707
767
 
768
+ const runtime = yield* DurableAgentRuntime.pipe(
769
+ Effect.provide(
770
+ DurableAgentRuntime.layerWithBindings(cell.bindings).pipe(
771
+ Layer.provide(RunToolAuthorization.allowAll),
772
+ ),
773
+ ),
774
+ );
775
+
776
+ const submit = cell.submit.pipe(Effect.provideService(DurableAgentRuntime, runtime));
777
+
708
778
  // One-shot arm: the fault fires at most once anywhere in the cell (initial drive OR a
709
779
  // re-drive round's public unblocking operation), modelling one crash at this boundary.
710
780
  const fired = yield* Ref.make(false);
781
+
711
782
  yield* control.setHandler((hit) =>
712
783
  hit !== location
713
784
  ? Effect.void
@@ -722,13 +793,16 @@ const runSweepCell = Effect.fn("Certification.runSweepCell")(function* (
722
793
 
723
794
  // Submissions are idempotent (DUR-001): one replay recovers a submit-boundary fault.
724
795
  let receipts: ReadonlyArray<Receipt>;
725
- const firstSubmit = yield* Effect.exit(cell.submit);
796
+ const firstSubmit = yield* Effect.exit(submit);
797
+
726
798
  if (Exit.isSuccess(firstSubmit)) {
727
799
  receipts = firstSubmit.value;
728
800
  } else {
729
- const secondSubmit = yield* Effect.exit(cell.submit);
801
+ const secondSubmit = yield* Effect.exit(submit);
802
+
730
803
  if (Exit.isFailure(secondSubmit)) {
731
804
  yield* control.clear;
805
+
732
806
  return failed(
733
807
  `submission replay did not recover: ${failureTagOf(secondSubmit.cause)}`,
734
808
  yield* Ref.get(fired),
@@ -738,21 +812,24 @@ const runSweepCell = Effect.fn("Certification.runSweepCell")(function* (
738
812
  }
739
813
  const lanes = cell.lanes(receipts);
740
814
 
741
- const driveLane = (lane: ThreadId) => runtime.processThreadResolved(lane, cell.bindings);
815
+ const driveLane = (lane: ThreadId) => runtime.processThreadResolved(lane);
742
816
 
743
817
  const allSettled = Effect.gen(function* () {
744
818
  for (const receipt of receipts) {
745
819
  const snapshot = yield* ledger.lookup(
746
820
  SubmissionLookupById.make({ submissionId: receipt.submissionId }),
747
821
  );
822
+
748
823
  if (Option.isNone(snapshot) || snapshot.value.state !== "settled") return false;
749
824
  }
825
+
750
826
  return true;
751
827
  });
752
828
 
753
829
  // Re-drive to convergence using ONLY public operations: worker drives, recovery passes,
754
830
  // and the authorized DUR-017/approval unblocking paths chosen from `explainThread`.
755
831
  let converged = false;
832
+
756
833
  for (let round = 0; round < MAX_REDRIVE_ROUNDS && !converged; round++) {
757
834
  // Expire any lease a faulted Attempt left behind (D5): virtual time is the
758
835
  // adapter-neutral reclaim lever — a live lease may block every new claim.
@@ -763,6 +840,7 @@ const runSweepCell = Effect.fn("Certification.runSweepCell")(function* (
763
840
  }
764
841
  for (const lane of lanes) {
765
842
  const explains = yield* Effect.exit(runtime.explainThread(lane));
843
+
766
844
  if (Exit.isFailure(explains)) continue;
767
845
  for (const explanation of explains.value) {
768
846
  for (const unknown of explanation.evidence.unknownCalls) {
@@ -783,6 +861,7 @@ const runSweepCell = Effect.fn("Certification.runSweepCell")(function* (
783
861
  const decided = explanation.evidence.approvalDecisions.some(
784
862
  (decision) => decision.toolCallId === pending.toolCallId,
785
863
  );
864
+
786
865
  if (decided) continue;
787
866
  yield* Effect.exit(
788
867
  runtime.resolveApproval(
@@ -799,6 +878,7 @@ const runSweepCell = Effect.fn("Certification.runSweepCell")(function* (
799
878
  }
800
879
  }
801
880
  const settled = yield* Effect.exit(allSettled);
881
+
802
882
  converged = Exit.isSuccess(settled) && settled.value;
803
883
  }
804
884
  yield* control.clear;
@@ -811,8 +891,10 @@ const runSweepCell = Effect.fn("Certification.runSweepCell")(function* (
811
891
  // Every lane of the cell must verify in convergence mode with a recomputed digest chain.
812
892
  let digestChainVerified = true;
813
893
  const failedChecks: Array<string> = [];
894
+
814
895
  for (const lane of lanes) {
815
896
  const verdict = yield* Effect.exit(verifyLane(lane, batchProducers));
897
+
816
898
  if (Exit.isFailure(verdict)) {
817
899
  return failed(`lane ${lane} could not be verified: ${failureTagOf(verdict.cause)}`, wasFired);
818
900
  }
@@ -873,6 +955,7 @@ export const resolveTierThree = Effect.fn("Certification.resolveTierThree")(func
873
955
  }
874
956
  if (options.crashLever !== undefined) {
875
957
  const cases = yield* options.crashLever;
958
+
876
959
  return CertificationTierThreeReport.make({
877
960
  status: cases.length === 0 ? "not-exercised" : "exercised",
878
961
  evidence: options.tierThreeEvidence ?? [],
@@ -889,6 +972,7 @@ export const resolveTierThree = Effect.fn("Certification.resolveTierThree")(func
889
972
  cases: [],
890
973
  });
891
974
  }
975
+
892
976
  return CertificationTierThreeReport.make({
893
977
  status: "not-exercised",
894
978
  evidence: [],
@@ -917,12 +1001,14 @@ export const certifyDurableAdapters = <LedgerE = never, StoreE = never>(
917
1001
  options: CertifyDurableAdaptersOptions<LedgerE, StoreE>,
918
1002
  ): Effect.Effect<CertificationReport, LedgerE | StoreE, Crypto.Crypto> => {
919
1003
  const batchProducers = new Map<BatchId, ProducerId>();
1004
+
920
1005
  // Interpose the candidate store with an append-time capture of each batch's producer
921
1006
  // identity — the one value the ThreadStore port deliberately does not export — so
922
1007
  // Tier 2's invariant verification recomputes the FULL digest chain instead of skipping it.
923
1008
  const capturingStore = Layer.effect(ThreadStore)(
924
1009
  Effect.gen(function* () {
925
1010
  const inner = yield* ThreadStore;
1011
+
926
1012
  return ThreadStore.of({
927
1013
  ...inner,
928
1014
  append: (request) =>
@@ -933,7 +1019,9 @@ export const certifyDurableAdapters = <LedgerE = never, StoreE = never>(
933
1019
  }),
934
1020
  ).pipe(Layer.provide(options.threadStore));
935
1021
 
936
- const support = Layer.mergeAll(
1022
+ // RUN-036: certification uses the default-none Tool failure observer. Trusted application
1023
+ // reporting adds no durable transition and is verified separately from adapter certification.
1024
+ const environment = Layer.mergeAll(
937
1025
  options.submissionLedger,
938
1026
  capturingStore,
939
1027
  options.wakeScheduler ?? WakeScheduler.layerNoop,
@@ -947,9 +1035,6 @@ export const certifyDurableAdapters = <LedgerE = never, StoreE = never>(
947
1035
  abortPollInterval: Duration.millis(50),
948
1036
  }),
949
1037
  );
950
- // RUN-036: certification uses the default-none Tool failure observer. Trusted application
951
- // reporting adds no durable transition and is verified separately from adapter certification.
952
- const environment = DurableAgentRuntime.layer.pipe(Layer.provideMerge(support));
953
1038
 
954
1039
  const leaseAdvance = Duration.millis(
955
1040
  Duration.toMillis(options.ownershipLeaseDuration ?? DEFAULT_OWNERSHIP_LEASE_DURATION) + 1_000,
@@ -960,6 +1045,7 @@ export const certifyDurableAdapters = <LedgerE = never, StoreE = never>(
960
1045
 
961
1046
  // Tier 2 — coordinator failpoint convergence sweep.
962
1047
  const tier2: Array<CertificationSweepResult> = [];
1048
+
963
1049
  for (const scenario of CERTIFICATION_SCENARIOS) {
964
1050
  for (const location of DurableRuntimeFailpointLocation.literals) {
965
1051
  tier2.push(yield* runSweepCell(scenario, location, batchProducers, leaseAdvance));
@@ -974,6 +1060,7 @@ export const certifyDurableAdapters = <LedgerE = never, StoreE = never>(
974
1060
  const tier3 = yield* resolveTierThree(capabilities.durability, options);
975
1061
 
976
1062
  const generatedAt = yield* nowUtc;
1063
+
977
1064
  const ok =
978
1065
  tier1.every((result) => result.status === "passed") &&
979
1066
  tier2.every((result) => result.status !== "failed") &&