@effect-agent/platform-cloudflare 0.1.0-beta.69 → 0.1.0-beta.71

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/Alarm.mjs CHANGED
@@ -6,7 +6,7 @@ import "@effect-agent/thread/AgentRegistration";
6
6
  import { DurableAgentRuntime } from "@effect-agent/thread/DurableAgentRuntime";
7
7
  import { SubmissionLedger } from "@effect-agent/thread/SubmissionLedger";
8
8
  import { ThreadProjectionMaintenance, drainDue } from "@effect-agent/thread/ThreadProjectionMaintenance";
9
- import { Cause, Clock, Context, DateTime, Effect, Exit, Fiber, Layer, Option, Random, Ref, Schema, Semaphore, Stream } from "effect";
9
+ import { Cause, Clock, Context, DateTime, Deferred, Effect, Exit, Fiber, Layer, Option, Random, Ref, Schema, Semaphore, Stream } from "effect";
10
10
  //#region src/Alarm.ts
11
11
  var Alarm_exports = /* @__PURE__ */ __exportAll({
12
12
  DurableAlarmError: () => DurableAlarmError,
@@ -307,14 +307,26 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
307
307
  activeAtStart
308
308
  };
309
309
  }));
310
- const delivery = yield* Effect.forkChild(messages.drain);
310
+ const stopDelivery = yield* Deferred.make();
311
+ const delivery = yield* Effect.forkChild(Effect.gen(function* () {
312
+ while (true) {
313
+ const stopping = yield* Deferred.isDone(stopDelivery);
314
+ yield* messages.drain;
315
+ if (stopping) return;
316
+ const next = yield* messages.pendingDeadline;
317
+ const now = yield* Clock.currentTimeMillis;
318
+ const delay = Option.isSome(next) ? Math.min(config.wakeScanInterval, Math.max(1, next.value - now)) : config.wakeScanInterval;
319
+ yield* Effect.raceFirst(Deferred.await(stopDelivery), Effect.sleep(delay));
320
+ }
321
+ }));
322
+ const finishDelivery = Deferred.succeed(stopDelivery, void 0).pipe(Effect.andThen(Fiber.join(delivery)));
311
323
  const projected = yield* Effect.exit(drainDue.pipe(Effect.provideService(ThreadProjectionMaintenance, projection)));
312
324
  if (Exit.isFailure(projected) && Cause.hasInterrupts(projected.cause)) return yield* Effect.failCause(projected.cause);
313
325
  const deadline = yield* publication.pendingDeadline;
314
326
  if (started._tag === "Actionable" || Option.isSome(deadline) && deadline.value <= (yield* Clock.currentTimeMillis)) yield* publication.drain;
315
327
  const pending = yield* publication.pendingDeadline;
