@effect-agent/testing 0.1.0-beta.6 → 0.1.0-beta.61
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 +105 -0
- package/dist/Certification.mjs +590 -0
- package/dist/Certification.mjs.map +1 -0
- package/dist/Chaos.d.mts +126 -0
- package/dist/Chaos.mjs +755 -0
- package/dist/Chaos.mjs.map +1 -0
- package/dist/CodeExecutorConformance.d.mts +33 -0
- package/dist/CodeExecutorConformance.mjs +231 -0
- package/dist/CodeExecutorConformance.mjs.map +1 -0
- package/dist/CodeExecutorSubstitute.d.mts +21 -0
- package/dist/CodeExecutorSubstitute.mjs +364 -0
- package/dist/CodeExecutorSubstitute.mjs.map +1 -0
- package/dist/DocsResearcher.d.mts +270 -0
- package/dist/DocsResearcher.mjs +490 -0
- package/dist/DocsResearcher.mjs.map +1 -0
- package/dist/ScriptedModel-DAvxIiud.d.mts +220 -0
- package/dist/ScriptedModel.d.mts +2 -0
- package/dist/ScriptedModel.mjs +155 -0
- package/dist/ScriptedModel.mjs.map +1 -0
- package/dist/TravelPlanner.d.mts +1665 -0
- package/dist/TravelPlanner.mjs +1968 -0
- package/dist/TravelPlanner.mjs.map +1 -0
- package/dist/deterministic-layers-Eka0fMZq.mjs +358 -0
- package/dist/deterministic-layers-Eka0fMZq.mjs.map +1 -0
- package/dist/index.d.mts +2 -3378
- package/dist/index.mjs +2 -4766
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/package.json +1 -48
- package/src/{certification.ts → Certification.ts} +251 -115
- package/src/{chaos.ts → Chaos.ts} +246 -122
- package/src/{code-executor-conformance.ts → CodeExecutorConformance.ts} +29 -6
- package/src/{code-executor-substitute.ts → CodeExecutorSubstitute.ts} +104 -42
- package/src/{fixtures/docs-researcher/index.ts → DocsResearcher.ts} +68 -5
- package/src/{scripted-model.ts → ScriptedModel.ts} +22 -25
- package/src/TravelPlanner.ts +232 -0
- package/src/fixtures/docs-researcher/definition.ts +19 -10
- package/src/fixtures/docs-researcher/harness.ts +41 -30
- package/src/fixtures/docs-researcher/mcp.ts +49 -3
- package/src/fixtures/travel-planner/definition.ts +15 -2
- package/src/fixtures/travel-planner/deterministic-layers.ts +47 -4
- package/src/fixtures/travel-planner/phase2.ts +4 -3
- package/src/fixtures/travel-planner/phase3.ts +18 -37
- package/src/fixtures/travel-planner/phase4.ts +23 -36
- package/src/fixtures/travel-planner/phase5.ts +38 -41
- package/src/fixtures/travel-planner/phase6.ts +192 -84
- package/src/fixtures/travel-planner/phase7.ts +4 -102
- package/src/fixtures/travel-planner/scenarios.ts +3 -4
- package/src/fixtures/travel-planner/subagents-durable.ts +33 -57
- package/src/fixtures/travel-planner/subagents.ts +35 -13
- package/src/index.ts +1 -11
- package/dist/index.mjs.map +0 -1
- package/src/code-executor-conformance.d.ts +0 -30
- package/src/fixtures/travel-planner/index.ts +0 -11
- package/src/fixtures/warehouse/index.ts +0 -412
|
@@ -1,67 +1,72 @@
|
|
|
1
|
+
import * as Subagent from "@effect-agent/capabilities/Subagent";
|
|
2
|
+
import { SubagentPolicy, SubagentRuntime } from "@effect-agent/capabilities/Subagent";
|
|
3
|
+
import { SubagentReservationsMemoryLive } from "@effect-agent/capabilities/SubagentReservations";
|
|
4
|
+
import * as Agent from "@effect-agent/core/Agent";
|
|
5
|
+
import { AgentPolicy } from "@effect-agent/core/AgentPolicy";
|
|
1
6
|
import {
|
|
2
|
-
|
|
3
|
-
SubagentPolicy,
|
|
4
|
-
SubagentReservationsMemoryLive,
|
|
5
|
-
SubagentRuntime,
|
|
6
|
-
} from "@effect-agent/capabilities";
|
|
7
|
-
import {
|
|
8
|
-
Agent,
|
|
9
|
-
AgentPolicy,
|
|
10
|
-
ConversationId,
|
|
11
|
-
IdGenerator,
|
|
7
|
+
ThreadId,
|
|
12
8
|
RunId,
|
|
13
9
|
ToolCallId,
|
|
14
10
|
TurnId,
|
|
15
11
|
type AgentId,
|
|
16
12
|
type SubmissionId,
|
|
17
|
-
} from "@effect-agent/core";
|
|
18
|
-
import {
|
|
13
|
+
} from "@effect-agent/core/Identifiers";
|
|
14
|
+
import { IdGenerator } from "@effect-agent/core/IdGenerator";
|
|
15
|
+
import { DurableStep, DurableStepError } from "@effect-agent/engine/DurableStep";
|
|
16
|
+
import { RunToolAuthorization } from "@effect-agent/engine/RunOptions";
|
|
17
|
+
import { DurableWorkerBinding, type ResolvedBinding } from "@effect-agent/thread/AgentRegistration";
|
|
19
18
|
import {
|
|
20
|
-
AgentBindingResolver,
|
|
21
|
-
ApprovalDecisionCommand,
|
|
22
|
-
CertificationCaseResult,
|
|
23
|
-
CertificationReport,
|
|
24
|
-
CertificationSweepResult,
|
|
25
|
-
CertificationTierThreeReport,
|
|
26
|
-
CertifiedAdapterIdentity,
|
|
27
|
-
ConversationExportRequest,
|
|
28
|
-
ConversationStore,
|
|
29
|
-
DefinitionDigests,
|
|
30
|
-
DeploymentId,
|
|
31
|
-
Digest,
|
|
32
19
|
DurableAgentRuntime,
|
|
33
20
|
DurableRuntimeConfig,
|
|
34
|
-
|
|
21
|
+
type DurableSubmitFailure,
|
|
22
|
+
type DurableSubmitOptions,
|
|
23
|
+
type Receipt,
|
|
24
|
+
} from "@effect-agent/thread/DurableAgentRuntime";
|
|
25
|
+
import {
|
|
35
26
|
DurableRuntimeFailpointError,
|
|
36
27
|
DurableRuntimeFailpointLocation,
|
|
37
|
-
|
|
38
|
-
|
|
28
|
+
} from "@effect-agent/thread/DurableFailpoint";
|
|
29
|
+
import {
|
|
30
|
+
DefinitionDigests,
|
|
31
|
+
DeploymentId,
|
|
32
|
+
Digest,
|
|
33
|
+
ProducerId,
|
|
34
|
+
type BatchId,
|
|
35
|
+
} from "@effect-agent/thread/Records";
|
|
36
|
+
import { childThreadIdFor } from "@effect-agent/thread/RunJournal";
|
|
37
|
+
import {
|
|
38
|
+
ApprovalDecisionCommand,
|
|
39
39
|
IdempotencyKey,
|
|
40
|
-
LoadCheckpointRequest,
|
|
41
40
|
Principal,
|
|
42
|
-
ProducerId,
|
|
43
41
|
ResolutionSafeToRetry,
|
|
44
42
|
SubmissionLedger,
|
|
45
43
|
SubmissionLookupById,
|
|
46
|
-
ToolReconciler,
|
|
47
44
|
UnknownResolutionCommand,
|
|
48
|
-
WakeScheduler,
|
|
49
45
|
DEFAULT_OWNERSHIP_LEASE_DURATION,
|
|
46
|
+
type SubmissionSnapshot,
|
|
47
|
+
} from "@effect-agent/thread/SubmissionLedger";
|
|
48
|
+
import {
|
|
49
|
+
type CertificationCaseResult,
|
|
50
|
+
CertificationReport,
|
|
51
|
+
CertificationSweepResult,
|
|
52
|
+
CertificationTierThreeReport,
|
|
53
|
+
CertifiedAdapterIdentity,
|
|
50
54
|
certifyPorts,
|
|
51
|
-
childConversationIdFor,
|
|
52
|
-
verifyConversationInvariants,
|
|
53
|
-
type BatchId,
|
|
54
55
|
type CertificationScenario,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
} from "@effect-agent/thread/testing/Certification";
|
|
57
|
+
import { DurableRuntimeFailpointTestControl } from "@effect-agent/thread/testing/DurableFailpointTestControl";
|
|
58
|
+
import { verifyThreadInvariants } from "@effect-agent/thread/ThreadInvariants";
|
|
59
|
+
import {
|
|
60
|
+
ThreadExportRequest,
|
|
61
|
+
ThreadStore,
|
|
62
|
+
LoadCheckpointRequest,
|
|
63
|
+
} from "@effect-agent/thread/ThreadStore";
|
|
64
|
+
import { ToolReconciler } from "@effect-agent/thread/ToolReconciler";
|
|
65
|
+
import { WakeScheduler } from "@effect-agent/thread/WakeScheduler";
|
|
66
|
+
import type { Crypto } from "effect";
|
|
61
67
|
import {
|
|
62
68
|
Cause,
|
|
63
69
|
Clock,
|
|
64
|
-
Crypto,
|
|
65
70
|
DateTime,
|
|
66
71
|
Duration,
|
|
67
72
|
Effect,
|
|
@@ -87,7 +92,7 @@ import { LanguageModel, Model, Tool, Toolkit, type Response } from "effect/unsta
|
|
|
87
92
|
* (plain / uncertain-tool / durable-steps / approval / join / delegation). After the injected
|
|
88
93
|
* fault the runner asserts the state stays CLASSIFIABLE (recovery + the public unblocking
|
|
89
94
|
* operations `resolveUnknown`/`resolveApproval` are the only levers used) and that the
|
|
90
|
-
* re-drive CONVERGES to `
|
|
95
|
+
* re-drive CONVERGES to `verifyThreadInvariants` with `requireAllSettled` — including a
|
|
91
96
|
* fully discharged digest-chain check, because the runner captures per-batch producer
|
|
92
97
|
* identity at append time.
|
|
93
98
|
* - **Tier 3 — real loss lever**: recorded honestly. A durable adapter either supplies a
|
|
@@ -126,7 +131,7 @@ export interface CertifyDurableAdaptersOptions<LedgerE = never, StoreE = never>
|
|
|
126
131
|
* does); the certification's own environment supplies nothing else.
|
|
127
132
|
*/
|
|
128
133
|
readonly submissionLedger: Layer.Layer<SubmissionLedger, LedgerE, Crypto.Crypto>;
|
|
129
|
-
readonly
|
|
134
|
+
readonly threadStore: Layer.Layer<ThreadStore, StoreE, Crypto.Crypto>;
|
|
130
135
|
/** Defaults to `WakeScheduler.layerNoop`; the runner re-drives lanes explicitly. */
|
|
131
136
|
readonly wakeScheduler?: Layer.Layer<WakeScheduler> | undefined;
|
|
132
137
|
/** Executes Tier 3 in this run; takes precedence over `tierThreeEvidence`. */
|
|
@@ -166,8 +171,36 @@ export const CERTIFICATION_SCENARIOS: ReadonlyArray<CertificationScenario> = [
|
|
|
166
171
|
*/
|
|
167
172
|
export const TIER2_UNREACHED_LOCATIONS: ReadonlyArray<DurableRuntimeFailpointLocation> = [
|
|
168
173
|
"abort:after-intent",
|
|
174
|
+
// Compaction requires a `contextTokenLimit` policy plus prior-Run history
|
|
175
|
+
// none of the six scenario shapes carries; pinned in-process by the
|
|
176
|
+
// RUN-026 rows in `packages/testing/test/durable-runtime.test.ts`
|
|
177
|
+
// (compaction failpoint idempotence across re-drive).
|
|
178
|
+
"compaction:before-canonical-append",
|
|
179
|
+
"compaction:after-canonical-append",
|
|
180
|
+
// These shapes use neither programmatic Tools nor grace finalization. Their reservations
|
|
181
|
+
// are exercised before/after append in the public durable-runtime regression suite.
|
|
182
|
+
"policy:before-reservation-append",
|
|
183
|
+
"policy:after-reservation-append",
|
|
169
184
|
"resolve:after-intent",
|
|
170
185
|
"subagent:after-child-abort-intent",
|
|
186
|
+
// Background workers use retained delivery, source capacity, and child-origin paths absent
|
|
187
|
+
// from these six attached/ordinary scenarios. The before/after creation and completion
|
|
188
|
+
// boundaries are exercised by packages/thread/test/worker-host.test.ts.
|
|
189
|
+
"worker:before-source-append",
|
|
190
|
+
"worker:after-source-append",
|
|
191
|
+
"worker:before-origin-append",
|
|
192
|
+
"worker:after-origin-append",
|
|
193
|
+
"worker:before-completion-append",
|
|
194
|
+
"worker:after-completion-append",
|
|
195
|
+
// Root attached declarations keep their independent pool; nested shared subtree mutations
|
|
196
|
+
// are covered by the same focused worker-host failpoint suite.
|
|
197
|
+
"worker:before-subtree-append",
|
|
198
|
+
"worker:after-subtree-append",
|
|
199
|
+
// Automatic report decisions and retained delivery insertion have dedicated worker crash tests.
|
|
200
|
+
"worker:before-report-append",
|
|
201
|
+
"worker:after-report-append",
|
|
202
|
+
"worker:before-report-delivery",
|
|
203
|
+
"worker:after-report-delivery",
|
|
171
204
|
];
|
|
172
205
|
|
|
173
206
|
/** Locations of `tier2` rows whose armed fault never fired in ANY scenario, sorted. */
|
|
@@ -175,9 +208,11 @@ export const tier2NeverFiredLocations = (
|
|
|
175
208
|
tier2: ReadonlyArray<CertificationSweepResult>,
|
|
176
209
|
): ReadonlyArray<DurableRuntimeFailpointLocation> => {
|
|
177
210
|
const fired = new Set<DurableRuntimeFailpointLocation>();
|
|
211
|
+
|
|
178
212
|
for (const row of tier2) {
|
|
179
213
|
if (row.failpointFired) fired.add(row.location);
|
|
180
214
|
}
|
|
215
|
+
|
|
181
216
|
return DurableRuntimeFailpointLocation.literals.filter((location) => !fired.has(location)).sort();
|
|
182
217
|
};
|
|
183
218
|
|
|
@@ -187,18 +222,21 @@ export const tier2NeverFiredLocations = (
|
|
|
187
222
|
|
|
188
223
|
const SHA_A = Schema.decodeSync(Digest)("a".repeat(64));
|
|
189
224
|
const DIGESTS = DefinitionDigests.make({ agent: SHA_A, model: SHA_A, tools: SHA_A });
|
|
225
|
+
|
|
190
226
|
const CHILD_DIGEST_STRINGS = {
|
|
191
227
|
agent: "b".repeat(64),
|
|
192
228
|
model: "c".repeat(64),
|
|
193
229
|
tools: "d".repeat(64),
|
|
194
230
|
} as const;
|
|
231
|
+
|
|
195
232
|
const CHILD_DIGESTS = DefinitionDigests.make({
|
|
196
233
|
agent: Schema.decodeSync(Digest)(CHILD_DIGEST_STRINGS.agent),
|
|
197
234
|
model: Schema.decodeSync(Digest)(CHILD_DIGEST_STRINGS.model),
|
|
198
235
|
tools: Schema.decodeSync(Digest)(CHILD_DIGEST_STRINGS.tools),
|
|
199
236
|
});
|
|
237
|
+
|
|
200
238
|
const PRINCIPAL = Schema.decodeSync(Principal)("principal-certification");
|
|
201
|
-
const
|
|
239
|
+
const decodeThreadId = Schema.decodeSync(ThreadId);
|
|
202
240
|
const decodeIdempotencyKey = Schema.decodeSync(IdempotencyKey);
|
|
203
241
|
const decodeToolCallId = Schema.decodeSync(ToolCallId);
|
|
204
242
|
|
|
@@ -248,18 +286,26 @@ const promptShapeModel = (
|
|
|
248
286
|
generateText: () => Effect.succeed([]),
|
|
249
287
|
streamText: (request) => {
|
|
250
288
|
const hasToolResult = request.prompt.content.some((message) => message.role === "tool");
|
|
289
|
+
|
|
251
290
|
const parts =
|
|
252
291
|
toolParts === undefined || hasToolResult ? finalParts(finalText) : toolParts;
|
|
292
|
+
|
|
253
293
|
return Stream.fromIterable(parts);
|
|
254
294
|
},
|
|
255
295
|
}),
|
|
256
296
|
),
|
|
257
297
|
);
|
|
258
298
|
|
|
299
|
+
// Tier-2 intentionally advances virtual time past a 30-second ownership lease on every
|
|
300
|
+
// re-drive round. Keep its business-policy duration above the full eight-round sweep so this
|
|
301
|
+
// fixture certifies crash convergence rather than accidentally relying on a fresh duration
|
|
302
|
+
// allowance per replacement Attempt. RUN-030 owns the dedicated duration-expiry coverage.
|
|
303
|
+
const CERTIFICATION_MAX_DURATION = "10 minutes" as const;
|
|
304
|
+
|
|
259
305
|
const policy = AgentPolicy.make({
|
|
260
306
|
maxTurns: 4,
|
|
261
307
|
maxToolCalls: 4,
|
|
262
|
-
maxDuration:
|
|
308
|
+
maxDuration: CERTIFICATION_MAX_DURATION,
|
|
263
309
|
toolConcurrency: 2,
|
|
264
310
|
});
|
|
265
311
|
|
|
@@ -267,7 +313,7 @@ const QuestionInput = Schema.Struct({ question: Schema.String });
|
|
|
267
313
|
const AnswerOutput = Schema.Struct({ answer: Schema.String });
|
|
268
314
|
|
|
269
315
|
/** plain / join: no tools — the pure Turn/submission/join seams. */
|
|
270
|
-
const plainDefinition = Agent.
|
|
316
|
+
const plainDefinition = Agent.make("certify-plain", {
|
|
271
317
|
input: QuestionInput,
|
|
272
318
|
output: AnswerOutput,
|
|
273
319
|
instructions: "Answer as JSON.",
|
|
@@ -280,8 +326,10 @@ const Book = Tool.make("book", {
|
|
|
280
326
|
parameters: Schema.Struct({ ref: Schema.String }),
|
|
281
327
|
success: Schema.Struct({ confirmation: Schema.String }),
|
|
282
328
|
});
|
|
329
|
+
|
|
283
330
|
const bookToolkit = Toolkit.make(Book);
|
|
284
|
-
|
|
331
|
+
|
|
332
|
+
const uncertainDefinition = Agent.make("certify-uncertain", {
|
|
285
333
|
input: QuestionInput,
|
|
286
334
|
output: AnswerOutput,
|
|
287
335
|
instructions: "Book it.",
|
|
@@ -296,8 +344,10 @@ const Itinerary = Tool.make("itinerary", {
|
|
|
296
344
|
failure: DurableStepError,
|
|
297
345
|
dependencies: [DurableStep],
|
|
298
346
|
});
|
|
347
|
+
|
|
299
348
|
const itineraryToolkit = Toolkit.make(Itinerary);
|
|
300
|
-
|
|
349
|
+
|
|
350
|
+
const stepsDefinition = Agent.make("certify-steps", {
|
|
301
351
|
input: QuestionInput,
|
|
302
352
|
output: AnswerOutput,
|
|
303
353
|
instructions: "Reserve the itinerary.",
|
|
@@ -311,8 +361,10 @@ const BookApproval = Tool.make("book", {
|
|
|
311
361
|
success: Schema.Struct({ confirmation: Schema.String }),
|
|
312
362
|
needsApproval: true,
|
|
313
363
|
});
|
|
364
|
+
|
|
314
365
|
const approvalToolkit = Toolkit.make(BookApproval);
|
|
315
|
-
|
|
366
|
+
|
|
367
|
+
const approvalDefinition = Agent.make("certify-approval", {
|
|
316
368
|
input: QuestionInput,
|
|
317
369
|
output: AnswerOutput,
|
|
318
370
|
instructions: "Book after approval.",
|
|
@@ -321,7 +373,7 @@ const approvalDefinition = Agent.define("certify-approval", {
|
|
|
321
373
|
});
|
|
322
374
|
|
|
323
375
|
/** delegation: durable attached child plus an ordinary uncertain sibling in ONE batch. */
|
|
324
|
-
const childDefinition = Agent.
|
|
376
|
+
const childDefinition = Agent.make("certify-child", {
|
|
325
377
|
input: QuestionInput,
|
|
326
378
|
output: AnswerOutput,
|
|
327
379
|
instructions: "Answer as JSON.",
|
|
@@ -329,7 +381,7 @@ const childDefinition = Agent.define("certify-child", {
|
|
|
329
381
|
policy: AgentPolicy.make({
|
|
330
382
|
maxTurns: 2,
|
|
331
383
|
maxToolCalls: 1,
|
|
332
|
-
maxDuration:
|
|
384
|
+
maxDuration: CERTIFICATION_MAX_DURATION,
|
|
333
385
|
toolConcurrency: 1,
|
|
334
386
|
}),
|
|
335
387
|
});
|
|
@@ -352,7 +404,7 @@ const researchDelegation = Subagent.define("delegate_research", {
|
|
|
352
404
|
maxConcurrency: 2,
|
|
353
405
|
maxTurns: 4,
|
|
354
406
|
maxToolCalls: 4,
|
|
355
|
-
maxDuration:
|
|
407
|
+
maxDuration: CERTIFICATION_MAX_DURATION,
|
|
356
408
|
}),
|
|
357
409
|
});
|
|
358
410
|
|
|
@@ -361,7 +413,7 @@ const Lookup = Tool.make("lookup", {
|
|
|
361
413
|
success: Schema.Struct({ value: Schema.String }),
|
|
362
414
|
});
|
|
363
415
|
|
|
364
|
-
const coordinatorDefinition = Agent.
|
|
416
|
+
const coordinatorDefinition = Agent.make("certify-coordinator", {
|
|
365
417
|
input: Schema.Struct({ mission: Schema.String }),
|
|
366
418
|
output: Schema.Struct({ report: Schema.String }),
|
|
367
419
|
instructions: "Delegate and look up, then answer as JSON.",
|
|
@@ -369,7 +421,7 @@ const coordinatorDefinition = Agent.define("certify-coordinator", {
|
|
|
369
421
|
policy: AgentPolicy.make({
|
|
370
422
|
maxTurns: 4,
|
|
371
423
|
maxToolCalls: 3,
|
|
372
|
-
maxDuration:
|
|
424
|
+
maxDuration: CERTIFICATION_MAX_DURATION,
|
|
373
425
|
toolConcurrency: 2,
|
|
374
426
|
}),
|
|
375
427
|
});
|
|
@@ -384,12 +436,14 @@ const identifiers = Layer.effect(
|
|
|
384
436
|
IdGenerator,
|
|
385
437
|
Effect.gen(function* () {
|
|
386
438
|
const counter = yield* Ref.make(0);
|
|
439
|
+
|
|
387
440
|
const next = <A>(decode: (value: string) => A, prefix: string) =>
|
|
388
441
|
Ref.getAndUpdate(counter, (value) => value + 1).pipe(
|
|
389
442
|
Effect.map((value) => decode(`${prefix}-${value}`)),
|
|
390
443
|
);
|
|
444
|
+
|
|
391
445
|
return {
|
|
392
|
-
|
|
446
|
+
nextThreadId: next(decodeThreadId, "certify-fixture-thread"),
|
|
393
447
|
nextRunId: next(Schema.decodeSync(RunId), "certify-fixture-run"),
|
|
394
448
|
nextTurnId: next(Schema.decodeSync(TurnId), "certify-fixture-turn"),
|
|
395
449
|
};
|
|
@@ -403,15 +457,15 @@ const delegationSupport = Layer.mergeAll(SubagentReservationsMemoryLive, identif
|
|
|
403
457
|
// ---------------------------------------------------------------------------
|
|
404
458
|
|
|
405
459
|
interface CertificationCell {
|
|
406
|
-
readonly
|
|
460
|
+
readonly bindings: ReadonlyArray<ResolvedBinding>;
|
|
407
461
|
/** Idempotent submission batch — safe to replay verbatim after a submit-boundary fault. */
|
|
408
462
|
readonly submit: Effect.Effect<ReadonlyArray<Receipt>, DurableSubmitFailure, DurableAgentRuntime>;
|
|
409
463
|
/** All lanes of the cell in drive order, computed from the (possibly replayed) receipts. */
|
|
410
|
-
readonly lanes: (receipts: ReadonlyArray<Receipt>) => ReadonlyArray<
|
|
464
|
+
readonly lanes: (receipts: ReadonlyArray<Receipt>) => ReadonlyArray<ThreadId>;
|
|
411
465
|
}
|
|
412
466
|
|
|
413
|
-
const submitOptionsFor = (slug: string,
|
|
414
|
-
|
|
467
|
+
const submitOptionsFor = (slug: string, threadId: ThreadId): DurableSubmitOptions => ({
|
|
468
|
+
threadId,
|
|
415
469
|
principal: PRINCIPAL,
|
|
416
470
|
idempotencyKey: decodeIdempotencyKey(`certify-key-${slug}`),
|
|
417
471
|
definitions: DIGESTS,
|
|
@@ -423,20 +477,24 @@ const makeSingleAgentCell = (
|
|
|
423
477
|
resolved: ResolvedBinding,
|
|
424
478
|
slug: string,
|
|
425
479
|
): CertificationCell => {
|
|
426
|
-
const
|
|
480
|
+
const threadId = decodeThreadId(`certify-${slug}`);
|
|
481
|
+
|
|
427
482
|
const submit = Effect.gen(function* () {
|
|
428
483
|
const runtime = yield* DurableAgentRuntime;
|
|
484
|
+
|
|
429
485
|
const receipt = yield* runtime.submit(
|
|
430
486
|
{ definition: { id: definition.id, input: definition.input } },
|
|
431
487
|
{ question: `certify ${slug}` },
|
|
432
|
-
submitOptionsFor(slug,
|
|
488
|
+
submitOptionsFor(slug, threadId),
|
|
433
489
|
);
|
|
490
|
+
|
|
434
491
|
return [receipt];
|
|
435
492
|
});
|
|
493
|
+
|
|
436
494
|
return {
|
|
437
|
-
|
|
495
|
+
bindings: [resolved],
|
|
438
496
|
submit,
|
|
439
|
-
lanes: () => [
|
|
497
|
+
lanes: () => [threadId],
|
|
440
498
|
};
|
|
441
499
|
};
|
|
442
500
|
|
|
@@ -450,7 +508,9 @@ const makeCell = Effect.fn("Certification.makeCell")(function* (
|
|
|
450
508
|
plainDefinition,
|
|
451
509
|
promptShapeModel("certify-plain", '{"answer":"done"}'),
|
|
452
510
|
);
|
|
511
|
+
|
|
453
512
|
const resolved = yield* DurableWorkerBinding.make(binding, DIGESTS);
|
|
513
|
+
|
|
454
514
|
return makeSingleAgentCell(plainDefinition, resolved, slug);
|
|
455
515
|
}
|
|
456
516
|
case "uncertain-tool": {
|
|
@@ -462,12 +522,15 @@ const makeCell = Effect.fn("Certification.makeCell")(function* (
|
|
|
462
522
|
toolTurn(toolCallPart("book-1", "book", { ref: `r-${slug}` })),
|
|
463
523
|
),
|
|
464
524
|
);
|
|
525
|
+
|
|
465
526
|
const toolLayer = bookToolkit.toLayer({
|
|
466
527
|
book: ({ ref }) => Effect.succeed({ confirmation: `confirmed-${ref}` }),
|
|
467
528
|
});
|
|
529
|
+
|
|
468
530
|
const resolved = yield* DurableWorkerBinding.make(binding, DIGESTS).pipe(
|
|
469
531
|
Effect.provide(toolLayer),
|
|
470
532
|
);
|
|
533
|
+
|
|
471
534
|
return makeSingleAgentCell(uncertainDefinition, resolved, slug);
|
|
472
535
|
}
|
|
473
536
|
case "durable-steps": {
|
|
@@ -479,26 +542,32 @@ const makeCell = Effect.fn("Certification.makeCell")(function* (
|
|
|
479
542
|
toolTurn(toolCallPart("itinerary-1", "itinerary", { ref: `trip-${slug}` })),
|
|
480
543
|
),
|
|
481
544
|
);
|
|
545
|
+
|
|
482
546
|
const toolLayer = itineraryToolkit.toLayer({
|
|
483
547
|
itinerary: ({ ref }) =>
|
|
484
548
|
Effect.gen(function* () {
|
|
485
549
|
const step = yield* DurableStep;
|
|
550
|
+
|
|
486
551
|
const flight = yield* step.do(
|
|
487
552
|
"reserve-flight",
|
|
488
553
|
Schema.String,
|
|
489
554
|
Effect.succeed(`flight-${ref}`),
|
|
490
555
|
);
|
|
556
|
+
|
|
491
557
|
const lodging = yield* step.do(
|
|
492
558
|
"reserve-lodging",
|
|
493
559
|
Schema.String,
|
|
494
560
|
Effect.succeed(`lodging-${ref}`),
|
|
495
561
|
);
|
|
562
|
+
|
|
496
563
|
return { state: `${flight}+${lodging}` };
|
|
497
564
|
}),
|
|
498
565
|
});
|
|
566
|
+
|
|
499
567
|
const resolved = yield* DurableWorkerBinding.make(binding, DIGESTS).pipe(
|
|
500
568
|
Effect.provide(toolLayer),
|
|
501
569
|
);
|
|
570
|
+
|
|
502
571
|
return makeSingleAgentCell(stepsDefinition, resolved, slug);
|
|
503
572
|
}
|
|
504
573
|
case "approval": {
|
|
@@ -510,12 +579,15 @@ const makeCell = Effect.fn("Certification.makeCell")(function* (
|
|
|
510
579
|
toolTurn(toolCallPart("book-1", "book", { ref: `r-${slug}` })),
|
|
511
580
|
),
|
|
512
581
|
);
|
|
582
|
+
|
|
513
583
|
const toolLayer = approvalToolkit.toLayer({
|
|
514
584
|
book: ({ ref }) => Effect.succeed({ confirmation: `confirmed-${ref}` }),
|
|
515
585
|
});
|
|
586
|
+
|
|
516
587
|
const resolved = yield* DurableWorkerBinding.make(binding, DIGESTS).pipe(
|
|
517
588
|
Effect.provide(toolLayer),
|
|
518
589
|
);
|
|
590
|
+
|
|
519
591
|
return makeSingleAgentCell(approvalDefinition, resolved, slug);
|
|
520
592
|
}
|
|
521
593
|
case "join": {
|
|
@@ -523,31 +595,39 @@ const makeCell = Effect.fn("Certification.makeCell")(function* (
|
|
|
523
595
|
plainDefinition,
|
|
524
596
|
promptShapeModel("certify-join", '{"answer":"host answer"}'),
|
|
525
597
|
);
|
|
598
|
+
|
|
526
599
|
const resolved = yield* DurableWorkerBinding.make(binding, DIGESTS);
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
600
|
+
const threadId = decodeThreadId(`certify-${slug}`);
|
|
601
|
+
|
|
602
|
+
const submitOne = Effect.fn("Certification.submitOne")(function* (
|
|
603
|
+
key: string,
|
|
604
|
+
question: string,
|
|
605
|
+
) {
|
|
606
|
+
const runtime = yield* DurableAgentRuntime;
|
|
607
|
+
|
|
608
|
+
return yield* runtime.submit(
|
|
609
|
+
{ definition: { id: plainDefinition.id, input: plainDefinition.input } },
|
|
610
|
+
{ question },
|
|
611
|
+
{
|
|
612
|
+
threadId,
|
|
613
|
+
principal: PRINCIPAL,
|
|
614
|
+
idempotencyKey: decodeIdempotencyKey(key),
|
|
615
|
+
definitions: DIGESTS,
|
|
616
|
+
},
|
|
617
|
+
);
|
|
618
|
+
});
|
|
619
|
+
|
|
542
620
|
const cell: CertificationCell = {
|
|
543
|
-
|
|
621
|
+
bindings: [resolved],
|
|
544
622
|
submit: Effect.gen(function* () {
|
|
545
623
|
const host = yield* submitOne(`certify-key-${slug}-host`, "host question");
|
|
546
624
|
const queued = yield* submitOne(`certify-key-${slug}-queued`, "queued question");
|
|
625
|
+
|
|
547
626
|
return [host, queued];
|
|
548
627
|
}),
|
|
549
|
-
lanes: () => [
|
|
628
|
+
lanes: () => [threadId],
|
|
550
629
|
};
|
|
630
|
+
|
|
551
631
|
return cell;
|
|
552
632
|
}
|
|
553
633
|
case "delegation": {
|
|
@@ -555,6 +635,7 @@ const makeCell = Effect.fn("Certification.makeCell")(function* (
|
|
|
555
635
|
childDefinition,
|
|
556
636
|
promptShapeModel("certify-child", '{"answer":"child-answer"}'),
|
|
557
637
|
);
|
|
638
|
+
|
|
558
639
|
const parentBinding = Agent.withModel(
|
|
559
640
|
coordinatorDefinition,
|
|
560
641
|
promptShapeModel(
|
|
@@ -566,36 +647,45 @@ const makeCell = Effect.fn("Certification.makeCell")(function* (
|
|
|
566
647
|
),
|
|
567
648
|
),
|
|
568
649
|
);
|
|
650
|
+
|
|
569
651
|
const delegationLayer = SubagentRuntime.layer(researchDelegation, childBinding, {
|
|
570
652
|
mapChildFailure,
|
|
571
653
|
durable: { targetDigests: CHILD_DIGEST_STRINGS },
|
|
572
654
|
}).pipe(Layer.provide(delegationSupport));
|
|
655
|
+
|
|
573
656
|
const lookupLayer = Toolkit.make(Lookup).toLayer({
|
|
574
657
|
lookup: ({ key }) => Effect.succeed({ value: `found-${key}` }),
|
|
575
658
|
});
|
|
659
|
+
|
|
576
660
|
const parentResolved = yield* DurableWorkerBinding.make(parentBinding, DIGESTS).pipe(
|
|
577
661
|
Effect.provide(Layer.mergeAll(delegationLayer, lookupLayer)),
|
|
578
662
|
);
|
|
663
|
+
|
|
579
664
|
const childResolved = yield* DurableWorkerBinding.make(childBinding, CHILD_DIGESTS);
|
|
580
|
-
const
|
|
665
|
+
const threadId = decodeThreadId(`certify-${slug}`);
|
|
666
|
+
|
|
581
667
|
const cell: CertificationCell = {
|
|
582
|
-
|
|
668
|
+
bindings: [parentResolved, childResolved],
|
|
583
669
|
submit: Effect.gen(function* () {
|
|
584
670
|
const runtime = yield* DurableAgentRuntime;
|
|
671
|
+
|
|
585
672
|
const receipt = yield* runtime.submit(
|
|
586
673
|
{ definition: { id: coordinatorDefinition.id, input: coordinatorDefinition.input } },
|
|
587
674
|
{ mission: "plan" },
|
|
588
|
-
submitOptionsFor(slug,
|
|
675
|
+
submitOptionsFor(slug, threadId),
|
|
589
676
|
);
|
|
677
|
+
|
|
590
678
|
return [receipt];
|
|
591
679
|
}),
|
|
592
680
|
lanes: (receipts) => {
|
|
593
681
|
const parent = receipts.at(0);
|
|
682
|
+
|
|
594
683
|
return parent === undefined
|
|
595
|
-
? [
|
|
596
|
-
: [
|
|
684
|
+
? [threadId]
|
|
685
|
+
: [threadId, childThreadIdFor(parent.submissionId, DELEGATE_CALL)];
|
|
597
686
|
},
|
|
598
687
|
};
|
|
688
|
+
|
|
599
689
|
return cell;
|
|
600
690
|
}
|
|
601
691
|
}
|
|
@@ -611,56 +701,68 @@ const MAX_REDRIVE_ROUNDS = 8;
|
|
|
611
701
|
* the digest chain is fully recomputed instead of skipped.
|
|
612
702
|
*/
|
|
613
703
|
const verifyLane = Effect.fn("Certification.verifyLane")(function* (
|
|
614
|
-
lane:
|
|
704
|
+
lane: ThreadId,
|
|
615
705
|
batchProducers: ReadonlyMap<BatchId, ProducerId>,
|
|
616
706
|
) {
|
|
617
|
-
const store = yield*
|
|
707
|
+
const store = yield* ThreadStore;
|
|
618
708
|
const ledger = yield* SubmissionLedger;
|
|
619
|
-
const exported = yield* store.export(
|
|
709
|
+
const exported = yield* store.export(ThreadExportRequest.make({ threadId: lane }));
|
|
620
710
|
const rows = new Map<SubmissionId, SubmissionSnapshot>();
|
|
621
711
|
const nonterminal = yield* Stream.runCollect(ledger.scanNonterminal);
|
|
712
|
+
|
|
622
713
|
for (const submission of nonterminal) {
|
|
623
|
-
if (submission.
|
|
714
|
+
if (submission.threadId === lane) rows.set(submission.submissionId, submission);
|
|
624
715
|
}
|
|
625
716
|
const named = new Set<SubmissionId>();
|
|
717
|
+
|
|
626
718
|
for (const envelope of exported.records) {
|
|
627
719
|
const payload = envelope.record.payload;
|
|
720
|
+
|
|
628
721
|
if (
|
|
629
722
|
payload._tag === "UserInputRecorded" ||
|
|
630
723
|
payload._tag === "SubmissionSettled" ||
|
|
631
724
|
payload._tag === "AbortRequested"
|
|
632
725
|
) {
|
|
633
|
-
named.add(payload.submissionId);
|
|
726
|
+
if (payload.submissionId !== undefined) named.add(payload.submissionId);
|
|
634
727
|
}
|
|
635
728
|
}
|
|
636
729
|
for (const submissionId of named) {
|
|
637
730
|
if (rows.has(submissionId)) continue;
|
|
638
731
|
const found = yield* ledger.lookup(SubmissionLookupById.make({ submissionId }));
|
|
639
|
-
|
|
732
|
+
|
|
733
|
+
if (Option.isSome(found) && found.value.threadId === lane) {
|
|
640
734
|
rows.set(submissionId, found.value);
|
|
641
735
|
}
|
|
642
736
|
}
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
737
|
+
|
|
738
|
+
const checkpoint =
|
|
739
|
+
store.checkpoints === undefined
|
|
740
|
+
? Option.none()
|
|
741
|
+
: yield* store.checkpoints.load(LoadCheckpointRequest.make({ threadId: lane }));
|
|
742
|
+
|
|
743
|
+
return yield* verifyThreadInvariants({
|
|
647
744
|
export: exported,
|
|
648
745
|
submissions: [...rows.values()],
|
|
649
746
|
batchProducers,
|
|
650
747
|
checkpoint: Option.getOrUndefined(checkpoint),
|
|
748
|
+
checkpointsSupported: store.checkpoints !== undefined,
|
|
651
749
|
requireAllSettled: true,
|
|
652
750
|
});
|
|
653
751
|
});
|
|
654
752
|
|
|
655
753
|
const failureTagOf = <E>(cause: Cause.Cause<E>): string => {
|
|
656
754
|
const failure = Cause.findErrorOption(cause);
|
|
755
|
+
|
|
657
756
|
if (Option.isSome(failure)) {
|
|
658
757
|
const error: unknown = failure.value;
|
|
758
|
+
|
|
659
759
|
if (typeof error === "object" && error !== null && "_tag" in error) {
|
|
660
|
-
return String(
|
|
760
|
+
return String(error._tag);
|
|
661
761
|
}
|
|
762
|
+
|
|
662
763
|
return String(error).slice(0, 256);
|
|
663
764
|
}
|
|
765
|
+
|
|
664
766
|
return "defect";
|
|
665
767
|
};
|
|
666
768
|
|
|
@@ -671,7 +773,6 @@ const runSweepCell = Effect.fn("Certification.runSweepCell")(function* (
|
|
|
671
773
|
batchProducers: ReadonlyMap<BatchId, ProducerId>,
|
|
672
774
|
leaseAdvance: Duration.Duration,
|
|
673
775
|
) {
|
|
674
|
-
const runtime = yield* DurableAgentRuntime;
|
|
675
776
|
const ledger = yield* SubmissionLedger;
|
|
676
777
|
const control = yield* DurableRuntimeFailpointTestControl;
|
|
677
778
|
const slug = `${scenario}-${location.replaceAll(":", "-")}`;
|
|
@@ -688,9 +789,20 @@ const runSweepCell = Effect.fn("Certification.runSweepCell")(function* (
|
|
|
688
789
|
|
|
689
790
|
const cell = yield* makeCell(scenario, slug);
|
|
690
791
|
|
|
792
|
+
const runtime = yield* DurableAgentRuntime.pipe(
|
|
793
|
+
Effect.provide(
|
|
794
|
+
DurableAgentRuntime.layerWithBindings(cell.bindings).pipe(
|
|
795
|
+
Layer.provide(RunToolAuthorization.allowAll),
|
|
796
|
+
),
|
|
797
|
+
),
|
|
798
|
+
);
|
|
799
|
+
|
|
800
|
+
const submit = cell.submit.pipe(Effect.provideService(DurableAgentRuntime, runtime));
|
|
801
|
+
|
|
691
802
|
// One-shot arm: the fault fires at most once anywhere in the cell (initial drive OR a
|
|
692
803
|
// re-drive round's public unblocking operation), modelling one crash at this boundary.
|
|
693
804
|
const fired = yield* Ref.make(false);
|
|
805
|
+
|
|
694
806
|
yield* control.setHandler((hit) =>
|
|
695
807
|
hit !== location
|
|
696
808
|
? Effect.void
|
|
@@ -705,13 +817,16 @@ const runSweepCell = Effect.fn("Certification.runSweepCell")(function* (
|
|
|
705
817
|
|
|
706
818
|
// Submissions are idempotent (DUR-001): one replay recovers a submit-boundary fault.
|
|
707
819
|
let receipts: ReadonlyArray<Receipt>;
|
|
708
|
-
const firstSubmit = yield* Effect.exit(
|
|
820
|
+
const firstSubmit = yield* Effect.exit(submit);
|
|
821
|
+
|
|
709
822
|
if (Exit.isSuccess(firstSubmit)) {
|
|
710
823
|
receipts = firstSubmit.value;
|
|
711
824
|
} else {
|
|
712
|
-
const secondSubmit = yield* Effect.exit(
|
|
825
|
+
const secondSubmit = yield* Effect.exit(submit);
|
|
826
|
+
|
|
713
827
|
if (Exit.isFailure(secondSubmit)) {
|
|
714
828
|
yield* control.clear;
|
|
829
|
+
|
|
715
830
|
return failed(
|
|
716
831
|
`submission replay did not recover: ${failureTagOf(secondSubmit.cause)}`,
|
|
717
832
|
yield* Ref.get(fired),
|
|
@@ -721,24 +836,24 @@ const runSweepCell = Effect.fn("Certification.runSweepCell")(function* (
|
|
|
721
836
|
}
|
|
722
837
|
const lanes = cell.lanes(receipts);
|
|
723
838
|
|
|
724
|
-
const driveLane = (lane:
|
|
725
|
-
runtime
|
|
726
|
-
.processConversationResolved(lane)
|
|
727
|
-
.pipe(Effect.provideService(AgentBindingResolver, cell.resolver));
|
|
839
|
+
const driveLane = (lane: ThreadId) => runtime.processThreadResolved(lane);
|
|
728
840
|
|
|
729
841
|
const allSettled = Effect.gen(function* () {
|
|
730
842
|
for (const receipt of receipts) {
|
|
731
843
|
const snapshot = yield* ledger.lookup(
|
|
732
844
|
SubmissionLookupById.make({ submissionId: receipt.submissionId }),
|
|
733
845
|
);
|
|
846
|
+
|
|
734
847
|
if (Option.isNone(snapshot) || snapshot.value.state !== "settled") return false;
|
|
735
848
|
}
|
|
849
|
+
|
|
736
850
|
return true;
|
|
737
851
|
});
|
|
738
852
|
|
|
739
853
|
// Re-drive to convergence using ONLY public operations: worker drives, recovery passes,
|
|
740
|
-
// and the authorized DUR-017/approval unblocking paths chosen from `
|
|
854
|
+
// and the authorized DUR-017/approval unblocking paths chosen from `explainThread`.
|
|
741
855
|
let converged = false;
|
|
856
|
+
|
|
742
857
|
for (let round = 0; round < MAX_REDRIVE_ROUNDS && !converged; round++) {
|
|
743
858
|
// Expire any lease a faulted Attempt left behind (D5): virtual time is the
|
|
744
859
|
// adapter-neutral reclaim lever — a live lease may block every new claim.
|
|
@@ -748,7 +863,8 @@ const runSweepCell = Effect.fn("Certification.runSweepCell")(function* (
|
|
|
748
863
|
yield* Effect.exit(driveLane(lane));
|
|
749
864
|
}
|
|
750
865
|
for (const lane of lanes) {
|
|
751
|
-
const explains = yield* Effect.exit(runtime.
|
|
866
|
+
const explains = yield* Effect.exit(runtime.explainThread(lane));
|
|
867
|
+
|
|
752
868
|
if (Exit.isFailure(explains)) continue;
|
|
753
869
|
for (const explanation of explains.value) {
|
|
754
870
|
for (const unknown of explanation.evidence.unknownCalls) {
|
|
@@ -769,6 +885,7 @@ const runSweepCell = Effect.fn("Certification.runSweepCell")(function* (
|
|
|
769
885
|
const decided = explanation.evidence.approvalDecisions.some(
|
|
770
886
|
(decision) => decision.toolCallId === pending.toolCallId,
|
|
771
887
|
);
|
|
888
|
+
|
|
772
889
|
if (decided) continue;
|
|
773
890
|
yield* Effect.exit(
|
|
774
891
|
runtime.resolveApproval(
|
|
@@ -785,6 +902,7 @@ const runSweepCell = Effect.fn("Certification.runSweepCell")(function* (
|
|
|
785
902
|
}
|
|
786
903
|
}
|
|
787
904
|
const settled = yield* Effect.exit(allSettled);
|
|
905
|
+
|
|
788
906
|
converged = Exit.isSuccess(settled) && settled.value;
|
|
789
907
|
}
|
|
790
908
|
yield* control.clear;
|
|
@@ -797,8 +915,10 @@ const runSweepCell = Effect.fn("Certification.runSweepCell")(function* (
|
|
|
797
915
|
// Every lane of the cell must verify in convergence mode with a recomputed digest chain.
|
|
798
916
|
let digestChainVerified = true;
|
|
799
917
|
const failedChecks: Array<string> = [];
|
|
918
|
+
|
|
800
919
|
for (const lane of lanes) {
|
|
801
920
|
const verdict = yield* Effect.exit(verifyLane(lane, batchProducers));
|
|
921
|
+
|
|
802
922
|
if (Exit.isFailure(verdict)) {
|
|
803
923
|
return failed(`lane ${lane} could not be verified: ${failureTagOf(verdict.cause)}`, wasFired);
|
|
804
924
|
}
|
|
@@ -859,10 +979,14 @@ export const resolveTierThree = Effect.fn("Certification.resolveTierThree")(func
|
|
|
859
979
|
}
|
|
860
980
|
if (options.crashLever !== undefined) {
|
|
861
981
|
const cases = yield* options.crashLever;
|
|
982
|
+
|
|
862
983
|
return CertificationTierThreeReport.make({
|
|
863
|
-
status: "exercised",
|
|
984
|
+
status: cases.length === 0 ? "not-exercised" : "exercised",
|
|
864
985
|
evidence: options.tierThreeEvidence ?? [],
|
|
865
986
|
cases,
|
|
987
|
+
...(cases.length === 0
|
|
988
|
+
? { detail: "the supplied crash lever executed no real-loss cases" }
|
|
989
|
+
: {}),
|
|
866
990
|
});
|
|
867
991
|
}
|
|
868
992
|
if (options.tierThreeEvidence !== undefined && options.tierThreeEvidence.length > 0) {
|
|
@@ -872,6 +996,7 @@ export const resolveTierThree = Effect.fn("Certification.resolveTierThree")(func
|
|
|
872
996
|
cases: [],
|
|
873
997
|
});
|
|
874
998
|
}
|
|
999
|
+
|
|
875
1000
|
return CertificationTierThreeReport.make({
|
|
876
1001
|
status: "not-exercised",
|
|
877
1002
|
evidence: [],
|
|
@@ -900,13 +1025,15 @@ export const certifyDurableAdapters = <LedgerE = never, StoreE = never>(
|
|
|
900
1025
|
options: CertifyDurableAdaptersOptions<LedgerE, StoreE>,
|
|
901
1026
|
): Effect.Effect<CertificationReport, LedgerE | StoreE, Crypto.Crypto> => {
|
|
902
1027
|
const batchProducers = new Map<BatchId, ProducerId>();
|
|
1028
|
+
|
|
903
1029
|
// Interpose the candidate store with an append-time capture of each batch's producer
|
|
904
|
-
// identity — the one value the
|
|
1030
|
+
// identity — the one value the ThreadStore port deliberately does not export — so
|
|
905
1031
|
// Tier 2's invariant verification recomputes the FULL digest chain instead of skipping it.
|
|
906
|
-
const capturingStore = Layer.effect(
|
|
1032
|
+
const capturingStore = Layer.effect(ThreadStore)(
|
|
907
1033
|
Effect.gen(function* () {
|
|
908
|
-
const inner = yield*
|
|
909
|
-
|
|
1034
|
+
const inner = yield* ThreadStore;
|
|
1035
|
+
|
|
1036
|
+
return ThreadStore.of({
|
|
910
1037
|
...inner,
|
|
911
1038
|
append: (request) =>
|
|
912
1039
|
Effect.sync(() => {
|
|
@@ -914,13 +1041,15 @@ export const certifyDurableAdapters = <LedgerE = never, StoreE = never>(
|
|
|
914
1041
|
}).pipe(Effect.andThen(inner.append(request))),
|
|
915
1042
|
});
|
|
916
1043
|
}),
|
|
917
|
-
).pipe(Layer.provide(options.
|
|
1044
|
+
).pipe(Layer.provide(options.threadStore));
|
|
918
1045
|
|
|
919
|
-
|
|
1046
|
+
// RUN-036: certification uses the default-none Tool failure observer. Trusted application
|
|
1047
|
+
// reporting adds no durable transition and is verified separately from adapter certification.
|
|
1048
|
+
const environment = Layer.mergeAll(
|
|
920
1049
|
options.submissionLedger,
|
|
921
1050
|
capturingStore,
|
|
922
1051
|
options.wakeScheduler ?? WakeScheduler.layerNoop,
|
|
923
|
-
|
|
1052
|
+
DurableRuntimeFailpointTestControl.layer,
|
|
924
1053
|
ToolReconciler.uncertain,
|
|
925
1054
|
DurableRuntimeConfig.layer({
|
|
926
1055
|
deploymentId: Schema.decodeSync(DeploymentId)("deployment-certification"),
|
|
@@ -930,7 +1059,6 @@ export const certifyDurableAdapters = <LedgerE = never, StoreE = never>(
|
|
|
930
1059
|
abortPollInterval: Duration.millis(50),
|
|
931
1060
|
}),
|
|
932
1061
|
);
|
|
933
|
-
const environment = DurableAgentRuntime.layer.pipe(Layer.provideMerge(support));
|
|
934
1062
|
|
|
935
1063
|
const leaseAdvance = Duration.millis(
|
|
936
1064
|
Duration.toMillis(options.ownershipLeaseDuration ?? DEFAULT_OWNERSHIP_LEASE_DURATION) + 1_000,
|
|
@@ -941,6 +1069,7 @@ export const certifyDurableAdapters = <LedgerE = never, StoreE = never>(
|
|
|
941
1069
|
|
|
942
1070
|
// Tier 2 — coordinator failpoint convergence sweep.
|
|
943
1071
|
const tier2: Array<CertificationSweepResult> = [];
|
|
1072
|
+
|
|
944
1073
|
for (const scenario of CERTIFICATION_SCENARIOS) {
|
|
945
1074
|
for (const location of DurableRuntimeFailpointLocation.literals) {
|
|
946
1075
|
tier2.push(yield* runSweepCell(scenario, location, batchProducers, leaseAdvance));
|
|
@@ -955,13 +1084,14 @@ export const certifyDurableAdapters = <LedgerE = never, StoreE = never>(
|
|
|
955
1084
|
const tier3 = yield* resolveTierThree(capabilities.durability, options);
|
|
956
1085
|
|
|
957
1086
|
const generatedAt = yield* nowUtc;
|
|
1087
|
+
|
|
958
1088
|
const ok =
|
|
959
1089
|
tier1.every((result) => result.status === "passed") &&
|
|
960
1090
|
tier2.every((result) => result.status !== "failed") &&
|
|
961
1091
|
tier3.cases.every((result) => result.status === "passed");
|
|
962
1092
|
|
|
963
1093
|
return CertificationReport.make({
|
|
964
|
-
format: "effect-agent/certification@
|
|
1094
|
+
format: "effect-agent/certification@2",
|
|
965
1095
|
adapter: CertifiedAdapterIdentity.make({
|
|
966
1096
|
name: options.adapter.name,
|
|
967
1097
|
...(options.adapter.version === undefined ? {} : { version: options.adapter.version }),
|
|
@@ -972,6 +1102,12 @@ export const certifyDurableAdapters = <LedgerE = never, StoreE = never>(
|
|
|
972
1102
|
tier2,
|
|
973
1103
|
tier3,
|
|
974
1104
|
ok,
|
|
1105
|
+
fullyCertified:
|
|
1106
|
+
ok &&
|
|
1107
|
+
capabilities.durability !== "non-durable" &&
|
|
1108
|
+
tier3.status === "exercised" &&
|
|
1109
|
+
tier3.cases.length > 0 &&
|
|
1110
|
+
tier3.cases.every((result) => result.suite === "real-loss"),
|
|
975
1111
|
});
|
|
976
1112
|
});
|
|
977
1113
|
|