Enable stricter Effect LSP rules#2596
Conversation
- Replace barrel imports across server and test code - Add diagnostics overrides for Node-only scripts and probes - Tighten auth/session encoding and deterministic test fixtures
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Autofix Details
Bugbot Autofix prepared fixes for both issues found in the latest run.
- ✅ Fixed: Metrics record bogus duration if interrupted early
- Moved processingStartedAtMs acquisition to Clock.currentTimeMillis before Effect.exit via Effect.flatMap, ensuring the timestamp is always valid even if the inner effect is interrupted before its first yield.
- ✅ Fixed: Published package.json loses pretty-print formatting
- Replaced Schema.UnknownFromJsonString with a custom PrettyUnknownFromJsonString schema that passes space:2 to SchemaGetter.stringifyJson, restoring 2-space indented JSON output while satisfying the preferSchemaOverJson lint rule.
Or push these changes by commenting:
@cursor push b63ec8265e
Preview (b63ec8265e)
diff --git a/apps/server/scripts/cli.ts b/apps/server/scripts/cli.ts
--- a/apps/server/scripts/cli.ts
+++ b/apps/server/scripts/cli.ts
@@ -8,6 +8,8 @@
import * as Option from "effect/Option";
import * as Path from "effect/Path";
import * as Schema from "effect/Schema";
+import * as SchemaGetter from "effect/SchemaGetter";
+import * as SchemaTransformation from "effect/SchemaTransformation";
import { Command, Flag } from "effect/unstable/cli";
import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
@@ -35,6 +37,16 @@
overrides: Record<string, string>;
}
+const PrettyUnknownFromJsonString = Schema.String.pipe(
+ Schema.decodeTo(
+ Schema.Unknown,
+ new SchemaTransformation.Transformation<unknown, string>(
+ SchemaGetter.parseJson(),
+ SchemaGetter.stringifyJson({ space: 2 }),
+ ),
+ ),
+);
+
class CliError extends Data.TaggedError("CliError")<{
readonly message: string;
readonly cause?: unknown;
@@ -235,7 +247,7 @@
yield* fs.writeFileString(backupPath, original);
yield* fs.writeFileString(
packageJsonPath,
- `${Schema.encodeUnknownSync(Schema.UnknownFromJsonString)(pkg)}\n`,
+ `${Schema.encodeUnknownSync(PrettyUnknownFromJsonString)(pkg)}\n`,
);
yield* Effect.log("[cli] Prepared package.json for publish");
diff --git a/apps/server/src/orchestration/Layers/OrchestrationEngine.ts b/apps/server/src/orchestration/Layers/OrchestrationEngine.ts
--- a/apps/server/src/orchestration/Layers/OrchestrationEngine.ts
+++ b/apps/server/src/orchestration/Layers/OrchestrationEngine.ts
@@ -98,7 +98,6 @@
const processEnvelope = (envelope: CommandEnvelope): Effect.Effect<void> => {
const dispatchStartSequence = commandReadModel.snapshotSequence;
- let processingStartedAtMs = 0;
const aggregateRef = commandToAggregateRef(envelope.command);
const baseMetricAttributes = {
commandType: envelope.command.type,
@@ -119,163 +118,172 @@
}
});
- return Effect.exit(
- Effect.gen(function* () {
- processingStartedAtMs = yield* Clock.currentTimeMillis;
- yield* Effect.annotateCurrentSpan({
- "orchestration.command_id": envelope.command.commandId,
- "orchestration.command_type": envelope.command.type,
- "orchestration.aggregate_kind": aggregateRef.aggregateKind,
- "orchestration.aggregate_id": aggregateRef.aggregateId,
- });
+ return Clock.currentTimeMillis.pipe(
+ Effect.flatMap((processingStartedAtMs) =>
+ Effect.exit(
+ Effect.gen(function* () {
+ yield* Effect.annotateCurrentSpan({
+ "orchestration.command_id": envelope.command.commandId,
+ "orchestration.command_type": envelope.command.type,
+ "orchestration.aggregate_kind": aggregateRef.aggregateKind,
+ "orchestration.aggregate_id": aggregateRef.aggregateId,
+ });
- const existingReceipt = yield* commandReceiptRepository.getByCommandId({
- commandId: envelope.command.commandId,
- });
- if (Option.isSome(existingReceipt)) {
- if (existingReceipt.value.status === "accepted") {
- return {
- sequence: existingReceipt.value.resultSequence,
- };
- }
- return yield* new OrchestrationCommandPreviouslyRejectedError({
- commandId: envelope.command.commandId,
- detail: existingReceipt.value.error ?? "Previously rejected.",
- });
- }
-
- const eventBase = yield* decideOrchestrationCommand({
- command: envelope.command,
- readModel: commandReadModel,
- });
- const eventBases = Array.isArray(eventBase) ? eventBase : [eventBase];
- const committedCommand = yield* sql
- .withTransaction(
- Effect.gen(function* () {
- const committedEvents: OrchestrationEvent[] = [];
- let nextCommandReadModel = commandReadModel;
-
- for (const nextEvent of eventBases) {
- const savedEvent = yield* eventStore.append(nextEvent);
- nextCommandReadModel = yield* projectEvent(nextCommandReadModel, savedEvent);
- yield* projectionPipeline.projectEvent(savedEvent);
- committedEvents.push(savedEvent);
+ const existingReceipt = yield* commandReceiptRepository.getByCommandId({
+ commandId: envelope.command.commandId,
+ });
+ if (Option.isSome(existingReceipt)) {
+ if (existingReceipt.value.status === "accepted") {
+ return {
+ sequence: existingReceipt.value.resultSequence,
+ };
}
-
- const lastSavedEvent = committedEvents.at(-1) ?? null;
- if (lastSavedEvent === null) {
- return yield* new OrchestrationCommandInvariantError({
- commandType: envelope.command.type,
- detail: "Command produced no events.",
- });
- }
-
- yield* commandReceiptRepository.upsert({
+ return yield* new OrchestrationCommandPreviouslyRejectedError({
commandId: envelope.command.commandId,
- aggregateKind: lastSavedEvent.aggregateKind,
- aggregateId: lastSavedEvent.aggregateId,
- acceptedAt: lastSavedEvent.occurredAt,
- resultSequence: lastSavedEvent.sequence,
- status: "accepted",
- error: null,
+ detail: existingReceipt.value.error ?? "Previously rejected.",
});
+ }
- return {
- committedEvents,
- lastSequence: lastSavedEvent.sequence,
- nextCommandReadModel,
- } as const;
- }),
- )
- .pipe(
- Effect.catchTag("SqlError", (sqlError) =>
- Effect.fail(
- toPersistenceSqlError("OrchestrationEngine.processEnvelope:transaction")(sqlError),
- ),
- ),
- );
+ const eventBase = yield* decideOrchestrationCommand({
+ command: envelope.command,
+ readModel: commandReadModel,
+ });
+ const eventBases = Array.isArray(eventBase) ? eventBase : [eventBase];
+ const committedCommand = yield* sql
+ .withTransaction(
+ Effect.gen(function* () {
+ const committedEvents: OrchestrationEvent[] = [];
+ let nextCommandReadModel = commandReadModel;
- commandReadModel = committedCommand.nextCommandReadModel;
- for (const [index, event] of committedCommand.committedEvents.entries()) {
- yield* PubSub.publish(eventPubSub, event);
- if (index === 0) {
- yield* Metric.update(
- Metric.withAttributes(
- orchestrationCommandAckDuration,
- metricAttributes({
- ...baseMetricAttributes,
- ackEventType: event.type,
- }),
- ),
- Duration.millis(Math.max(0, (yield* Clock.currentTimeMillis) - envelope.startedAtMs)),
- );
- }
- }
- return { sequence: committedCommand.lastSequence };
- }).pipe(Effect.withSpan(`orchestration.command.${envelope.command.type}`)),
- ).pipe(
- Effect.flatMap((exit) =>
- Effect.gen(function* () {
- const outcome = Exit.isSuccess(exit)
- ? "success"
- : Cause.hasInterruptsOnly(exit.cause)
- ? "interrupt"
- : "failure";
- yield* Metric.update(
- Metric.withAttributes(
- orchestrationCommandDuration,
- metricAttributes(baseMetricAttributes),
- ),
- Duration.millis(Math.max(0, (yield* Clock.currentTimeMillis) - processingStartedAtMs)),
- );
- yield* Metric.update(
- Metric.withAttributes(
- orchestrationCommandsTotal,
- metricAttributes({
- ...baseMetricAttributes,
- outcome,
- }),
- ),
- 1,
- );
+ for (const nextEvent of eventBases) {
+ const savedEvent = yield* eventStore.append(nextEvent);
+ nextCommandReadModel = yield* projectEvent(nextCommandReadModel, savedEvent);
+ yield* projectionPipeline.projectEvent(savedEvent);
+ committedEvents.push(savedEvent);
+ }
- if (Exit.isSuccess(exit)) {
- yield* Deferred.succeed(envelope.result, exit.value);
- return;
- }
+ const lastSavedEvent = committedEvents.at(-1) ?? null;
+ if (lastSavedEvent === null) {
+ return yield* new OrchestrationCommandInvariantError({
+ commandType: envelope.command.type,
+ detail: "Command produced no events.",
+ });
+ }
- const error = Cause.squash(exit.cause) as OrchestrationDispatchError;
- if (!Schema.is(OrchestrationCommandPreviouslyRejectedError)(error)) {
- yield* reconcileReadModelAfterDispatchFailure.pipe(
- Effect.catch(() =>
- Effect.logWarning(
- "failed to reconcile orchestration read model after dispatch failure",
- ).pipe(
- Effect.annotateLogs({
+ yield* commandReceiptRepository.upsert({
commandId: envelope.command.commandId,
- snapshotSequence: commandReadModel.snapshotSequence,
- }),
+ aggregateKind: lastSavedEvent.aggregateKind,
+ aggregateId: lastSavedEvent.aggregateId,
+ acceptedAt: lastSavedEvent.occurredAt,
+ resultSequence: lastSavedEvent.sequence,
+ status: "accepted",
+ error: null,
+ });
+
+ return {
+ committedEvents,
+ lastSequence: lastSavedEvent.sequence,
+ nextCommandReadModel,
+ } as const;
+ }),
+ )
+ .pipe(
+ Effect.catchTag("SqlError", (sqlError) =>
+ Effect.fail(
+ toPersistenceSqlError("OrchestrationEngine.processEnvelope:transaction")(
+ sqlError,
+ ),
+ ),
),
- ),
- );
+ );
- if (Schema.is(OrchestrationCommandInvariantError)(error)) {
- yield* commandReceiptRepository
- .upsert({
- commandId: envelope.command.commandId,
- aggregateKind: aggregateRef.aggregateKind,
- aggregateId: aggregateRef.aggregateId,
- acceptedAt: yield* nowIso,
- resultSequence: commandReadModel.snapshotSequence,
- status: "rejected",
- error: error.message,
- })
- .pipe(Effect.catch(() => Effect.void));
+ commandReadModel = committedCommand.nextCommandReadModel;
+ for (const [index, event] of committedCommand.committedEvents.entries()) {
+ yield* PubSub.publish(eventPubSub, event);
+ if (index === 0) {
+ yield* Metric.update(
+ Metric.withAttributes(
+ orchestrationCommandAckDuration,
+ metricAttributes({
+ ...baseMetricAttributes,
+ ackEventType: event.type,
+ }),
+ ),
+ Duration.millis(
+ Math.max(0, (yield* Clock.currentTimeMillis) - envelope.startedAtMs),
+ ),
+ );
+ }
}
- }
+ return { sequence: committedCommand.lastSequence };
+ }).pipe(Effect.withSpan(`orchestration.command.${envelope.command.type}`)),
+ ).pipe(
+ Effect.flatMap((exit) =>
+ Effect.gen(function* () {
+ const outcome = Exit.isSuccess(exit)
+ ? "success"
+ : Cause.hasInterruptsOnly(exit.cause)
+ ? "interrupt"
+ : "failure";
+ yield* Metric.update(
+ Metric.withAttributes(
+ orchestrationCommandDuration,
+ metricAttributes(baseMetricAttributes),
+ ),
+ Duration.millis(
+ Math.max(0, (yield* Clock.currentTimeMillis) - processingStartedAtMs),
+ ),
+ );
+ yield* Metric.update(
+ Metric.withAttributes(
+ orchestrationCommandsTotal,
+ metricAttributes({
+ ...baseMetricAttributes,
+ outcome,
+ }),
+ ),
+ 1,
+ );
- yield* Deferred.fail(envelope.result, error);
- }),
+ if (Exit.isSuccess(exit)) {
+ yield* Deferred.succeed(envelope.result, exit.value);
+ return;
+ }
+
+ const error = Cause.squash(exit.cause) as OrchestrationDispatchError;
+ if (!Schema.is(OrchestrationCommandPreviouslyRejectedError)(error)) {
+ yield* reconcileReadModelAfterDispatchFailure.pipe(
+ Effect.catch(() =>
+ Effect.logWarning(
+ "failed to reconcile orchestration read model after dispatch failure",
+ ).pipe(
+ Effect.annotateLogs({
+ commandId: envelope.command.commandId,
+ snapshotSequence: commandReadModel.snapshotSequence,
+ }),
+ ),
+ ),
+ );
+
+ if (Schema.is(OrchestrationCommandInvariantError)(error)) {
+ yield* commandReceiptRepository
+ .upsert({
+ commandId: envelope.command.commandId,
+ aggregateKind: aggregateRef.aggregateKind,
+ aggregateId: aggregateRef.aggregateId,
+ acceptedAt: yield* nowIso,
+ resultSequence: commandReadModel.snapshotSequence,
+ status: "rejected",
+ error: error.message,
+ })
+ .pipe(Effect.catch(() => Effect.void));
+ }
+ }
+
+ yield* Deferred.fail(envelope.result, error);
+ }),
+ ),
+ ),
),
);
};You can send follow-ups to the cloud agent here.
| const processEnvelope = (envelope: CommandEnvelope): Effect.Effect<void> => { | ||
| const dispatchStartSequence = commandReadModel.snapshotSequence; | ||
| const processingStartedAtMs = Date.now(); | ||
| let processingStartedAtMs = 0; |
There was a problem hiding this comment.
Metrics record bogus duration if interrupted early
Medium Severity
processingStartedAtMs is initialized to 0 and only assigned inside the inner Effect.gen via yield* Clock.currentTimeMillis. If the fiber is interrupted before that first yield completes, the value stays 0. The outer Effect.flatMap continuation always runs (via Effect.exit) and computes Clock.currentTimeMillis - 0, recording an astronomically large duration metric (~1.78 trillion ms). Previously, Date.now() was called eagerly before entering the Effect, guaranteeing a valid start time.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit f44c0a7. Configure here.
ApprovabilityVerdict: Needs human review Diff is too large for automated approval analysis. A human reviewer should evaluate this PR. You can customize Macroscope's approvability policy. Learn more. |
- Switch test imports to the current TestClock module path - Use the NetService context tag consistently across server, desktop, and ssh code
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
There are 4 total unresolved issues (including 2 from previous reviews).
Autofix Details
Bugbot Autofix prepared a fix for 1 of the 2 issues found in the latest run.
- ✅ Fixed: Busy-wait polling replaces timed sleep in tests
- Replaced Effect.runPromise(Effect.yieldNow) with Effect.runPromise(Effect.sleep("10 millis")) in all three polling functions to restore a real 10ms delay between poll iterations.
Or push these changes by commenting:
@cursor push 5580ad54e2
Preview (5580ad54e2)
diff --git a/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts b/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts
--- a/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts
+++ b/apps/server/src/orchestration/Layers/CheckpointReactor.test.ts
@@ -170,7 +170,7 @@
if ((await Effect.runPromise(Clock.currentTimeMillis)) >= deadline) {
throw new Error("Timed out waiting for thread state.");
}
- await Effect.runPromise(Effect.yieldNow);
+ await Effect.runPromise(Effect.sleep("10 millis"));
return poll();
};
return poll();
@@ -192,7 +192,7 @@
if ((await Effect.runPromise(Clock.currentTimeMillis)) >= deadline) {
throw new Error("Timed out waiting for orchestration event.");
}
- await Effect.runPromise(Effect.yieldNow);
+ await Effect.runPromise(Effect.sleep("10 millis"));
return poll();
};
return poll();
@@ -239,7 +239,7 @@
if ((await Effect.runPromise(Clock.currentTimeMillis)) >= deadline) {
throw new Error(`Timed out waiting for git ref '${ref}'.`);
}
- await Effect.runPromise(Effect.yieldNow);
+ await Effect.runPromise(Effect.sleep("10 millis"));
return poll();
};
return poll();You can send follow-ups to the cloud agent here.
Replace Effect.yieldNow (near-instant microtask resolution) with
Effect.sleep("10 millis") to restore a real delay between poll
iterations in waitForThread, waitForEvent, and waitForGitRefExists.
Without a real delay, these polling loops spin as tight busy-waits,
starving the event loop of time to process I/O callbacks from git
operations and database writes.
Applied via @cursor push command
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 3 total unresolved issues (including 2 from previous reviews).
Autofix Details
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed:
nowIsoSyncusesEffect.runSyncbypassing runtime clock- Replaced
nowIsoSync(which usedEffect.runSync) with an effectfulnowIsoconstant consumed viaEffect.flatMapinsideEffect.catchhandlers, ensuringDateTime.nowis evaluated within the fiber's runtime clock context and is controllable byTestClock.
- Replaced
Or push these changes by commenting:
@cursor push 24ad635e33
Preview (24ad635e33)
diff --git a/apps/server/src/orchestration/Layers/CheckpointReactor.ts b/apps/server/src/orchestration/Layers/CheckpointReactor.ts
--- a/apps/server/src/orchestration/Layers/CheckpointReactor.ts
+++ b/apps/server/src/orchestration/Layers/CheckpointReactor.ts
@@ -33,7 +33,7 @@
import { VcsStatusBroadcaster } from "../../vcs/VcsStatusBroadcaster.ts";
import { WorkspaceEntries } from "../../workspace/Services/WorkspaceEntries.ts";
-const nowIsoSync = () => Effect.runSync(DateTime.now.pipe(Effect.map(DateTime.formatIso)));
+const nowIso = DateTime.now.pipe(Effect.map(DateTime.formatIso));
type ReactorInput =
| {
@@ -724,12 +724,14 @@
if (event.type === "thread.checkpoint-revert-requested") {
yield* handleRevertRequested(event).pipe(
Effect.catch((error) =>
- appendRevertFailureActivity({
- threadId: event.payload.threadId,
- turnCount: event.payload.turnCount,
- detail: error.message,
- createdAt: nowIsoSync(),
- }),
+ Effect.flatMap(nowIso, (createdAt) =>
+ appendRevertFailureActivity({
+ threadId: event.payload.threadId,
+ turnCount: event.payload.turnCount,
+ detail: error.message,
+ createdAt,
+ }),
+ ),
),
);
return;
@@ -743,12 +745,14 @@
if (event.type === "thread.turn-diff-completed") {
yield* captureCheckpointFromPlaceholder(event).pipe(
Effect.catch((error) =>
- appendCaptureFailureActivity({
- threadId: event.payload.threadId,
- turnId: event.payload.turnId,
- detail: error.message,
- createdAt: nowIsoSync(),
- }).pipe(Effect.catch(() => Effect.void)),
+ Effect.flatMap(nowIso, (createdAt) =>
+ appendCaptureFailureActivity({
+ threadId: event.payload.threadId,
+ turnId: event.payload.turnId,
+ detail: error.message,
+ createdAt,
+ }),
+ ).pipe(Effect.catch(() => Effect.void)),
),
);
}
@@ -767,12 +771,14 @@
yield* refreshLocalGitStatusFromTurnCompletion(event);
yield* captureCheckpointFromTurnCompletion(event).pipe(
Effect.catch((error) =>
- appendCaptureFailureActivity({
- threadId: event.threadId,
- turnId,
- detail: error.message,
- createdAt: nowIsoSync(),
- }).pipe(Effect.catch(() => Effect.void)),
+ Effect.flatMap(nowIso, (createdAt) =>
+ appendCaptureFailureActivity({
+ threadId: event.threadId,
+ turnId,
+ detail: error.message,
+ createdAt,
+ }),
+ ).pipe(Effect.catch(() => Effect.void)),
),
);
return;You can send follow-ups to the cloud agent here.
- Replace sync timestamp reads with Effect-based time generation - Update provider initial snapshots and unavailable snapshots to return effects - Adjust managed provider tests for the new async snapshot contract
- Reject invalid session exp claims - Use safer DateTime parsing across diagnostics, adapter, and settings
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 3 total unresolved issues (including 2 from previous reviews).
Bugbot Autofix prepared a fix for the issue found in the latest run.
- ✅ Fixed: Missing try-catch around BigInt conversion causes uncaught throw
- Restored try-catch around BigInt(text) in unixNanoToDateTime so invalid non-integer strings return null instead of throwing an uncaught SyntaxError.
Or push these changes by commenting:
@cursor push 5250529987
Preview (5250529987)
diff --git a/apps/server/src/diagnostics/TraceDiagnostics.ts b/apps/server/src/diagnostics/TraceDiagnostics.ts
--- a/apps/server/src/diagnostics/TraceDiagnostics.ts
+++ b/apps/server/src/diagnostics/TraceDiagnostics.ts
@@ -90,8 +90,12 @@
const text = toStringValue(value);
if (!text) return null;
- const millis = Number(BigInt(text) / 1_000_000n);
- return Option.getOrNull(DateTime.make(millis));
+ try {
+ const millis = Number(BigInt(text) / 1_000_000n);
+ return Option.getOrNull(DateTime.make(millis));
+ } catch {
+ return null;
+ }
}
function readExitTag(exit: unknown): string | null {You can send follow-ups to the cloud agent here.
Reviewed by Cursor Bugbot for commit 00d4b68. Configure here.
- Centralize stable 2-space JSON encoding in `packages/shared` - Switch CLI and release-version scripts to the shared helper - Keep keybindings config writes using the same pretty encoder
- Set a default `forceKillAfter` when spawning the Codex app server - Rely on child-process lifecycle from the spawned handle instead of an explicit release kill



Summary
effectimports with explicit subpath imports across server, desktop, and integration code.serverExposurefolder and updated all call sites.Testing
bun fmtbun lintbun typecheckbun run testNote
Medium Risk
Mostly mechanical import refactors, but it also changes auth token claim encoding/validation and replaces
Date.now()/new Date()timing with EffectClock/DateTimein orchestration and checkpoint flows, which could affect runtime behavior and test expectations.Overview
Migrates the codebase away from
effectbarrel imports to explicit module namespace imports (e.g.effect/Effect,effect/Layer) across desktop, server, and integration code, and adds@effect-diagnosticssuppressions where node builtins/global timers are intentionally used.Makes time/UUID/JSON behavior more Effect-driven and deterministic. Orchestration and checkpoint code now uses
Clock.currentTimeMillis/DateTime.nowfor timestamps and metrics timing, tests replace realDate.now()/new Date()with fixed ISO values, and several scripts/tests switch to Effect/Node-safe timer APIs.Tightens a few runtime behaviors. Session credential issuance now Schema-encodes claims (with explicit error mapping) and verifies
expviaDateTime.makeinstead of unsafe construction; CLI publishing/keybindings config writing now uses shared pretty-JSON Schema helpers; and project CLI errors are normalized into a taggedProjectCommandErrorwith ISO timestamps derived fromDateTime.Reviewed by Cursor Bugbot for commit 1ccd9f4. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Enable stricter Effect LSP rules across the monorepo by migrating to namespace imports
@effect/language-serviceplugin config from per-packagetsconfig.jsonfiles intotsconfig.base.json, enabling shared stricter rules (e.g.preferSchemaOverJson,missingEffectServiceDependency,globalErrorInEffectFailureas errors) across all packages.import { Effect, Layer } from 'effect') to module-qualified namespace imports (import * as Effect from 'effect/Effect') across hundreds of files to satisfy the newnamespaceImportPackagesrule.new Date().toISOString()andDate.now()with EffectDateTimeandClockservices throughout production code, and replacesJSON.stringify/JSON.parsewithSchema.encodeUnknownSync/Schema.decodeUnknownSyncto satisfypreferSchemaOverJson.ProjectSetupScriptRunnerError,ProviderMaintenanceCommandError,ProjectCommandError, etc.) to replace genericErrorin failure channels, satisfyingglobalErrorInEffectFailure.makePendingClaudeProvider,buildUnavailableProviderSnapshot, etc.) from plain values toEffect-returning functions, as their timestamps now require the Effect clock.setTimeout/Date.now()polling withTestClock.adjust,Effect.yieldNow, and fixed ISO string literals for determinism.makePersistedServerRuntimeState,makeManagedServerProvider, and the provider snapshot builders must nowyield*an Effect instead of using a plain value; theTerminalCwdErrorcause-message method now returns a boolean instead of a string.Macroscope summarized 1ccd9f4.