@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.
Files changed (44) hide show
  1. package/dist/certification.d.mts +102 -0
  2. package/dist/certification.mjs +561 -0
  3. package/dist/certification.mjs.map +1 -0
  4. package/dist/chaos.d.mts +124 -0
  5. package/dist/chaos.mjs +739 -0
  6. package/dist/chaos.mjs.map +1 -0
  7. package/dist/code-executor.d.mts +48 -0
  8. package/dist/code-executor.mjs +565 -0
  9. package/dist/code-executor.mjs.map +1 -0
  10. package/dist/deterministic-layers-CKyYxBhN.mjs +354 -0
  11. package/dist/deterministic-layers-CKyYxBhN.mjs.map +1 -0
  12. package/dist/docs-researcher.d.mts +254 -0
  13. package/dist/docs-researcher.mjs +479 -0
  14. package/dist/docs-researcher.mjs.map +1 -0
  15. package/dist/index.d.mts +2 -2947
  16. package/dist/index.mjs +2 -4913
  17. package/dist/scripted-model-C2y0ztuj.mjs +167 -0
  18. package/dist/scripted-model-C2y0ztuj.mjs.map +1 -0
  19. package/dist/scripted-model-dOa_e0-n.d.mts +677 -0
  20. package/dist/travel-planner.d.mts +1767 -0
  21. package/dist/travel-planner.mjs +2103 -0
  22. package/dist/travel-planner.mjs.map +1 -0
  23. package/package.json +34 -11
  24. package/src/certification.ts +71 -61
  25. package/src/chaos.ts +58 -71
  26. package/src/code-executor.ts +3 -0
  27. package/src/docs-researcher.ts +2 -0
  28. package/src/fixtures/docs-researcher/definition.ts +7 -7
  29. package/src/fixtures/docs-researcher/harness.ts +12 -14
  30. package/src/fixtures/docs-researcher/index.ts +1 -1
  31. package/src/fixtures/travel-planner/definition.ts +1 -1
  32. package/src/fixtures/travel-planner/deterministic-layers.ts +7 -4
  33. package/src/fixtures/travel-planner/phase2.ts +1 -1
  34. package/src/fixtures/travel-planner/phase3.ts +14 -18
  35. package/src/fixtures/travel-planner/phase4.ts +7 -7
  36. package/src/fixtures/travel-planner/phase5.ts +5 -5
  37. package/src/fixtures/travel-planner/phase6.ts +30 -29
  38. package/src/fixtures/travel-planner/subagents-durable.ts +6 -6
  39. package/src/fixtures/travel-planner/subagents.ts +4 -4
  40. package/src/index.ts +1 -10
  41. package/src/travel-planner.ts +2 -0
  42. package/dist/index.mjs.map +0 -1
  43. package/src/code-executor-conformance.d.ts +0 -30
  44. package/src/fixtures/warehouse/index.ts +0 -412
@@ -7,7 +7,7 @@ import {
7
7
  import {
8
8
  Agent,
9
9
  AgentPolicy,
10
- ConversationId,
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
- ConversationExportRequest,
23
- ConversationStore,
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
- childConversationIdFor,
46
- verifyConversationInvariants,
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/session";
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/session/testing";
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 `verifyConversationInvariants` with `requireAllSettled` — including a
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 conversationStore: Layer.Layer<ConversationStore, StoreE, Crypto.Crypto>;
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 decodeConversationId = Schema.decodeSync(ConversationId);
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.define("certify-plain", {
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.define("certify-uncertain", {
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.define("certify-steps", {
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.define("certify-approval", {
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.define("certify-child", {
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.define("certify-coordinator", {
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
- nextConversationId: next(decodeConversationId, "certify-fixture-conversation"),
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 resolver: (typeof AgentBindingResolver)["Service"];
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<ConversationId>;
425
+ readonly lanes: (receipts: ReadonlyArray<Receipt>) => ReadonlyArray<ThreadId>;
424
426
  }
425
427
 
426
- const submitOptionsFor = (slug: string, conversationId: ConversationId): DurableSubmitOptions => ({
427
- conversationId,
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 conversationId = decodeConversationId(`certify-${slug}`);
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, conversationId),
447
+ submitOptionsFor(slug, threadId),
446
448
  );
447
449
  return [receipt];
448
450
  });
449
451
  return {
450
- resolver: AgentBindingResolver.fromBindings([resolved]),
452
+ bindings: [resolved],
451
453
  submit,
452
- lanes: () => [conversationId],
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 conversationId = decodeConversationId(`certify-${slug}`);
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
- conversationId,
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
- resolver: AgentBindingResolver.fromBindings([resolved]),
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: () => [conversationId],
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 conversationId = decodeConversationId(`certify-${slug}`);
595
+ const threadId = decodeThreadId(`certify-${slug}`);
594
596
  const cell: CertificationCell = {
595
- resolver: AgentBindingResolver.fromBindings([parentResolved, childResolved]),
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, conversationId),
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
- ? [conversationId]
609
- : [conversationId, childConversationIdFor(parent.submissionId, DELEGATE_CALL)];
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: ConversationId,
629
+ lane: ThreadId,
628
630
  batchProducers: ReadonlyMap<BatchId, ProducerId>,
629
631
  ) {
630
- const store = yield* ConversationStore;
632
+ const store = yield* ThreadStore;
631
633
  const ledger = yield* SubmissionLedger;
632
- const exported = yield* store.export(ConversationExportRequest.make({ conversationId: lane }));
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.conversationId === lane) rows.set(submission.submissionId, 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.conversationId === lane) {
654
+ if (Option.isSome(found) && found.value.threadId === lane) {
653
655
  rows.set(submissionId, found.value);
654
656
  }
655
657
  }
656
- const checkpoint = yield* store.loadCheckpoint(
657
- LoadCheckpointRequest.make({ conversationId: lane }),
658
- );
659
- return yield* verifyConversationInvariants({
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: ConversationId) =>
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 `explainConversation`.
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.explainConversation(lane));
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 ConversationStore port deliberately does not export — so
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(ConversationStore)(
923
+ const capturingStore = Layer.effect(ThreadStore)(
920
924
  Effect.gen(function* () {
921
- const inner = yield* ConversationStore;
922
- return ConversationStore.of({
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.conversationStore));
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
- DurableRuntimeFailpoint.layerTest,
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@1",
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