316
328
  if (started._tag === "CaughtUp" || Option.isSome(pending)) {
317
- yield* Fiber.join(delivery);
329
+ yield* finishDelivery;
318
330
  if (Exit.isFailure(projected)) return yield* Effect.failCause(projected.cause);
319
331
  yield* failpoint.hit("maintenance:finish:before");
320
332
  const disposition = yield* mutations.withSnapshot((active) => Effect.gen(function* () {
@@ -343,7 +355,7 @@ var ThreadMaintenance = class ThreadMaintenance extends Context.Service()("@effe
343
355
  }
344
356
  const recovered = yield* runtime.runRecovery;
345
357
  const settlement = yield* runtime.processThreadHead(identity.threadId, { yieldAfter });
346
- yield* Fiber.join(delivery);
358
+ yield* finishDelivery;
347
359
  if (Exit.isFailure(projected)) return yield* Effect.failCause(projected.cause);
348
360
  const remaining = yield* Stream.runCollect(ledger.scanNonterminal);
349
361
  const reports = new Map(recovered.map((report) => [report.submissionId, report]));
@@ -1 +1 @@
1
- {"version":3,"file":"Alarm.mjs","names":[],"sources":["../src/Alarm.ts"],"sourcesContent":["import { type DurableBindingFailure } from \"@effect-agent/thread/AgentRegistration\";\nimport {\n DurableAgentRuntime,\n type DurableWorkerFailure,\n type RecoveryReport,\n} from \"@effect-agent/thread/DurableAgentRuntime\";\nimport { SubmissionLedger, type SubmissionSnapshot } from \"@effect-agent/thread/SubmissionLedger\";\nimport {\n ThreadProjectionMaintenance,\n drainDue,\n type ThreadProjectionError,\n} from \"@effect-agent/thread/ThreadProjectionMaintenance\";\nimport {\n Cause,\n Clock,\n Context,\n DateTime,\n Effect,\n Exit,\n Fiber,\n Layer,\n Option,\n Random,\n Ref,\n Schema,\n Semaphore,\n Stream,\n} from \"effect\";\n\nimport { ThreadObjectIdentity, DurableObjectContext } from \"./CloudflareBindings.ts\";\nimport { CloudflareDurableRuntimeConfig } from \"./CloudflareConfig.ts\";\nimport { safeCauseMessage } from \"./internal/boundary.ts\";\n\n/**\n * The single multiplexed Durable Object alarm (decision D-P6-2). A Durable Object has ONE\n * alarm slot; every cadence the Node host ran on fibers (wake scan, lease expiry, settlement\n * and abort re-checks, retry backoff) multiplexes into one idempotent maintenance pass, and\n * the slot always holds the EARLIEST deadline any caller asked for.\n *\n * The alarm invariant (plan §1.4): every committed actionable mutation carries a newer durable\n * maintenance generation and a committed alarm. Stable externally-driven waits may be\n * nonterminal without retaining an alarm; their resolving mutation advances the generation and\n * restores the alarm atomically.\n */\n\n/** The Durable Object alarm API failed; surfaces on host entry points as a typed refusal. */\nexport class DurableAlarmError extends Schema.TaggedError<DurableAlarmError>()(\n \"DurableAlarmError\",\n {\n operation: Schema.String,\n message: Schema.String,\n cause: Schema.optionalKey(Schema.Defect()),\n },\n) {}\n\nconst alarmFailure =\n (operation: string) =>\n (cause: unknown): DurableAlarmError =>\n DurableAlarmError.make({\n operation,\n message: safeCauseMessage(cause, \"The Cloudflare alarm API failed without a diagnostic\"),\n cause,\n });\n\n/** `ctx.storage` alarm slot as an Effect service; storage is truth, never a memory field. */\nexport class DurableAlarmService extends Context.Service<\n DurableAlarmService,\n {\n /** The scheduled deadline in epoch milliseconds, if any. */\n readonly scheduled: Effect.Effect<Option.Option<number>, DurableAlarmError>;\n /** Replace the slot with this deadline. */\n readonly scheduleAt: (epochMillis: number) => Effect.Effect<void, DurableAlarmError>;\n /** Keep the EARLIER of the existing deadline and this one (the multiplexing rule). */\n readonly ensureScheduledBy: (epochMillis: number) => Effect.Effect<void, DurableAlarmError>;\n /**\n * Arm an immediate alarm (the durable, coalescing local wake) — DEFERRED while a\n * maintenance pass is executing. Workerd cancels an in-flight alarm handler when a new\n * EARLIER deadline is written during its execution (`requestScheduledAlarm`), and the\n * maintenance pass runs INSIDE the alarm handler: an immediate wake landing mid-pass\n * (a routed port mutation, a sibling's `wake()`, the coordinator's own local notify)\n * would kill the running Attempt — manufacturing an ownership loss no real eviction\n * caused, and routing open uncertain-class Tool Calls into spurious Unknown Outcomes.\n * Deferral is contract-safe: wakes are droppable hints, every mutating entry point\n * pre-arms BEFORE its first durable mutation (the alarm invariant never rests on this\n * call). The pass's durable generation check observes any racing mutation, so the\n * in-memory hint does not need to be flushed after a stable wait is acknowledged.\n */\n readonly scheduleNow: Effect.Effect<void, DurableAlarmError>;\n /**\n * Run one maintenance pass with wake deferral (see `scheduleNow`). Calls made while `body`\n * executes are droppable promptness hints; correctness rests on the durable generation.\n */\n readonly withWakesDeferred: <A, E, R>(body: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;\n /** Clear the slot; correctness-sensitive clears live in maintenance generation transactions. */\n readonly cancel: Effect.Effect<void, DurableAlarmError>;\n }\n>()(\"@effect-agent/platform-cloudflare/DurableAlarmService\") {\n static readonly layer: Layer.Layer<DurableAlarmService, never, DurableObjectContext> =\n Layer.effect(DurableAlarmService)(\n Effect.gen(function* () {\n const { ctx } = yield* DurableObjectContext;\n /**\n * In-memory pass bookkeeping — a pure CACHE, never state: a fresh incarnation has no\n * running pass, and a deferred wake lost to eviction was only ever a promptness hint\n * on top of the already-committed pre-armed alarm.\n */\n const runningPasses = yield* Ref.make(0);\n\n const scheduled = Effect.tryPromise({\n try: () => ctx.storage.getAlarm(),\n catch: alarmFailure(\"get alarm\"),\n }).pipe(\n Effect.map((deadline) =>\n deadline === null ? Option.none<number>() : Option.some(deadline),\n ),\n );\n\n const scheduleAt = (epochMillis: number) =>\n Effect.tryPromise({\n try: () => ctx.storage.setAlarm(epochMillis),\n catch: alarmFailure(\"set alarm\"),\n });\n\n const ensureScheduledBy = (epochMillis: number) =>\n scheduled.pipe(\n Effect.flatMap((existing) =>\n Option.isSome(existing) && existing.value <= epochMillis\n ? Effect.void\n : scheduleAt(epochMillis),\n ),\n );\n\n const armNow = Clock.currentTimeMillis.pipe(\n Effect.flatMap((now) => ensureScheduledBy(now)),\n );\n\n const scheduleNow = Ref.get(runningPasses).pipe(\n Effect.flatMap((passes) => (passes > 0 ? Effect.void : armNow)),\n );\n\n const withWakesDeferred = <A, E, R>(body: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>\n Ref.update(runningPasses, (passes) => passes + 1).pipe(\n Effect.andThen(body),\n Effect.ensuring(Ref.update(runningPasses, (passes) => passes - 1)),\n );\n\n const cancel = Effect.tryPromise({\n try: () => ctx.storage.deleteAlarm(),\n catch: alarmFailure(\"delete alarm\"),\n });\n\n return DurableAlarmService.of({\n scheduled,\n scheduleAt,\n ensureScheduledBy,\n scheduleNow,\n withWakesDeferred,\n cancel,\n });\n }),\n );\n}\n\n/** What one maintenance pass did — auditable evidence mirroring `NodeDurableHost`'s report. */\nexport class MaintenancePassReport extends Schema.Class<MaintenancePassReport>(\n \"@effect-agent/platform-cloudflare/MaintenancePassReport\",\n)({\n /** `caught-up` ran no runtime work (publication may be pending); `actionable` ran recovery. */\n phase: Schema.Literals([\"caught-up\", \"actionable\"]),\n /** Recovery decisions executed (or deferred) BEFORE any new claim in this pass. */\n recovered: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),\n /** Whether the head Attempt settled. Joined input may settle with that head. */\n settled: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),\n /** Submissions still nonterminal after the pass (suspended/unknown lanes stay honest). */\n nonterminal: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),\n /** `rearmed` for dirty/autonomous work, `cleared` for stable waits or settlement. */\n alarm: Schema.Literals([\"rearmed\", \"cleared\"]),\n}) {}\n\n/** Fault boundaries around every maintenance-owned durable mutation. */\nexport type ThreadMaintenanceFailpointLocation =\n | \"maintenance:dirty:before\"\n | \"maintenance:dirty:after\"\n | \"maintenance:mutation:armed\"\n | \"maintenance:mutation:finished\"\n | \"maintenance:ensure:before\"\n | \"maintenance:ensure:after\"\n | \"maintenance:begin:before\"\n | \"maintenance:begin:after\"\n | \"maintenance:finish:before\"\n | \"maintenance:finish:after\";\n\nexport type ThreadMaintenanceFailpointHandler = (\n location: ThreadMaintenanceFailpointLocation,\n) => Effect.Effect<void>;\n\n/** Test-only fault authority; production uses the inert layer. */\nexport class ThreadMaintenanceFailpoint extends Context.Service<\n ThreadMaintenanceFailpoint,\n {\n readonly hit: ThreadMaintenanceFailpointHandler;\n }\n>()(\"@effect-agent/platform-cloudflare/ThreadMaintenanceFailpoint\") {\n static readonly layer = Layer.succeed(this)({ hit: () => Effect.void });\n}\n\n/**\n * Durable host publication of canonical records and ledger approval/abort/resolution intents.\n * The host owns schema-versioned cursors, destination idempotency and acknowledgement. Delivery\n * is at least once. Hooks must not write the alarm slot or mutate the supplied raw source ports.\n *\n * `invalidate`, `prepareGeneration` and `pendingDeadline` must be bounded local operations.\n * `prepareGeneration` durably invalidates a scan only when its generation changes; repeated\n * calls must preserve partial scan progress. It runs with no source mutation in flight.\n * `drain` performs bounded delivery and persists retries before returning. A pending deadline\n * defers runtime recovery/Attempts, allowing committed host publications to drain first.\n * Unexpected hook failures leave the prearmed generation for retry. Hooks acquire per-call\n * resources with Effect.scoped; Layer construction owns incarnation resources (eviction need\n * not run finalizers). Do not hold a local hook behind network I/O or call back into producers.\n */\nexport interface ThreadPublicationService {\n readonly invalidate: Effect.Effect<void, DurableAlarmError>;\n readonly prepareGeneration: (generation: bigint) => Effect.Effect<void, DurableAlarmError>;\n readonly drain: Effect.Effect<void, DurableAlarmError>;\n readonly pendingDeadline: Effect.Effect<Option.Option<number>, DurableAlarmError>;\n}\n\n/** Opt in with `ThreadObject.layer(registrations, { publication: Layer.effect(ThreadPublication)(...) })`. */\nexport class ThreadPublication extends Context.Service<\n ThreadPublication,\n ThreadPublicationService\n>()(\"@effect-agent/platform-cloudflare/ThreadPublication\") {\n static readonly layer = Layer.succeed(this)({\n invalidate: Effect.void,\n prepareGeneration: () => Effect.void,\n drain: Effect.void,\n pendingDeadline: Effect.succeed(Option.none()),\n });\n}\n\n/**\n * Host-assembled message recovery, supplied by ThreadObject.layer even when the application\n * rebuilds ThreadMaintenance. These obligations outlive source Runs and never defer a ready\n * source Attempt while a destination is processing an accepted message.\n */\nexport const ThreadMessageDelivery = Context.Reference<{\n readonly drain: Effect.Effect<void, DurableAlarmError>;\n readonly pendingDeadline: Effect.Effect<Option.Option<number>, DurableAlarmError>;\n}>(\"@effect-agent/platform-cloudflare/ThreadMessageDelivery\", {\n defaultValue: () => ({ drain: Effect.void, pendingDeadline: Effect.succeed(Option.none()) }),\n});\n\nconst earliestDeadline = (\n left: Option.Option<number>,\n right: Option.Option<number>,\n): Option.Option<number> =>\n Option.isSome(left)\n ? Option.isSome(right)\n ? Option.some(Math.min(left.value, right.value))\n : left\n : right;\n\n/** @internal A committed source operation must not become a failed operation because delivery failed. */\nexport const publishCommitted = Effect.gen(function* () {\n const publication = yield* ThreadPublication;\n\n yield* publication.invalidate.pipe(Effect.andThen(publication.drain));\n}).pipe(\n Effect.catchCause((cause) =>\n Cause.hasInterrupts(cause)\n ? Effect.interrupt\n : Effect.logError(\"Thread publication deferred after source commit\", cause),\n ),\n);\n\nconst MaintenanceGeneration = Schema.BigIntFromString.check(\n Schema.isGreaterThanOrEqualToBigInt(0n),\n);\n\n/** Versioned, platform-private maintenance state stored through Durable Object KV. */\nclass ThreadMaintenanceState extends Schema.Class<ThreadMaintenanceState>(\n \"@effect-agent/platform-cloudflare/ThreadMaintenanceState\",\n)({\n schemaVersion: Schema.Literal(1),\n dirty: MaintenanceGeneration,\n processed: MaintenanceGeneration,\n nonterminal: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),\n}) {}\n\nconst MAINTENANCE_STATE_KEY = \"effect-agent:thread-maintenance:v1\";\nconst decodeMaintenanceState = Schema.decodeUnknownSync(ThreadMaintenanceState);\nconst encodeMaintenanceState = Schema.encodeSync(ThreadMaintenanceState);\n\nconst initialMaintenanceState = (): ThreadMaintenanceState =>\n ThreadMaintenanceState.make({\n schemaVersion: 1,\n // Bootstrap Objects created by the pre-generation release without scanning the ledger in\n // the constructor. One useful pass classifies and acknowledges any existing obligation.\n dirty: 1n,\n processed: 0n,\n nonterminal: 0,\n });\n\nconst readMaintenanceState = async (\n transaction: DurableObjectTransaction,\n): Promise<{ readonly state: ThreadMaintenanceState; readonly initialized: boolean }> => {\n const encoded = await transaction.get(MAINTENANCE_STATE_KEY);\n\n return encoded === undefined\n ? { state: initialMaintenanceState(), initialized: false }\n : { state: decodeMaintenanceState(encoded), initialized: true };\n};\n\nconst ensureTransactionAlarmBy = async (\n transaction: DurableObjectTransaction,\n deadline: number,\n): Promise<void> => {\n const scheduled = await transaction.getAlarm();\n\n if (scheduled === null || scheduled > deadline) {\n await transaction.setAlarm(deadline);\n }\n};\n\nconst stableExternalWait = (\n snapshot: SubmissionSnapshot,\n reports: ReadonlyMap<string, RecoveryReport>,\n): boolean => {\n const decision = reports.get(snapshot.submissionId)?.decision._tag;\n\n // An accepted abort still owes cleanup/settlement even if its claim was deferred this pass.\n if (decision === \"SettleAborted\") return false;\n switch (snapshot.state) {\n case \"suspended\":\n case \"joined\":\n return true;\n case \"unknown\":\n return decision === \"AwaitUnknownResolution\" || decision === \"MarkUnknown\";\n case \"admitted\":\n return reports.get(snapshot.submissionId)?.decision._tag === \"AwaitParentEstablishment\";\n case \"input-applied\":\n case \"joining\":\n case \"ready\":\n case \"running\":\n case \"settled\":\n case \"terminalizing\":\n return false;\n }\n};\n\n/**\n * Shared prearm/acknowledgement boundary for ingress and runtime-owned producers.\n * `ThreadObject.layer` provides this same instance in its Services. Rebuilt runtime/maintenance\n * Layers must reuse that instance; a second gate cannot observe the native producers' activity.\n */\nexport class ThreadMutationGate extends Context.Service<\n ThreadMutationGate,\n {\n readonly withMutation: <A, E, R>(\n body: Effect.Effect<A, E, R>,\n ) => Effect.Effect<A, E | DurableAlarmError, R>;\n readonly withSnapshot: <A, E, R>(\n body: (active: number) => Effect.Effect<A, E, R>,\n ) => Effect.Effect<A, E, R>;\n }\n>()(\"@effect-agent/platform-cloudflare/internal/ThreadMutationGate\") {\n static readonly layer = Layer.effect(this)(\n Effect.gen(function* () {\n const { ctx } = yield* DurableObjectContext;\n const config = yield* CloudflareDurableRuntimeConfig;\n const failpoint = yield* ThreadMaintenanceFailpoint;\n // A fresh incarnation has no live mutations; durable generations survive eviction.\n const activeMutations = yield* Ref.make(0);\n const generationGate = yield* Semaphore.make(1);\n const minimumAlarmDelay = Math.max(1, Math.ceil(config.alarmBackoffBase / 2));\n\n const runTransaction = <A>(operation: string, transaction: () => Promise<A>) =>\n Effect.tryPromise({ try: transaction, catch: alarmFailure(operation) });\n\n const beginMutation = Effect.fn(\"ThreadMaintenance.beginMutation\")(function* () {\n yield* failpoint.hit(\"maintenance:dirty:before\");\n const now = yield* Clock.currentTimeMillis;\n\n yield* runTransaction(\"advance maintenance generation\", () =>\n ctx.storage.transaction(async (transaction) => {\n const { state } = await readMaintenanceState(transaction);\n\n const next = ThreadMaintenanceState.make({\n ...state,\n dirty: state.dirty + 1n,\n });\n\n await transaction.put(MAINTENANCE_STATE_KEY, encodeMaintenanceState(next));\n // The earliest configured retry bounds a newly actionable mutation without relying\n // on its best-effort immediate wake hint.\n await ensureTransactionAlarmBy(transaction, now + minimumAlarmDelay);\n }),\n );\n yield* failpoint.hit(\"maintenance:dirty:after\");\n yield* Ref.update(activeMutations, (active) => active + 1);\n });\n\n const endMutation = generationGate.withPermit(\n Ref.update(activeMutations, (active) => Math.max(0, active - 1)),\n );\n\n const withMutation = <A, E, R>(\n body: Effect.Effect<A, E, R>,\n ): Effect.Effect<A, E | DurableAlarmError, R> =>\n Effect.acquireUseRelease(\n generationGate.withPermit(beginMutation()),\n () =>\n failpoint.hit(\"maintenance:mutation:armed\").pipe(\n Effect.andThen(body),\n Effect.tap(() => failpoint.hit(\"maintenance:mutation:finished\")),\n ),\n () => endMutation,\n );\n\n return ThreadMutationGate.of({\n withMutation,\n withSnapshot: (body) =>\n generationGate.withPermit(Effect.flatMap(Ref.get(activeMutations), body)),\n });\n }),\n );\n}\n\nexport type MaintenancePassFailure =\n | DurableWorkerFailure\n | DurableBindingFailure\n | DurableAlarmError\n | ThreadProjectionError;\n\n/**\n * Incremental, quiescent maintenance over a durable dirty/processed generation (issue #93).\n *\n * `pass` = generation snapshot/pre-arm → recovery → one head Attempt → generation acknowledgement:\n *\n * 1. One storage transaction reads dirty/processed and re-arms before work. A caught-up forced\n * alarm takes an O(1) path without recovery, ledger scans, or canonical-history reads.\n * 2. Recovery strictly precedes a new claim. One head Attempt advances the lane and requests\n * a safe yield after ten minutes. The whole event has a fourteen-minute cooperative timeout.\n * 3. The final transaction acknowledges only the generation observed at pass start. A racing\n * mutation therefore remains `dirty > processed` and retains its atomically-established alarm.\n * 4. Stable external waits acknowledge and clear. Autonomous retry, indeterminate, and lease\n * recovery states leave their generation dirty and retain bounded backoff rearming.\n */\nexport class ThreadMaintenance extends Context.Service<\n ThreadMaintenance,\n {\n /** One idempotent maintenance pass; failures propagate so workerd retries the alarm. */\n readonly pass: Effect.Effect<MaintenancePassReport, MaintenancePassFailure>;\n /**\n * Constructor gate: initialize/inspect only the O(1) maintenance record and ensure a dirty\n * generation has an alarm. It never scans the ledger or canonical history.\n */\n readonly ensureAlarm: Effect.Effect<void, MaintenancePassFailure>;\n /**\n * Serialize the pre-arm boundary with pass acknowledgement, advance the durable dirty\n * generation and arm the alarm in one transaction BEFORE running the caller's mutation.\n * A pass cannot acknowledge while that mutation remains in flight.\n */\n readonly withMutation: <A, E, R>(\n body: Effect.Effect<A, E, R>,\n ) => Effect.Effect<A, E | DurableAlarmError, R>;\n }\n>()(\"@effect-agent/platform-cloudflare/ThreadMaintenance\") {\n static readonly layer: Layer.Layer<\n ThreadMaintenance,\n never,\n | ThreadMutationGate\n | ThreadPublication\n | ThreadProjectionMaintenance\n | DurableAgentRuntime\n | SubmissionLedger\n | DurableAlarmService\n | ThreadMaintenanceFailpoint\n | CloudflareDurableRuntimeConfig\n | ThreadObjectIdentity\n | DurableObjectContext\n > = Layer.effect(ThreadMaintenance)(\n Effect.gen(function* () {\n const runtime = yield* DurableAgentRuntime;\n const ledger = yield* SubmissionLedger;\n const alarm = yield* DurableAlarmService;\n const config = yield* CloudflareDurableRuntimeConfig;\n const identity = yield* ThreadObjectIdentity;\n const { ctx } = yield* DurableObjectContext;\n const failpoint = yield* ThreadMaintenanceFailpoint;\n\n /**\n * Consecutive no-progress passes — an in-memory CACHE, not state: a fresh incarnation\n * restarts at zero and merely re-arms sooner than a long-lived one would have.\n */\n const stalls = yield* Ref.make(0);\n const mutations = yield* ThreadMutationGate;\n const publication = yield* ThreadPublication;\n const projection = yield* ThreadProjectionMaintenance;\n const messages = yield* ThreadMessageDelivery;\n\n // A broken disposable index still needs a retry alarm and must not prevent startup.\n const projectionDeadline = projection.pendingDeadline.pipe(\n Effect.catchCause((cause) =>\n Cause.hasInterrupts(cause)\n ? Effect.failCause(cause)\n : Effect.logError(\"Thread projection deadline unavailable\", cause).pipe(\n Effect.as(Option.some(0)),\n ),\n ),\n );\n\n const pendingDeadline = Effect.gen(function* () {\n return earliestDeadline(\n earliestDeadline(yield* publication.pendingDeadline, yield* messages.pendingDeadline),\n yield* projectionDeadline,\n );\n });\n\n const maintenancePassGate = yield* Semaphore.make(1);\n const minimumAlarmDelay = Math.max(1, Math.ceil(config.alarmBackoffBase / 2));\n\n const runTransaction = <A>(operation: string, transaction: () => Promise<A>) =>\n Effect.tryPromise({ try: transaction, catch: alarmFailure(operation) });\n\n const ensureAlarm = Effect.fn(\"ThreadMaintenance.ensureAlarm\")(function* () {\n yield* failpoint.hit(\"maintenance:ensure:before\");\n const now = yield* Clock.currentTimeMillis;\n\n yield* runTransaction(\"ensure maintenance alarm\", () =>\n ctx.storage.transaction(async (transaction) => {\n const { state, initialized } = await readMaintenanceState(transaction);\n\n if (!initialized) {\n await transaction.put(MAINTENANCE_STATE_KEY, encodeMaintenanceState(state));\n }\n if (state.dirty > state.processed) {\n await ensureTransactionAlarmBy(transaction, now + config.wakeScanInterval);\n }\n }),\n );\n const deadline = yield* pendingDeadline;\n\n if (Option.isSome(deadline)) {\n yield* runTransaction(\"ensure publication alarm\", () =>\n ctx.storage.transaction((transaction) =>\n ensureTransactionAlarmBy(\n transaction,\n Math.max(now + minimumAlarmDelay, deadline.value),\n ),\n ),\n );\n }\n yield* failpoint.hit(\"maintenance:ensure:after\");\n });\n\n const beginPass = Effect.fn(\"ThreadMaintenance.beginPass\")(function* () {\n yield* failpoint.hit(\"maintenance:begin:before\");\n const now = yield* Clock.currentTimeMillis;\n\n const result = yield* runTransaction(\"begin maintenance pass\", () =>\n ctx.storage.transaction(async (transaction) => {\n const { state, initialized } = await readMaintenanceState(transaction);\n\n if (!initialized) {\n await transaction.put(MAINTENANCE_STATE_KEY, encodeMaintenanceState(state));\n }\n if (state.processed >= state.dirty) {\n // Prearm even a publication-only pass before invoking any host hook.\n await ensureTransactionAlarmBy(transaction, now + minimumAlarmDelay);\n\n return { _tag: \"CaughtUp\" as const, nonterminal: state.nonterminal };\n }\n // Pre-arm the earliest retry before recovery. A successful finish may move this slot\n // LATER to its bounded backoff, which does not cancel the running handler.\n await ensureTransactionAlarmBy(transaction, now + minimumAlarmDelay);\n\n return {\n _tag: \"Actionable\" as const,\n generation: state.dirty,\n nonterminal: state.nonterminal,\n };\n }),\n );\n\n yield* failpoint.hit(\"maintenance:begin:after\");\n\n return result;\n });\n\n const rearmDelay = Effect.fn(\"ThreadMaintenance.rearmDelay\")(function* (progressed: boolean) {\n const priorStalls = yield* Ref.getAndUpdate(stalls, (count) =>\n progressed ? 0 : count + 1,\n );\n\n if (progressed) return config.alarmBackoffBase;\n const exponent = Math.min(priorStalls, 30);\n const backoff = Math.min(config.alarmBackoffCap, config.alarmBackoffBase * 2 ** exponent);\n const jitter = yield* Random.next;\n // Full jitter over [backoff/2, backoff]: desynchronizes retry storms without ever\n // waiting longer than the deterministic bound.\n const jittered = Math.ceil(backoff / 2 + (backoff / 2) * jitter);\n\n return Math.min(jittered, config.wakeScanInterval);\n });\n\n const pass = Effect.fn(\"ThreadMaintenance.pass\")(function* (\n yieldAfter: DateTime.Utc,\n ): Effect.fn.Return<MaintenancePassReport, MaintenancePassFailure> {\n const annotate = (report: MaintenancePassReport) =>\n Effect.annotateCurrentSpan({\n phase: report.phase,\n recovered: report.recovered,\n settled: report.settled,\n nonterminal: report.nonterminal,\n alarm: report.alarm,\n }).pipe(Effect.as(report));\n\n const started = yield* mutations.withSnapshot((activeAtStart) =>\n Effect.gen(function* () {\n const generation = yield* beginPass();\n\n if (generation._tag === \"Actionable\" && activeAtStart === 0) {\n // The gate excludes a producer starting between the snapshot and certification.\n yield* publication.prepareGeneration(generation.generation);\n }\n\n return { ...generation, activeAtStart };\n }),\n );\n\n // Run one bounded message wave beside source work. Slow destination admission or\n // status RPCs cannot consume the source Attempt's execution window. Join before\n // acknowledging the alarm so the final deadline includes all delivery mutations.\n const delivery = yield* Effect.forkChild(messages.drain);\n\n // Capture derived-index failures until canonical work has had its turn. Interruption\n // still stops the event; ordinary failures and defects retain the prearmed generation.\n const projected = yield* Effect.exit(\n drainDue.pipe(Effect.provideService(ThreadProjectionMaintenance, projection)),\n );\n\n if (Exit.isFailure(projected) && Cause.hasInterrupts(projected.cause))\n return yield* Effect.failCause(projected.cause);\n const deadline = yield* publication.pendingDeadline;\n\n if (\n started._tag === \"Actionable\" ||\n (Option.isSome(deadline) && deadline.value <= (yield* Clock.currentTimeMillis))\n ) {\n yield* publication.drain;\n }\n const pending = yield* publication.pendingDeadline;\n\n if (started._tag === \"CaughtUp\" || Option.isSome(pending)) {\n yield* Fiber.join(delivery);\n if (Exit.isFailure(projected)) return yield* Effect.failCause(projected.cause);\n yield* failpoint.hit(\"maintenance:finish:before\");\n\n const disposition = yield* mutations.withSnapshot((active) =>\n Effect.gen(function* () {\n // Re-read under the producer gate: a concurrent append/host mutation cannot be\n // cleared using a stale empty deadline. Dirty generations bound all producer races.\n const latest = yield* pendingDeadline;\n const now = yield* Clock.currentTimeMillis;\n\n return yield* runTransaction(\"finish publication pass\", () =>\n ctx.storage.transaction(async (transaction) => {\n const { state } = await readMaintenanceState(transaction);\n\n const nativeDeadline =\n active > 0 || state.dirty > state.processed\n ? now + config.wakeScanInterval\n : Infinity;\n\n const next = Option.isSome(latest)\n ? Math.min(nativeDeadline, latest.value)\n : nativeDeadline;\n\n if (Number.isFinite(next)) {\n await transaction.setAlarm(Math.max(now + minimumAlarmDelay, next));\n\n return \"rearmed\" as const;\n }\n await transaction.deleteAlarm();\n\n return \"cleared\" as const;\n }),\n );\n }),\n );\n\n yield* failpoint.hit(\"maintenance:finish:after\");\n\n return yield* annotate(\n MaintenancePassReport.make({\n phase: \"caught-up\",\n recovered: 0,\n settled: 0,\n nonterminal: started.nonterminal,\n alarm: disposition,\n }),\n );\n }\n // Step 2 — reconciliation strictly precedes new work in this pass (exit gate).\n const recovered: ReadonlyArray<RecoveryReport> = yield* runtime.runRecovery;\n // One head Attempt per event. The runtime yields after a committed turn when the\n // soft deadline is reached; queued followers belong to a subsequent alarm.\n const settlement = yield* runtime.processThreadHead(identity.threadId, { yieldAfter });\n\n yield* Fiber.join(delivery);\n if (Exit.isFailure(projected)) return yield* Effect.failCause(projected.cause);\n // Observe residual state before acknowledging this exact pass-start generation.\n const remaining = yield* Stream.runCollect(ledger.scanNonterminal);\n const reports = new Map(recovered.map((report) => [report.submissionId, report]));\n const head = remaining[0];\n const headWaiting = head !== undefined && stableExternalWait(head, reports);\n\n const autonomous = remaining.some((snapshot, index) => {\n // FIFO followers cannot execute through a stable external wait. Only plain queued\n // input is dormant here; admission repairs and accepted aborts still need a pass.\n if (\n index > 0 &&\n headWaiting &&\n snapshot.state === \"ready\" &&\n reports.get(snapshot.submissionId)?.decision._tag === \"ApplyInput\"\n )\n return false;\n\n return !stableExternalWait(snapshot, reports);\n });\n\n const progressed =\n Option.isSome(settlement) ||\n recovered.some((report) => report.disposition === \"repaired\");\n\n const delay = autonomous ? yield* rearmDelay(progressed) : 0;\n const now = yield* Clock.currentTimeMillis;\n\n yield* failpoint.hit(\"maintenance:finish:before\");\n\n const alarmDisposition = yield* mutations.withSnapshot((active) =>\n Effect.gen(function* () {\n const publicationDeadline = yield* pendingDeadline;\n\n return yield* runTransaction(\"finish maintenance pass\", () =>\n ctx.storage.transaction(async (transaction) => {\n const { state } = await readMaintenanceState(transaction);\n\n // Autonomous work and in-flight mutations intentionally leave the observed\n // generation dirty. Otherwise acknowledge only the pass-start generation.\n const processed =\n autonomous || started.activeAtStart > 0 || active > 0\n ? state.processed\n : state.processed > started.generation\n ? state.processed\n : started.generation;\n\n const next = ThreadMaintenanceState.make({\n ...state,\n processed,\n nonterminal: remaining.length,\n });\n\n await transaction.put(MAINTENANCE_STATE_KEY, encodeMaintenanceState(next));\n if (autonomous) {\n // Replace the crash-fallback slot with this pass's bounded backoff. The target\n // is never earlier than the begin-pass fallback, so workerd does not cancel\n // this running alarm handler before its report/span can complete.\n await transaction.setAlarm(\n Option.isSome(publicationDeadline)\n ? Math.max(\n now + minimumAlarmDelay,\n Math.min(now + delay, publicationDeadline.value),\n )\n : now + delay,\n );\n\n return \"rearmed\" as const;\n }\n if (started.activeAtStart > 0 || active > 0 || next.dirty > next.processed) {\n // A mutation overlapped this pass's observation window or raced\n // acknowledgement. It stays dirty and its pre-armed bounded alarm survives;\n // unseen effects are never acknowledged. Do not accelerate that future alarm\n // from inside the current handler: workerd cancels a running handler when it\n // writes an earlier slot.\n await ensureTransactionAlarmBy(\n transaction,\n Option.isSome(publicationDeadline)\n ? Math.max(\n now + minimumAlarmDelay,\n Math.min(now + config.wakeScanInterval, publicationDeadline.value),\n )\n : now + config.wakeScanInterval,\n );\n\n return \"rearmed\" as const;\n }\n if (Option.isSome(publicationDeadline)) {\n await transaction.setAlarm(\n Math.max(now + minimumAlarmDelay, publicationDeadline.value),\n );\n\n return \"rearmed\" as const;\n }\n await transaction.deleteAlarm();\n\n return \"cleared\" as const;\n }),\n );\n }),\n );\n\n yield* failpoint.hit(\"maintenance:finish:after\");\n if (alarmDisposition === \"cleared\") {\n yield* Ref.set(stalls, 0);\n }\n\n return yield* annotate(\n MaintenancePassReport.make({\n phase: \"actionable\",\n recovered: recovered.length,\n settled: Option.isSome(settlement) ? 1 : 0,\n nonterminal: remaining.length,\n alarm: alarmDisposition,\n }),\n );\n });\n\n return ThreadMaintenance.of({\n // A mid-pass immediate hint is droppable; durable dirty state decides the final alarm.\n pass: Effect.gen(function* () {\n const yieldAfter = DateTime.makeUnsafe((yield* Clock.currentTimeMillis) + 10 * 60_000);\n\n return yield* alarm.withWakesDeferred(maintenancePassGate.withPermit(pass(yieldAfter)));\n }).pipe(\n // Include permit waiting, recovery and acknowledgement in the event deadline.\n // Interruption releases Attempt ownership, leaving the prearmed dirty generation\n // for recovery. It never changes the logical Run duration or settles a policy failure.\n // This cooperative timer cannot preempt synchronous CPU work or stuck finalizers.\n Effect.timeoutOrElse({\n duration: \"14 minutes\",\n orElse: () =>\n DurableAlarmError.make({\n operation: \"maintenance pass deadline\",\n message:\n \"The maintenance event exceeded its 14 minute deadline; durable recovery remains pending\",\n }),\n }),\n ),\n ensureAlarm: mutations.withSnapshot(() => ensureAlarm()),\n withMutation: (body) =>\n mutations.withMutation(\n body.pipe(\n Effect.tap(() =>\n publishCommitted.pipe(Effect.provideService(ThreadPublication, publication)),\n ),\n ),\n ),\n });\n }),\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"Alarm.mjs","names":[],"sources":["../src/Alarm.ts"],"sourcesContent":["import { type DurableBindingFailure } from \"@effect-agent/thread/AgentRegistration\";\nimport {\n DurableAgentRuntime,\n type DurableWorkerFailure,\n type RecoveryReport,\n} from \"@effect-agent/thread/DurableAgentRuntime\";\nimport { SubmissionLedger, type SubmissionSnapshot } from \"@effect-agent/thread/SubmissionLedger\";\nimport {\n ThreadProjectionMaintenance,\n drainDue,\n type ThreadProjectionError,\n} from \"@effect-agent/thread/ThreadProjectionMaintenance\";\nimport {\n Cause,\n Clock,\n Context,\n DateTime,\n Deferred,\n Effect,\n Exit,\n Fiber,\n Layer,\n Option,\n Random,\n Ref,\n Schema,\n Semaphore,\n Stream,\n} from \"effect\";\n\nimport { ThreadObjectIdentity, DurableObjectContext } from \"./CloudflareBindings.ts\";\nimport { CloudflareDurableRuntimeConfig } from \"./CloudflareConfig.ts\";\nimport { safeCauseMessage } from \"./internal/boundary.ts\";\n\n/**\n * The single multiplexed Durable Object alarm (decision D-P6-2). A Durable Object has ONE\n * alarm slot; every cadence the Node host ran on fibers (wake scan, lease expiry, settlement\n * and abort re-checks, retry backoff) multiplexes into one idempotent maintenance pass, and\n * the slot always holds the EARLIEST deadline any caller asked for.\n *\n * The alarm invariant (plan §1.4): every committed actionable mutation carries a newer durable\n * maintenance generation and a committed alarm. Stable externally-driven waits may be\n * nonterminal without retaining an alarm; their resolving mutation advances the generation and\n * restores the alarm atomically.\n */\n\n/** The Durable Object alarm API failed; surfaces on host entry points as a typed refusal. */\nexport class DurableAlarmError extends Schema.TaggedError<DurableAlarmError>()(\n \"DurableAlarmError\",\n {\n operation: Schema.String,\n message: Schema.String,\n cause: Schema.optionalKey(Schema.Defect()),\n },\n) {}\n\nconst alarmFailure =\n (operation: string) =>\n (cause: unknown): DurableAlarmError =>\n DurableAlarmError.make({\n operation,\n message: safeCauseMessage(cause, \"The Cloudflare alarm API failed without a diagnostic\"),\n cause,\n });\n\n/** `ctx.storage` alarm slot as an Effect service; storage is truth, never a memory field. */\nexport class DurableAlarmService extends Context.Service<\n DurableAlarmService,\n {\n /** The scheduled deadline in epoch milliseconds, if any. */\n readonly scheduled: Effect.Effect<Option.Option<number>, DurableAlarmError>;\n /** Replace the slot with this deadline. */\n readonly scheduleAt: (epochMillis: number) => Effect.Effect<void, DurableAlarmError>;\n /** Keep the EARLIER of the existing deadline and this one (the multiplexing rule). */\n readonly ensureScheduledBy: (epochMillis: number) => Effect.Effect<void, DurableAlarmError>;\n /**\n * Arm an immediate alarm (the durable, coalescing local wake) — DEFERRED while a\n * maintenance pass is executing. Workerd cancels an in-flight alarm handler when a new\n * EARLIER deadline is written during its execution (`requestScheduledAlarm`), and the\n * maintenance pass runs INSIDE the alarm handler: an immediate wake landing mid-pass\n * (a routed port mutation, a sibling's `wake()`, the coordinator's own local notify)\n * would kill the running Attempt — manufacturing an ownership loss no real eviction\n * caused, and routing open uncertain-class Tool Calls into spurious Unknown Outcomes.\n * Deferral is contract-safe: wakes are droppable hints, every mutating entry point\n * pre-arms BEFORE its first durable mutation (the alarm invariant never rests on this\n * call). The pass's durable generation check observes any racing mutation, so the\n * in-memory hint does not need to be flushed after a stable wait is acknowledged.\n */\n readonly scheduleNow: Effect.Effect<void, DurableAlarmError>;\n /**\n * Run one maintenance pass with wake deferral (see `scheduleNow`). Calls made while `body`\n * executes are droppable promptness hints; correctness rests on the durable generation.\n */\n readonly withWakesDeferred: <A, E, R>(body: Effect.Effect<A, E, R>) => Effect.Effect<A, E, R>;\n /** Clear the slot; correctness-sensitive clears live in maintenance generation transactions. */\n readonly cancel: Effect.Effect<void, DurableAlarmError>;\n }\n>()(\"@effect-agent/platform-cloudflare/DurableAlarmService\") {\n static readonly layer: Layer.Layer<DurableAlarmService, never, DurableObjectContext> =\n Layer.effect(DurableAlarmService)(\n Effect.gen(function* () {\n const { ctx } = yield* DurableObjectContext;\n /**\n * In-memory pass bookkeeping — a pure CACHE, never state: a fresh incarnation has no\n * running pass, and a deferred wake lost to eviction was only ever a promptness hint\n * on top of the already-committed pre-armed alarm.\n */\n const runningPasses = yield* Ref.make(0);\n\n const scheduled = Effect.tryPromise({\n try: () => ctx.storage.getAlarm(),\n catch: alarmFailure(\"get alarm\"),\n }).pipe(\n Effect.map((deadline) =>\n deadline === null ? Option.none<number>() : Option.some(deadline),\n ),\n );\n\n const scheduleAt = (epochMillis: number) =>\n Effect.tryPromise({\n try: () => ctx.storage.setAlarm(epochMillis),\n catch: alarmFailure(\"set alarm\"),\n });\n\n const ensureScheduledBy = (epochMillis: number) =>\n scheduled.pipe(\n Effect.flatMap((existing) =>\n Option.isSome(existing) && existing.value <= epochMillis\n ? Effect.void\n : scheduleAt(epochMillis),\n ),\n );\n\n const armNow = Clock.currentTimeMillis.pipe(\n Effect.flatMap((now) => ensureScheduledBy(now)),\n );\n\n const scheduleNow = Ref.get(runningPasses).pipe(\n Effect.flatMap((passes) => (passes > 0 ? Effect.void : armNow)),\n );\n\n const withWakesDeferred = <A, E, R>(body: Effect.Effect<A, E, R>): Effect.Effect<A, E, R> =>\n Ref.update(runningPasses, (passes) => passes + 1).pipe(\n Effect.andThen(body),\n Effect.ensuring(Ref.update(runningPasses, (passes) => passes - 1)),\n );\n\n const cancel = Effect.tryPromise({\n try: () => ctx.storage.deleteAlarm(),\n catch: alarmFailure(\"delete alarm\"),\n });\n\n return DurableAlarmService.of({\n scheduled,\n scheduleAt,\n ensureScheduledBy,\n scheduleNow,\n withWakesDeferred,\n cancel,\n });\n }),\n );\n}\n\n/** What one maintenance pass did — auditable evidence mirroring `NodeDurableHost`'s report. */\nexport class MaintenancePassReport extends Schema.Class<MaintenancePassReport>(\n \"@effect-agent/platform-cloudflare/MaintenancePassReport\",\n)({\n /** `caught-up` ran no runtime work (publication may be pending); `actionable` ran recovery. */\n phase: Schema.Literals([\"caught-up\", \"actionable\"]),\n /** Recovery decisions executed (or deferred) BEFORE any new claim in this pass. */\n recovered: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),\n /** Whether the head Attempt settled. Joined input may settle with that head. */\n settled: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),\n /** Submissions still nonterminal after the pass (suspended/unknown lanes stay honest). */\n nonterminal: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),\n /** `rearmed` for dirty/autonomous work, `cleared` for stable waits or settlement. */\n alarm: Schema.Literals([\"rearmed\", \"cleared\"]),\n}) {}\n\n/** Fault boundaries around every maintenance-owned durable mutation. */\nexport type ThreadMaintenanceFailpointLocation =\n | \"maintenance:dirty:before\"\n | \"maintenance:dirty:after\"\n | \"maintenance:mutation:armed\"\n | \"maintenance:mutation:finished\"\n | \"maintenance:ensure:before\"\n | \"maintenance:ensure:after\"\n | \"maintenance:begin:before\"\n | \"maintenance:begin:after\"\n | \"maintenance:finish:before\"\n | \"maintenance:finish:after\";\n\nexport type ThreadMaintenanceFailpointHandler = (\n location: ThreadMaintenanceFailpointLocation,\n) => Effect.Effect<void>;\n\n/** Test-only fault authority; production uses the inert layer. */\nexport class ThreadMaintenanceFailpoint extends Context.Service<\n ThreadMaintenanceFailpoint,\n {\n readonly hit: ThreadMaintenanceFailpointHandler;\n }\n>()(\"@effect-agent/platform-cloudflare/ThreadMaintenanceFailpoint\") {\n static readonly layer = Layer.succeed(this)({ hit: () => Effect.void });\n}\n\n/**\n * Durable host publication of canonical records and ledger approval/abort/resolution intents.\n * The host owns schema-versioned cursors, destination idempotency and acknowledgement. Delivery\n * is at least once. Hooks must not write the alarm slot or mutate the supplied raw source ports.\n *\n * `invalidate`, `prepareGeneration` and `pendingDeadline` must be bounded local operations.\n * `prepareGeneration` durably invalidates a scan only when its generation changes; repeated\n * calls must preserve partial scan progress. It runs with no source mutation in flight.\n * `drain` performs bounded delivery and persists retries before returning. A pending deadline\n * defers runtime recovery/Attempts, allowing committed host publications to drain first.\n * Unexpected hook failures leave the prearmed generation for retry. Hooks acquire per-call\n * resources with Effect.scoped; Layer construction owns incarnation resources (eviction need\n * not run finalizers). Do not hold a local hook behind network I/O or call back into producers.\n */\nexport interface ThreadPublicationService {\n readonly invalidate: Effect.Effect<void, DurableAlarmError>;\n readonly prepareGeneration: (generation: bigint) => Effect.Effect<void, DurableAlarmError>;\n readonly drain: Effect.Effect<void, DurableAlarmError>;\n readonly pendingDeadline: Effect.Effect<Option.Option<number>, DurableAlarmError>;\n}\n\n/** Opt in with `ThreadObject.layer(registrations, { publication: Layer.effect(ThreadPublication)(...) })`. */\nexport class ThreadPublication extends Context.Service<\n ThreadPublication,\n ThreadPublicationService\n>()(\"@effect-agent/platform-cloudflare/ThreadPublication\") {\n static readonly layer = Layer.succeed(this)({\n invalidate: Effect.void,\n prepareGeneration: () => Effect.void,\n drain: Effect.void,\n pendingDeadline: Effect.succeed(Option.none()),\n });\n}\n\n/**\n * Host-assembled message recovery, supplied by ThreadObject.layer even when the application\n * rebuilds ThreadMaintenance. These obligations outlive source Runs and never defer a ready\n * source Attempt while a destination is processing an accepted message.\n */\nexport const ThreadMessageDelivery = Context.Reference<{\n readonly drain: Effect.Effect<void, DurableAlarmError>;\n readonly pendingDeadline: Effect.Effect<Option.Option<number>, DurableAlarmError>;\n}>(\"@effect-agent/platform-cloudflare/ThreadMessageDelivery\", {\n defaultValue: () => ({ drain: Effect.void, pendingDeadline: Effect.succeed(Option.none()) }),\n});\n\nconst earliestDeadline = (\n left: Option.Option<number>,\n right: Option.Option<number>,\n): Option.Option<number> =>\n Option.isSome(left)\n ? Option.isSome(right)\n ? Option.some(Math.min(left.value, right.value))\n : left\n : right;\n\n/** @internal A committed source operation must not become a failed operation because delivery failed. */\nexport const publishCommitted = Effect.gen(function* () {\n const publication = yield* ThreadPublication;\n\n yield* publication.invalidate.pipe(Effect.andThen(publication.drain));\n}).pipe(\n Effect.catchCause((cause) =>\n Cause.hasInterrupts(cause)\n ? Effect.interrupt\n : Effect.logError(\"Thread publication deferred after source commit\", cause),\n ),\n);\n\nconst MaintenanceGeneration = Schema.BigIntFromString.check(\n Schema.isGreaterThanOrEqualToBigInt(0n),\n);\n\n/** Versioned, platform-private maintenance state stored through Durable Object KV. */\nclass ThreadMaintenanceState extends Schema.Class<ThreadMaintenanceState>(\n \"@effect-agent/platform-cloudflare/ThreadMaintenanceState\",\n)({\n schemaVersion: Schema.Literal(1),\n dirty: MaintenanceGeneration,\n processed: MaintenanceGeneration,\n nonterminal: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),\n}) {}\n\nconst MAINTENANCE_STATE_KEY = \"effect-agent:thread-maintenance:v1\";\nconst decodeMaintenanceState = Schema.decodeUnknownSync(ThreadMaintenanceState);\nconst encodeMaintenanceState = Schema.encodeSync(ThreadMaintenanceState);\n\nconst initialMaintenanceState = (): ThreadMaintenanceState =>\n ThreadMaintenanceState.make({\n schemaVersion: 1,\n // Bootstrap Objects created by the pre-generation release without scanning the ledger in\n // the constructor. One useful pass classifies and acknowledges any existing obligation.\n dirty: 1n,\n processed: 0n,\n nonterminal: 0,\n });\n\nconst readMaintenanceState = async (\n transaction: DurableObjectTransaction,\n): Promise<{ readonly state: ThreadMaintenanceState; readonly initialized: boolean }> => {\n const encoded = await transaction.get(MAINTENANCE_STATE_KEY);\n\n return encoded === undefined\n ? { state: initialMaintenanceState(), initialized: false }\n : { state: decodeMaintenanceState(encoded), initialized: true };\n};\n\nconst ensureTransactionAlarmBy = async (\n transaction: DurableObjectTransaction,\n deadline: number,\n): Promise<void> => {\n const scheduled = await transaction.getAlarm();\n\n if (scheduled === null || scheduled > deadline) {\n await transaction.setAlarm(deadline);\n }\n};\n\nconst stableExternalWait = (\n snapshot: SubmissionSnapshot,\n reports: ReadonlyMap<string, RecoveryReport>,\n): boolean => {\n const decision = reports.get(snapshot.submissionId)?.decision._tag;\n\n // An accepted abort still owes cleanup/settlement even if its claim was deferred this pass.\n if (decision === \"SettleAborted\") return false;\n switch (snapshot.state) {\n case \"suspended\":\n case \"joined\":\n return true;\n case \"unknown\":\n return decision === \"AwaitUnknownResolution\" || decision === \"MarkUnknown\";\n case \"admitted\":\n return reports.get(snapshot.submissionId)?.decision._tag === \"AwaitParentEstablishment\";\n case \"input-applied\":\n case \"joining\":\n case \"ready\":\n case \"running\":\n case \"settled\":\n case \"terminalizing\":\n return false;\n }\n};\n\n/**\n * Shared prearm/acknowledgement boundary for ingress and runtime-owned producers.\n * `ThreadObject.layer` provides this same instance in its Services. Rebuilt runtime/maintenance\n * Layers must reuse that instance; a second gate cannot observe the native producers' activity.\n */\nexport class ThreadMutationGate extends Context.Service<\n ThreadMutationGate,\n {\n readonly withMutation: <A, E, R>(\n body: Effect.Effect<A, E, R>,\n ) => Effect.Effect<A, E | DurableAlarmError, R>;\n readonly withSnapshot: <A, E, R>(\n body: (active: number) => Effect.Effect<A, E, R>,\n ) => Effect.Effect<A, E, R>;\n }\n>()(\"@effect-agent/platform-cloudflare/internal/ThreadMutationGate\") {\n static readonly layer = Layer.effect(this)(\n Effect.gen(function* () {\n const { ctx } = yield* DurableObjectContext;\n const config = yield* CloudflareDurableRuntimeConfig;\n const failpoint = yield* ThreadMaintenanceFailpoint;\n // A fresh incarnation has no live mutations; durable generations survive eviction.\n const activeMutations = yield* Ref.make(0);\n const generationGate = yield* Semaphore.make(1);\n const minimumAlarmDelay = Math.max(1, Math.ceil(config.alarmBackoffBase / 2));\n\n const runTransaction = <A>(operation: string, transaction: () => Promise<A>) =>\n Effect.tryPromise({ try: transaction, catch: alarmFailure(operation) });\n\n const beginMutation = Effect.fn(\"ThreadMaintenance.beginMutation\")(function* () {\n yield* failpoint.hit(\"maintenance:dirty:before\");\n const now = yield* Clock.currentTimeMillis;\n\n yield* runTransaction(\"advance maintenance generation\", () =>\n ctx.storage.transaction(async (transaction) => {\n const { state } = await readMaintenanceState(transaction);\n\n const next = ThreadMaintenanceState.make({\n ...state,\n dirty: state.dirty + 1n,\n });\n\n await transaction.put(MAINTENANCE_STATE_KEY, encodeMaintenanceState(next));\n // The earliest configured retry bounds a newly actionable mutation without relying\n // on its best-effort immediate wake hint.\n await ensureTransactionAlarmBy(transaction, now + minimumAlarmDelay);\n }),\n );\n yield* failpoint.hit(\"maintenance:dirty:after\");\n yield* Ref.update(activeMutations, (active) => active + 1);\n });\n\n const endMutation = generationGate.withPermit(\n Ref.update(activeMutations, (active) => Math.max(0, active - 1)),\n );\n\n const withMutation = <A, E, R>(\n body: Effect.Effect<A, E, R>,\n ): Effect.Effect<A, E | DurableAlarmError, R> =>\n Effect.acquireUseRelease(\n generationGate.withPermit(beginMutation()),\n () =>\n failpoint.hit(\"maintenance:mutation:armed\").pipe(\n Effect.andThen(body),\n Effect.tap(() => failpoint.hit(\"maintenance:mutation:finished\")),\n ),\n () => endMutation,\n );\n\n return ThreadMutationGate.of({\n withMutation,\n withSnapshot: (body) =>\n generationGate.withPermit(Effect.flatMap(Ref.get(activeMutations), body)),\n });\n }),\n );\n}\n\nexport type MaintenancePassFailure =\n | DurableWorkerFailure\n | DurableBindingFailure\n | DurableAlarmError\n | ThreadProjectionError;\n\n/**\n * Incremental, quiescent maintenance over a durable dirty/processed generation (issue #93).\n *\n * `pass` = generation snapshot/pre-arm → recovery → one head Attempt → generation acknowledgement:\n *\n * 1. One storage transaction reads dirty/processed and re-arms before work. A caught-up forced\n * alarm takes an O(1) path without recovery, ledger scans, or canonical-history reads.\n * 2. Recovery strictly precedes a new claim. One head Attempt advances the lane and requests\n * a safe yield after ten minutes. The whole event has a fourteen-minute cooperative timeout.\n * 3. The final transaction acknowledges only the generation observed at pass start. A racing\n * mutation therefore remains `dirty > processed` and retains its atomically-established alarm.\n * 4. Stable external waits acknowledge and clear. Autonomous retry, indeterminate, and lease\n * recovery states leave their generation dirty and retain bounded backoff rearming.\n */\nexport class ThreadMaintenance extends Context.Service<\n ThreadMaintenance,\n {\n /** One idempotent maintenance pass; failures propagate so workerd retries the alarm. */\n readonly pass: Effect.Effect<MaintenancePassReport, MaintenancePassFailure>;\n /**\n * Constructor gate: initialize/inspect only the O(1) maintenance record and ensure a dirty\n * generation has an alarm. It never scans the ledger or canonical history.\n */\n readonly ensureAlarm: Effect.Effect<void, MaintenancePassFailure>;\n /**\n * Serialize the pre-arm boundary with pass acknowledgement, advance the durable dirty\n * generation and arm the alarm in one transaction BEFORE running the caller's mutation.\n * A pass cannot acknowledge while that mutation remains in flight.\n */\n readonly withMutation: <A, E, R>(\n body: Effect.Effect<A, E, R>,\n ) => Effect.Effect<A, E | DurableAlarmError, R>;\n }\n>()(\"@effect-agent/platform-cloudflare/ThreadMaintenance\") {\n static readonly layer: Layer.Layer<\n ThreadMaintenance,\n never,\n | ThreadMutationGate\n | ThreadPublication\n | ThreadProjectionMaintenance\n | DurableAgentRuntime\n | SubmissionLedger\n | DurableAlarmService\n | ThreadMaintenanceFailpoint\n | CloudflareDurableRuntimeConfig\n | ThreadObjectIdentity\n | DurableObjectContext\n > = Layer.effect(ThreadMaintenance)(\n Effect.gen(function* () {\n const runtime = yield* DurableAgentRuntime;\n const ledger = yield* SubmissionLedger;\n const alarm = yield* DurableAlarmService;\n const config = yield* CloudflareDurableRuntimeConfig;\n const identity = yield* ThreadObjectIdentity;\n const { ctx } = yield* DurableObjectContext;\n const failpoint = yield* ThreadMaintenanceFailpoint;\n\n /**\n * Consecutive no-progress passes — an in-memory CACHE, not state: a fresh incarnation\n * restarts at zero and merely re-arms sooner than a long-lived one would have.\n */\n const stalls = yield* Ref.make(0);\n const mutations = yield* ThreadMutationGate;\n const publication = yield* ThreadPublication;\n const projection = yield* ThreadProjectionMaintenance;\n const messages = yield* ThreadMessageDelivery;\n\n // A broken disposable index still needs a retry alarm and must not prevent startup.\n const projectionDeadline = projection.pendingDeadline.pipe(\n Effect.catchCause((cause) =>\n Cause.hasInterrupts(cause)\n ? Effect.failCause(cause)\n : Effect.logError(\"Thread projection deadline unavailable\", cause).pipe(\n Effect.as(Option.some(0)),\n ),\n ),\n );\n\n const pendingDeadline = Effect.gen(function* () {\n return earliestDeadline(\n earliestDeadline(yield* publication.pendingDeadline, yield* messages.pendingDeadline),\n yield* projectionDeadline,\n );\n });\n\n const maintenancePassGate = yield* Semaphore.make(1);\n const minimumAlarmDelay = Math.max(1, Math.ceil(config.alarmBackoffBase / 2));\n\n const runTransaction = <A>(operation: string, transaction: () => Promise<A>) =>\n Effect.tryPromise({ try: transaction, catch: alarmFailure(operation) });\n\n const ensureAlarm = Effect.fn(\"ThreadMaintenance.ensureAlarm\")(function* () {\n yield* failpoint.hit(\"maintenance:ensure:before\");\n const now = yield* Clock.currentTimeMillis;\n\n yield* runTransaction(\"ensure maintenance alarm\", () =>\n ctx.storage.transaction(async (transaction) => {\n const { state, initialized } = await readMaintenanceState(transaction);\n\n if (!initialized) {\n await transaction.put(MAINTENANCE_STATE_KEY, encodeMaintenanceState(state));\n }\n if (state.dirty > state.processed) {\n await ensureTransactionAlarmBy(transaction, now + config.wakeScanInterval);\n }\n }),\n );\n const deadline = yield* pendingDeadline;\n\n if (Option.isSome(deadline)) {\n yield* runTransaction(\"ensure publication alarm\", () =>\n ctx.storage.transaction((transaction) =>\n ensureTransactionAlarmBy(\n transaction,\n Math.max(now + minimumAlarmDelay, deadline.value),\n ),\n ),\n );\n }\n yield* failpoint.hit(\"maintenance:ensure:after\");\n });\n\n const beginPass = Effect.fn(\"ThreadMaintenance.beginPass\")(function* () {\n yield* failpoint.hit(\"maintenance:begin:before\");\n const now = yield* Clock.currentTimeMillis;\n\n const result = yield* runTransaction(\"begin maintenance pass\", () =>\n ctx.storage.transaction(async (transaction) => {\n const { state, initialized } = await readMaintenanceState(transaction);\n\n if (!initialized) {\n await transaction.put(MAINTENANCE_STATE_KEY, encodeMaintenanceState(state));\n }\n if (state.processed >= state.dirty) {\n // Prearm even a publication-only pass before invoking any host hook.\n await ensureTransactionAlarmBy(transaction, now + minimumAlarmDelay);\n\n return { _tag: \"CaughtUp\" as const, nonterminal: state.nonterminal };\n }\n // Pre-arm the earliest retry before recovery. A successful finish may move this slot\n // LATER to its bounded backoff, which does not cancel the running handler.\n await ensureTransactionAlarmBy(transaction, now + minimumAlarmDelay);\n\n return {\n _tag: \"Actionable\" as const,\n generation: state.dirty,\n nonterminal: state.nonterminal,\n };\n }),\n );\n\n yield* failpoint.hit(\"maintenance:begin:after\");\n\n return result;\n });\n\n const rearmDelay = Effect.fn(\"ThreadMaintenance.rearmDelay\")(function* (progressed: boolean) {\n const priorStalls = yield* Ref.getAndUpdate(stalls, (count) =>\n progressed ? 0 : count + 1,\n );\n\n if (progressed) return config.alarmBackoffBase;\n const exponent = Math.min(priorStalls, 30);\n const backoff = Math.min(config.alarmBackoffCap, config.alarmBackoffBase * 2 ** exponent);\n const jitter = yield* Random.next;\n // Full jitter over [backoff/2, backoff]: desynchronizes retry storms without ever\n // waiting longer than the deterministic bound.\n const jittered = Math.ceil(backoff / 2 + (backoff / 2) * jitter);\n\n return Math.min(jittered, config.wakeScanInterval);\n });\n\n const pass = Effect.fn(\"ThreadMaintenance.pass\")(function* (\n yieldAfter: DateTime.Utc,\n ): Effect.fn.Return<MaintenancePassReport, MaintenancePassFailure> {\n const annotate = (report: MaintenancePassReport) =>\n Effect.annotateCurrentSpan({\n phase: report.phase,\n recovered: report.recovered,\n settled: report.settled,\n nonterminal: report.nonterminal,\n alarm: report.alarm,\n }).pipe(Effect.as(report));\n\n const started = yield* mutations.withSnapshot((activeAtStart) =>\n Effect.gen(function* () {\n const generation = yield* beginPass();\n\n if (generation._tag === \"Actionable\" && activeAtStart === 0) {\n // The gate excludes a producer starting between the snapshot and certification.\n yield* publication.prepareGeneration(generation.generation);\n }\n\n return { ...generation, activeAtStart };\n }),\n );\n\n // Keep one bounded delivery wave active beside source work, including messages\n // created after this pass starts. New writes invalidate the cached deadline; the\n // wake scan bounds discovery even when this Object's native alarm is already running.\n const stopDelivery = yield* Deferred.make<void>();\n\n const delivery = yield* Effect.forkChild(\n Effect.gen(function* () {\n while (true) {\n const stopping = yield* Deferred.isDone(stopDelivery);\n\n yield* messages.drain;\n if (stopping) return;\n\n const next = yield* messages.pendingDeadline;\n const now = yield* Clock.currentTimeMillis;\n\n const delay = Option.isSome(next)\n ? Math.min(config.wakeScanInterval, Math.max(1, next.value - now))\n : config.wakeScanInterval;\n\n yield* Effect.raceFirst(Deferred.await(stopDelivery), Effect.sleep(delay));\n }\n }),\n );\n\n // Complete the in-flight wave and one final drain before reading alarm deadlines.\n // Failures still surface after source work; interruption supervises the child and\n // leaves the prearmed generation available for durable recovery.\n const finishDelivery = Deferred.succeed(stopDelivery, undefined).pipe(\n Effect.andThen(Fiber.join(delivery)),\n );\n\n // Capture derived-index failures until canonical work has had its turn. Interruption\n // still stops the event; ordinary failures and defects retain the prearmed generation.\n const projected = yield* Effect.exit(\n drainDue.pipe(Effect.provideService(ThreadProjectionMaintenance, projection)),\n );\n\n if (Exit.isFailure(projected) && Cause.hasInterrupts(projected.cause))\n return yield* Effect.failCause(projected.cause);\n const deadline = yield* publication.pendingDeadline;\n\n if (\n started._tag === \"Actionable\" ||\n (Option.isSome(deadline) && deadline.value <= (yield* Clock.currentTimeMillis))\n ) {\n yield* publication.drain;\n }\n const pending = yield* publication.pendingDeadline;\n\n if (started._tag === \"CaughtUp\" || Option.isSome(pending)) {\n yield* finishDelivery;\n if (Exit.isFailure(projected)) return yield* Effect.failCause(projected.cause);\n yield* failpoint.hit(\"maintenance:finish:before\");\n\n const disposition = yield* mutations.withSnapshot((active) =>\n Effect.gen(function* () {\n // Re-read under the producer gate: a concurrent append/host mutation cannot be\n // cleared using a stale empty deadline. Dirty generations bound all producer races.\n const latest = yield* pendingDeadline;\n const now = yield* Clock.currentTimeMillis;\n\n return yield* runTransaction(\"finish publication pass\", () =>\n ctx.storage.transaction(async (transaction) => {\n const { state } = await readMaintenanceState(transaction);\n\n const nativeDeadline =\n active > 0 || state.dirty > state.processed\n ? now + config.wakeScanInterval\n : Infinity;\n\n const next = Option.isSome(latest)\n ? Math.min(nativeDeadline, latest.value)\n : nativeDeadline;\n\n if (Number.isFinite(next)) {\n await transaction.setAlarm(Math.max(now + minimumAlarmDelay, next));\n\n return \"rearmed\" as const;\n }\n await transaction.deleteAlarm();\n\n return \"cleared\" as const;\n }),\n );\n }),\n );\n\n yield* failpoint.hit(\"maintenance:finish:after\");\n\n return yield* annotate(\n MaintenancePassReport.make({\n phase: \"caught-up\",\n recovered: 0,\n settled: 0,\n nonterminal: started.nonterminal,\n alarm: disposition,\n }),\n );\n }\n // Step 2 — reconciliation strictly precedes new work in this pass (exit gate).\n const recovered: ReadonlyArray<RecoveryReport> = yield* runtime.runRecovery;\n // One head Attempt per event. The runtime yields after a committed turn when the\n // soft deadline is reached; queued followers belong to a subsequent alarm.\n const settlement = yield* runtime.processThreadHead(identity.threadId, { yieldAfter });\n\n yield* finishDelivery;\n if (Exit.isFailure(projected)) return yield* Effect.failCause(projected.cause);\n // Observe residual state before acknowledging this exact pass-start generation.\n const remaining = yield* Stream.runCollect(ledger.scanNonterminal);\n const reports = new Map(recovered.map((report) => [report.submissionId, report]));\n const head = remaining[0];\n const headWaiting = head !== undefined && stableExternalWait(head, reports);\n\n const autonomous = remaining.some((snapshot, index) => {\n // FIFO followers cannot execute through a stable external wait. Only plain queued\n // input is dormant here; admission repairs and accepted aborts still need a pass.\n if (\n index > 0 &&\n headWaiting &&\n snapshot.state === \"ready\" &&\n reports.get(snapshot.submissionId)?.decision._tag === \"ApplyInput\"\n )\n return false;\n\n return !stableExternalWait(snapshot, reports);\n });\n\n const progressed =\n Option.isSome(settlement) ||\n recovered.some((report) => report.disposition === \"repaired\");\n\n const delay = autonomous ? yield* rearmDelay(progressed) : 0;\n const now = yield* Clock.currentTimeMillis;\n\n yield* failpoint.hit(\"maintenance:finish:before\");\n\n const alarmDisposition = yield* mutations.withSnapshot((active) =>\n Effect.gen(function* () {\n const publicationDeadline = yield* pendingDeadline;\n\n return yield* runTransaction(\"finish maintenance pass\", () =>\n ctx.storage.transaction(async (transaction) => {\n const { state } = await readMaintenanceState(transaction);\n\n // Autonomous work and in-flight mutations intentionally leave the observed\n // generation dirty. Otherwise acknowledge only the pass-start generation.\n const processed =\n autonomous || started.activeAtStart > 0 || active > 0\n ? state.processed\n : state.processed > started.generation\n ? state.processed\n : started.generation;\n\n const next = ThreadMaintenanceState.make({\n ...state,\n processed,\n nonterminal: remaining.length,\n });\n\n await transaction.put(MAINTENANCE_STATE_KEY, encodeMaintenanceState(next));\n if (autonomous) {\n // Replace the crash-fallback slot with this pass's bounded backoff. The target\n // is never earlier than the begin-pass fallback, so workerd does not cancel\n // this running alarm handler before its report/span can complete.\n await transaction.setAlarm(\n Option.isSome(publicationDeadline)\n ? Math.max(\n now + minimumAlarmDelay,\n Math.min(now + delay, publicationDeadline.value),\n )\n : now + delay,\n );\n\n return \"rearmed\" as const;\n }\n if (started.activeAtStart > 0 || active > 0 || next.dirty > next.processed) {\n // A mutation overlapped this pass's observation window or raced\n // acknowledgement. It stays dirty and its pre-armed bounded alarm survives;\n // unseen effects are never acknowledged. Do not accelerate that future alarm\n // from inside the current handler: workerd cancels a running handler when it\n // writes an earlier slot.\n await ensureTransactionAlarmBy(\n transaction,\n Option.isSome(publicationDeadline)\n ? Math.max(\n now + minimumAlarmDelay,\n Math.min(now + config.wakeScanInterval, publicationDeadline.value),\n )\n : now + config.wakeScanInterval,\n );\n\n return \"rearmed\" as const;\n }\n if (Option.isSome(publicationDeadline)) {\n await transaction.setAlarm(\n Math.max(now + minimumAlarmDelay, publicationDeadline.value),\n );\n\n return \"rearmed\" as const;\n }\n await transaction.deleteAlarm();\n\n return \"cleared\" as const;\n }),\n );\n }),\n );\n\n yield* failpoint.hit(\"maintenance:finish:after\");\n if (alarmDisposition === \"cleared\") {\n yield* Ref.set(stalls, 0);\n }\n\n return yield* annotate(\n MaintenancePassReport.make({\n phase: \"actionable\",\n recovered: recovered.length,\n settled: Option.isSome(settlement) ? 1 : 0,\n nonterminal: remaining.length,\n alarm: alarmDisposition,\n }),\n );\n });\n\n return ThreadMaintenance.of({\n // A mid-pass immediate hint is droppable; durable dirty state decides the final alarm.\n pass: Effect.gen(function* () {\n const yieldAfter = DateTime.makeUnsafe((yield* Clock.currentTimeMillis) + 10 * 60_000);\n\n return yield* alarm.withWakesDeferred(maintenancePassGate.withPermit(pass(yieldAfter)));\n }).pipe(\n // Include permit waiting, recovery and acknowledgement in the event deadline.\n // Interruption releases Attempt ownership, leaving the prearmed dirty generation\n // for recovery. It never changes the logical Run duration or settles a policy failure.\n // This cooperative timer cannot preempt synchronous CPU work or stuck finalizers.\n Effect.timeoutOrElse({\n duration: \"14 minutes\",\n orElse: () =>\n DurableAlarmError.make({\n operation: \"maintenance pass deadline\",\n message:\n \"The maintenance event exceeded its 14 minute deadline; durable recovery remains pending\",\n }),\n }),\n ),\n ensureAlarm: mutations.withSnapshot(() => ensureAlarm()),\n withMutation: (body) =>\n mutations.withMutation(\n body.pipe(\n Effect.tap(() =>\n publishCommitted.pipe(Effect.provideService(ThreadPublication, publication)),\n ),\n ),\n ),\n });\n }),\n );\n}\n"],"mappings":"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"}
@@ -1154,6 +1154,11 @@ declare const encodeHostResponse: (input: AbortRecorded | ApprovalRecorded | Hos
1154
1154
  readonly threadId: string;
1155
1155
  readonly actualEpoch: number;
1156
1156
  readonly attemptedEpoch: number;
1157
+ } | {
1158
+ readonly _tag: "DurableAlarmError";
1159
+ readonly operation: string;
1160
+ readonly message: string;
1161
+ readonly cause?: Schema.Json | undefined;
1157
1162
  } | {
1158
1163
  readonly _tag: "HostProtocolError";
1159
1164
  readonly message: string;
@@ -1181,11 +1186,6 @@ declare const encodeHostResponse: (input: AbortRecorded | ApprovalRecorded | Hos
1181
1186
  readonly limit: "database-bytes" | "input-bytes" | "queue-depth";
1182
1187
  readonly actual: number;
1183
1188
  readonly maximum: number;
1184
- } | {
1185
- readonly _tag: "DurableAlarmError";
1186
- readonly operation: string;
1187
- readonly message: string;
1188
- readonly cause?: Schema.Json | undefined;
1189
1189
  } | {
1190
1190
  readonly _tag: "OperationDenied";
1191
1191
  readonly operation: "abort" | "awaitSettlement" | "explain" | "observe" | "resolveApproval" | "resolveUnknown" | "retry" | "scanObligations" | "verify" | "wake";
@@ -668,6 +668,11 @@ declare const encodeAdminResponse: (input: AdminFailed | ExplainedRecovery | Obl
668
668
  } | {
669
669
  readonly _tag: "AdminFailed";
670
670
  readonly failure: {
671
+ readonly _tag: "DurableAlarmError";
672
+ readonly operation: string;
673
+ readonly message: string;
674
+ readonly cause?: Schema.Json | undefined;
675
+ } | {
671
676
  readonly _tag: "AdmissionPolicyError";
672
677
  readonly reason: "occupied" | "refused" | "unavailable";
673
678
  readonly code: string;
@@ -700,11 +705,6 @@ declare const encodeAdminResponse: (input: AdminFailed | ExplainedRecovery | Obl
700
705
  readonly threadId: string;
701
706
  readonly actualEpoch: number;
702
707
  readonly attemptedEpoch: number;
703
- } | {
704
- readonly _tag: "DurableAlarmError";
705
- readonly operation: string;
706
- readonly message: string;
707
- readonly cause?: Schema.Json | undefined;
708
708
  } | {
709
709
  readonly _tag: "OperationDenied";
710
710
  readonly operation: "abort" | "awaitSettlement" | "explain" | "observe" | "resolveApproval" | "resolveUnknown" | "retry" | "scanObligations" | "verify" | "wake";
@@ -773,4 +773,4 @@ interface Class<EventServices = never> {
773
773
  declare const make: <ApplicationServices, ApplicationError, EventServices = never, EventLayerError = never>(applicationLayer: Layer.Layer<CloudflareDurableRuntimeServices | ApplicationServices, ApplicationError, CloudflareBootstrapServices | DurableObjectState$1.DurableObjectState | WorkerEnvironment | DurableObjectContext | ThreadObjectNamespace>, options: Options<ApplicationServices, EventServices, EventLayerError>) => Class<ApplicationServices | EventServices>;
774
774
  //#endregion
775
775
  export { CloudflareDurableRuntimeOptions as C, layerConfig as D, layer as E, CloudflareDurableRuntimeInitializationError as S, ThreadPublicationOptions as T, decodeObligationThresholds as _, AdminVerifyRequest as a, make as b, Instance as c, RetryExecuted as d, ThreadObject_d_exports as f, decodeAdminVerifyRequest as g, decodeAdminResponse as h, AdminResponse as i, ObligationsScanned as l, decodeAdminExplainRequest as m, AdminFailed as n, Class as o, VerifiedIntegrity as p, AdminFailure as r, ExplainedRecovery as s, AdminExplainRequest as t, Options as u, decodeRetryCommand as v, CloudflareDurableRuntimeServices as w, CloudflareBootstrapServices as x, encodeAdminResponse as y };
776
- //# sourceMappingURL=ThreadObject-DwoxhMdm.d.mts.map
776
+ //# sourceMappingURL=ThreadObject-DTIsDsku.d.mts.map
@@ -1,2 +1,2 @@
1
- import { C as CloudflareDurableRuntimeOptions, D as layerConfig, E as layer, S as CloudflareDurableRuntimeInitializationError, T as ThreadPublicationOptions, _ as decodeObligationThresholds, a as AdminVerifyRequest, b as make, c as Instance, d as RetryExecuted, g as decodeAdminVerifyRequest, h as decodeAdminResponse, i as AdminResponse, l as ObligationsScanned, m as decodeAdminExplainRequest, n as AdminFailed, o as Class, p as VerifiedIntegrity, r as AdminFailure, s as ExplainedRecovery, t as AdminExplainRequest, u as Options, v as decodeRetryCommand, w as CloudflareDurableRuntimeServices, x as CloudflareBootstrapServices, y as encodeAdminResponse } from "./ThreadObject-DwoxhMdm.mjs";
1
+ import { C as CloudflareDurableRuntimeOptions, D as layerConfig, E as layer, S as CloudflareDurableRuntimeInitializationError, T as ThreadPublicationOptions, _ as decodeObligationThresholds, a as AdminVerifyRequest, b as make, c as Instance, d as RetryExecuted, g as decodeAdminVerifyRequest, h as decodeAdminResponse, i as AdminResponse, l as ObligationsScanned, m as decodeAdminExplainRequest, n as AdminFailed, o as Class, p as VerifiedIntegrity, r as AdminFailure, s as ExplainedRecovery, t as AdminExplainRequest, u as Options, v as decodeRetryCommand, w as CloudflareDurableRuntimeServices, x as CloudflareBootstrapServices, y as encodeAdminResponse } from "./ThreadObject-DTIsDsku.mjs";
2
2
  export { AdminExplainRequest, AdminFailed, AdminFailure, AdminResponse, AdminVerifyRequest, type CloudflareBootstrapServices as BootstrapServices, Class, ExplainedRecovery, type CloudflareDurableRuntimeInitializationError as InitializationError, Instance, ObligationsScanned, Options, type ThreadPublicationOptions as PublicationOptions, RetryExecuted, type CloudflareDurableRuntimeOptions as RuntimeOptions, type CloudflareDurableRuntimeServices as Services, VerifiedIntegrity, decodeAdminExplainRequest, decodeAdminResponse, decodeAdminVerifyRequest, decodeObligationThresholds, decodeRetryCommand, encodeAdminResponse, layer, layerConfig, make };
package/dist/index.d.mts CHANGED
@@ -7,6 +7,6 @@ import { t as CloudflareMemory_d_exports } from "./CloudflareMemory.mjs";
7
7
  import { t as CloudflareScheduling_d_exports } from "./CloudflareScheduling.mjs";
8
8
  import { t as CloudflareSubscriptions_d_exports } from "./CloudflareSubscriptions.mjs";
9
9
  import { t as CloudflareThreadClient_d_exports } from "./CloudflareThreadClient.mjs";
10
- import { f as ThreadObject_d_exports } from "./ThreadObject-DwoxhMdm.mjs";
10
+ import { f as ThreadObject_d_exports } from "./ThreadObject-DTIsDsku.mjs";
11
11
  import { t as WakeScheduler_d_exports } from "./WakeScheduler.mjs";
12
12
  export { Alarm_d_exports as Alarm, CloudflareBindings_d_exports as CloudflareBindings, CloudflareBrowser_d_exports as CloudflareBrowser, CloudflareCodeMode_d_exports as CloudflareCodeMode, CloudflareConfig_d_exports as CloudflareConfig, CloudflareMemory_d_exports as CloudflareMemory, CloudflareScheduling_d_exports as CloudflareScheduling, CloudflareSubscriptions_d_exports as CloudflareSubscriptions, CloudflareThreadClient_d_exports as CloudflareThreadClient, ThreadObject_d_exports as ThreadObject, WakeScheduler_d_exports as WakeScheduler };
package/package.json CHANGED
@@ -1 +1 @@
1
- {"name":"@effect-agent/platform-cloudflare","version":"0.1.0-beta.69","dependencies":{"@effect-agent/core":"0.1.0-beta.69","@effect-agent/engine":"0.1.0-beta.69","@effect-agent/sandbox":"0.1.0-beta.69","@effect-agent/storage-cloudflare":"0.1.0-beta.69","@effect-agent/thread":"0.1.0-beta.69","@effect/platform-browser":"4.0.0-rc.112","@effect/sql-sqlite-do":"4.0.0-rc.112"},"devDependencies":{"@cloudflare/puppeteer":"1.1.0","@cloudflare/vitest-pool-workers":"0.21.3","@cloudflare/workers-types":"5.20260825.1","@effect-agent/capabilities":"0.1.0-beta.69","@effect-agent/testing":"0.1.0-beta.69","@effect/platform-node":"4.0.0-rc.112","@effect/sql-d1":"4.0.0-rc.112","@effect/vitest":"4.0.0-rc.112","effect":"4.0.0-rc.112","effect-cf":"0.40.0","esbuild":"0.28.1","miniflare":"5.20260811.1-alpha","typescript":"7.0.2","vite-plus":"0.3.0","vitest":"4.1.11"},"peerDependencies":{"@cloudflare/puppeteer":"^1.1.0","effect":"^4.0.0-rc.112","effect-cf":"^0.40.0"},"exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./Alarm":{"types":"./dist/Alarm.d.mts","default":"./dist/Alarm.mjs"},"./BrowserRestCapture":{"types":"./dist/BrowserRestCapture.d.mts","default":"./dist/BrowserRestCapture.mjs"},"./BrowserRestCrawl":{"types":"./dist/BrowserRestCrawl.d.mts","default":"./dist/BrowserRestCrawl.mjs"},"./CloudflareBindings":{"types":"./dist/CloudflareBindings.d.mts","default":"./dist/CloudflareBindings.mjs"},"./CloudflareBrowser":{"types":"./dist/CloudflareBrowser.d.mts","default":"./dist/CloudflareBrowser.mjs"},"./CloudflareCodeMode":{"types":"./dist/CloudflareCodeMode.d.mts","default":"./dist/CloudflareCodeMode.mjs"},"./CloudflareConfig":{"types":"./dist/CloudflareConfig.d.mts","default":"./dist/CloudflareConfig.mjs"},"./CloudflareMemory":{"types":"./dist/CloudflareMemory.d.mts","default":"./dist/CloudflareMemory.mjs"},"./CloudflareScheduling":{"types":"./dist/CloudflareScheduling.d.mts","default":"./dist/CloudflareScheduling.mjs"},"./CloudflareSubscriptions":{"types":"./dist/CloudflareSubscriptions.d.mts","default":"./dist/CloudflareSubscriptions.mjs"},"./CloudflareThreadClient":{"types":"./dist/CloudflareThreadClient.d.mts","default":"./dist/CloudflareThreadClient.mjs"},"./InteractiveBrowser":{"types":"./dist/InteractiveBrowser.d.mts","default":"./dist/InteractiveBrowser.mjs"},"./ProtectedBrowser":{"types":"./dist/ProtectedBrowser.d.mts","default":"./dist/ProtectedBrowser.mjs"},"./ThreadObject":{"types":"./dist/ThreadObject.d.mts","default":"./dist/ThreadObject.mjs"},"./WakeScheduler":{"types":"./dist/WakeScheduler.d.mts","default":"./dist/WakeScheduler.mjs"}},"description":"Cloudflare Layer assembly for Effect Agent: Durable Objects and Browser Run adapters.","license":"MIT","repository":{"type":"git","url":"git+https://github.com/danieljvdm/effect-agent.git","directory":"packages/platform-cloudflare"},"files":["dist","src"],"type":"module","sideEffects":[],"publishConfig":{"access":"public"},"scripts":{"build":"vp pack","check":"tsc --noEmit -p tsconfig.json"},"peerDependenciesMeta":{"@cloudflare/puppeteer":{"optional":true}}}
1
+ {"name":"@effect-agent/platform-cloudflare","version":"0.1.0-beta.71","dependencies":{"@effect-agent/core":"0.1.0-beta.71","@effect-agent/engine":"0.1.0-beta.71","@effect-agent/sandbox":"0.1.0-beta.71","@effect-agent/storage-cloudflare":"0.1.0-beta.71","@effect-agent/thread":"0.1.0-beta.71","@effect/platform-browser":"4.0.0-rc.112","@effect/sql-sqlite-do":"4.0.0-rc.112"},"devDependencies":{"@cloudflare/puppeteer":"1.1.0","@cloudflare/vitest-pool-workers":"0.21.3","@cloudflare/workers-types":"5.20260825.1","@effect-agent/capabilities":"0.1.0-beta.71","@effect-agent/testing":"0.1.0-beta.71","@effect/platform-node":"4.0.0-rc.112","@effect/sql-d1":"4.0.0-rc.112","@effect/vitest":"4.0.0-rc.112","effect":"4.0.0-rc.112","effect-cf":"0.40.0","esbuild":"0.28.1","miniflare":"5.20260811.1-alpha","typescript":"7.0.2","vite-plus":"0.3.0","vitest":"4.1.11"},"peerDependencies":{"@cloudflare/puppeteer":"^1.1.0","effect":"^4.0.0-rc.112","effect-cf":"^0.40.0"},"exports":{".":{"types":"./dist/index.d.mts","default":"./dist/index.mjs"},"./Alarm":{"types":"./dist/Alarm.d.mts","default":"./dist/Alarm.mjs"},"./BrowserRestCapture":{"types":"./dist/BrowserRestCapture.d.mts","default":"./dist/BrowserRestCapture.mjs"},"./BrowserRestCrawl":{"types":"./dist/BrowserRestCrawl.d.mts","default":"./dist/BrowserRestCrawl.mjs"},"./CloudflareBindings":{"types":"./dist/CloudflareBindings.d.mts","default":"./dist/CloudflareBindings.mjs"},"./CloudflareBrowser":{"types":"./dist/CloudflareBrowser.d.mts","default":"./dist/CloudflareBrowser.mjs"},"./CloudflareCodeMode":{"types":"./dist/CloudflareCodeMode.d.mts","default":"./dist/CloudflareCodeMode.mjs"},"./CloudflareConfig":{"types":"./dist/CloudflareConfig.d.mts","default":"./dist/CloudflareConfig.mjs"},"./CloudflareMemory":{"types":"./dist/CloudflareMemory.d.mts","default":"./dist/CloudflareMemory.mjs"},"./CloudflareScheduling":{"types":"./dist/CloudflareScheduling.d.mts","default":"./dist/CloudflareScheduling.mjs"},"./CloudflareSubscriptions":{"types":"./dist/CloudflareSubscriptions.d.mts","default":"./dist/CloudflareSubscriptions.mjs"},"./CloudflareThreadClient":{"types":"./dist/CloudflareThreadClient.d.mts","default":"./dist/CloudflareThreadClient.mjs"},"./InteractiveBrowser":{"types":"./dist/InteractiveBrowser.d.mts","default":"./dist/InteractiveBrowser.mjs"},"./ProtectedBrowser":{"types":"./dist/ProtectedBrowser.d.mts","default":"./dist/ProtectedBrowser.mjs"},"./ThreadObject":{"types":"./dist/ThreadObject.d.mts","default":"./dist/ThreadObject.mjs"},"./WakeScheduler":{"types":"./dist/WakeScheduler.d.mts","default":"./dist/WakeScheduler.mjs"}},"description":"Cloudflare Layer assembly for Effect Agent: Durable Objects and Browser Run adapters.","license":"MIT","repository":{"type":"git","url":"git+https://github.com/danieljvdm/effect-agent.git","directory":"packages/platform-cloudflare"},"files":["dist","src"],"type":"module","sideEffects":[],"publishConfig":{"access":"public"},"scripts":{"build":"vp pack","check":"tsc --noEmit -p tsconfig.json"},"peerDependenciesMeta":{"@cloudflare/puppeteer":{"optional":true}}}
package/src/Alarm.ts CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  Clock,
16
16
  Context,
17
17
  DateTime,
18
+ Deferred,
18
19
  Effect,
19
20
  Exit,
20
21
  Fiber,
@@ -629,10 +630,37 @@ export class ThreadMaintenance extends Context.Service<
629
630
  }),
630
631
  );
631
632
 
632
- // Run one bounded message wave beside source work. Slow destination admission or
633
- // status RPCs cannot consume the source Attempt's execution window. Join before
634
- // acknowledging the alarm so the final deadline includes all delivery mutations.
635
- const delivery = yield* Effect.forkChild(messages.drain);
633
+ // Keep one bounded delivery wave active beside source work, including messages
634
+ // created after this pass starts. New writes invalidate the cached deadline; the
635
+ // wake scan bounds discovery even when this Object's native alarm is already running.
636
+ const stopDelivery = yield* Deferred.make<void>();
637
+
638
+ const delivery = yield* Effect.forkChild(
639
+ Effect.gen(function* () {
640
+ while (true) {
641
+ const stopping = yield* Deferred.isDone(stopDelivery);
642
+
643
+ yield* messages.drain;
644
+ if (stopping) return;
645
+
646
+ const next = yield* messages.pendingDeadline;
647
+ const now = yield* Clock.currentTimeMillis;
648
+
649
+ const delay = Option.isSome(next)
650
+ ? Math.min(config.wakeScanInterval, Math.max(1, next.value - now))
651
+ : config.wakeScanInterval;
652
+
653
+ yield* Effect.raceFirst(Deferred.await(stopDelivery), Effect.sleep(delay));
654
+ }
655
+ }),
656
+ );
657
+
658
+ // Complete the in-flight wave and one final drain before reading alarm deadlines.
659
+ // Failures still surface after source work; interruption supervises the child and
660
+ // leaves the prearmed generation available for durable recovery.
661
+ const finishDelivery = Deferred.succeed(stopDelivery, undefined).pipe(
662
+ Effect.andThen(Fiber.join(delivery)),
663
+ );
636
664
 
637
665
  // Capture derived-index failures until canonical work has had its turn. Interruption
638
666
  // still stops the event; ordinary failures and defects retain the prearmed generation.
@@ -653,7 +681,7 @@ export class ThreadMaintenance extends Context.Service<
653
681
  const pending = yield* publication.pendingDeadline;
654
682
 
655
683
  if (started._tag === "CaughtUp" || Option.isSome(pending)) {
656
- yield* Fiber.join(delivery);
684
+ yield* finishDelivery;
657
685
  if (Exit.isFailure(projected)) return yield* Effect.failCause(projected.cause);
658
686
  yield* failpoint.hit("maintenance:finish:before");
659
687
 
@@ -708,7 +736,7 @@ export class ThreadMaintenance extends Context.Service<
708
736
  // soft deadline is reached; queued followers belong to a subsequent alarm.
709
737
  const settlement = yield* runtime.processThreadHead(identity.threadId, { yieldAfter });
710
738
 
711
- yield* Fiber.join(delivery);
739
+ yield* finishDelivery;
712
740
  if (Exit.isFailure(projected)) return yield* Effect.failCause(projected.cause);
713
741
  // Observe residual state before acknowledging this exact pass-start generation.
714
742
  const remaining = yield* Stream.runCollect(ledger.scanNonterminal);