Skip to content

test(usage): cover cross-environment merge and lint fixes

MacroscopeApp / Macroscope - Effect Service Conventions failed Aug 8, 2026 in 2m 37s

Effect Service Conventions: 2 issues found

Two convention violations found in the new usage service.

  • apps/server/src/usage/UsageService.ts:108 — make is the module's construction entry point but is not exported; the convention is export const make alongside export const layer (as in WorkspacePaths.ts, ReviewService.ts, and most other server services).
  • apps/server/src/usage/UsageService.ts:84 — layerTest is defined as a static on the tag class. This repo exports test layers at module level; the equivalent statics on ServerConfig and ServerSettingsService are marked @deprecated Import and use \layerTest` from this module.Moving it toexport const layerTestkeeps the module namespace shape and letsserver.test.tsuseUsageService.layerTestinstead ofUsageService.UsageService.layerTest`.

Both were posted as inline review comments.

Details

Note

Your check run agent prompt is: .macroscope/check-run-agents/effect-service-conventions.md
More information about how Check Run Agents work can be found in our Help Center.

Scope reviewed: apps/server/src/usage/* (UsageService.ts, usageAggregation.ts, usagePricing.ts, usageTranscripts.ts, usageTranscriptReader.ts), packages/contracts/src/usage.ts and rpc.ts, packages/client-runtime/src/state/server.ts, apps/web/src/state/usage.ts, apps/web/src/usage/*, apps/web/src/components/usage/*, plus the touched server.ts, server.test.ts, ws.ts, and RpcAuthorization.ts call sites. The two binary-marked files (usageAggregation.ts, usageMerge.ts) were fetched and read at head.

Items checked and found conforming:

  • Effect modules are imported as subpath namespaces (effect/Effect, effect/Layer, effect/Schema, …); import { HttpClient, HttpClientResponse } from "effect/unstable/http" matches established repo-wide usage. No consolidated imports from "effect".
  • ServerSettings is imported as a namespace and used as ServerSettings.ServerSettingsService; the pure helpers (resolveClaudeHomePath, resolveCodexHomeLayout, parseRateTable, transcript readers) correctly use named imports.
  • Single-file order is canonical: imports, schemas, Context.Service tag with an inline interface, construction, layer. No standalone FooShape type.
  • layer is exported as export const layer = Layer.effect(UsageService, make); production dependencies (FileSystem, Path, ServerConfig, ServerSettingsService, HttpClient) are acquired with yield* rather than injected as service instances. No ManagedRuntime.make, runPromise, or runPromiseExit introduced. The only Layer.succeed is the pure test layer.
  • UsageReadError uses Schema.TaggedErrorClass with a bounded, serializable reason/detail and derives message from those attributes. Both construction sites are pure window-validation failures, so no cause is required, and detail carries only the requested day bounds — no payloads, URLs, or credentials.
  • The direct node:fs streaming in usageTranscriptReader.ts is isolated behind a documented performance rationale and does not hide an Effect service dependency, so it was not flagged.
  • New tests cover the pure aggregation, transcript parsing, and merge logic rather than mocking business logic.

🔍 Agent activity — 1 tool call

💭 Looking at the UsageService implementation to understand the Effect service conventions being used. The class extends Context.Service with an inline interface definition, includes a static layerTest, and follows the pattern of defining a make function followed by exporting the layer. The imports use subpath namespaces like Clock and HttpClient, which aligns with the Effect library conventions. Now I'm checking whether the imports follow the established patterns — specifically looking at how ServerConfig is imported from the local config module and whether that matches the service boundary conventions used elsewhere in the repo. The pattern of importing config values directly as named imports appears consistent with other files in the codebase. Looking at the dependency acquisition in the make function, it properly yields all the required services like FileSystem, Path, ServerConfig, and HttpClient. The mutable state for caching and rate limiting is scoped to the closure, which is appropriate for per-instance state. I'm also examining how the transcript reader uses Node's filesystem directly with a performance-related diagnostic suppression, and how errors are handled through the UsageReadError class defined with Schema.TaggedErrorClass. The detail message safely bounds the date comparison without copying the cause. The two invalidWindow errors are pure validation with no underlying cause, which is fine. Errors from scan failures get swallowed broadly through Effect.catchCause, which aligns with the conventions. For the test layer, layerTest uses Layer.succeed as a test seam, though I need to check whether it's calling any runtime-backed APIs that would violate the convention against hiding dependencies that way. The Schema.decodeUnknownEffect call uses double casts to work around type constraints, which isn't explicitly prohibited. And readSummary manually provides the Path service inside the effect, which is acceptable given the comment explaining the approach. For UsageService.of in the test layer and the make function returning readSummary as const, both follow the patterns fine. The readSummary error channel is declared as UsageReadError in the interface, but the actual implementation from Effect.fn might include additional error types that need reconciling. The layer export using Layer.effect is canonical. The file placement with UsageService.ts in the usage directory follows conventions, and the layer definition itself is correct. I need to check the binary-diff files for usageAggregation.ts and usageMerge.ts since they're in scope, and also verify the imports in apps/web/src/state/usage.ts which uses the Effect atom-react hooks. I'm noticing the codebase has a pattern where layerTest is sometimes defined as a static class property but marked as deprecated, with a preference for module-level exports instead. Some files like config.ts and serverSettings.ts follow this newer pattern, while AnalyticsService still has the static version alongside a re-exported module-level constant. I'm trying to figure out whether having a static layerTest on the class itself violates the layer export convention, since the instructions specifically mention exporting layer as a constant but don't explicitly address static properties. The repo consistently exports make, but UsageService doesn't follow this pattern—that's a convention violation worth flagging with a comment. Now I'm checking if the service tag placement matches the standard order: imports, error and schema declarations, the Context.Service tag with its interface definition, make, then layer. Looking at UsageService.ts, the schema declarations appear to be in the right spot before the tag, but there's a static layerTest inside the class using Layer.succeed, which conflicts with the repo's direction toward module-level exports rather than class statics—other services like AnalyticsService use static readonly layerTest, but newer patterns in config.ts and serverSettings.ts deprecate statics in favor of module-level exports. The test currently accesses it as UsageService.UsageService.layerTest (namespace, class, static), which is awkward compared to just UsageService.layerTest. I should probably keep my findings precise and focused: the missing make export is a clear violation, and the static layerTest placement is a lower-severity style note that could be combined with the make comment. Looking at other potential issues: the JSON.parse in usage.ts isn't a service convention problem, ws.ts is trivial, and the Effect.promise wrapping the transcript reader using node fs—that's not really a violation since the repo has disabled the node builtin lint rule and there's documented performance reasoning, so I won't flag it. For error conventions, UsageReadError has a reason discriminator with two values but only one is actually used in practice; that's fine since the multi-value model is sanctioned when the tag is generic. The unused scanFailed literal is dead code but not a convention violation. Both error cases are pure validation failures without needing a cause field. The service swallows failures through Effect.catchCause, hiding the scanFailed path entirely, but the Effect layer itself is preserved so that's acceptable. I'm noticing ServerConfig is imported as a service dependency in UsageService. The repo follows a module-level export pattern for layerTest, but UsageService is defining it as a class static instead, which violates the public module shape guidance. I've found two issues in UsageService.ts so far: make isn't exported on line 108, and layerTest is incorrectly placed as a class static from lines 84-105 rather than as a module-level export. I'll document both findings with inline comments. Scanning the rest of the file for dependency injection violations, the memo cache is properly scoped inside make, and the error handling with Effect.fn looks correct. The web state atoms and contract error definitions using Schema.TaggedErrorClass are both following conventions. I'm now checking the error handling patterns to see if they're using Effect.catchTags for statically known failures versus Effect.catch for broader error handling. Let me verify the rest of the UsageService file and check how the layers compose together — the HttpClient, FileSystem, and Path dependencies flow through RuntimeCoreDependenciesLive via provideMerge, so the ordering should work fine. I'm also noticing that usageTranscriptReader.ts uses node:fs directly with a diagnostics suppression, which might be worth examining. The direct node:fs usage isn't actually hiding a dependency in a closure or global — it's raw IO with a documented performance reason, so that's not a violation of the dependency acquisition rules. The real issue is that make isn't exported, which is a clear convention violation according to the stated rules about exporting the real make when a module owns construction. That's worth flagging as a failure rather than just a style preference. For the layerTest static, it's more of a suggestion since the repo deprecates statics but it's not as explicit a violation. I'll include both findings in the review, marking the make export as the primary issue. Since there are findings, the check defaults to failure. For the line numbers, I'm targeting the specific lines in the diff where these issues appear — the make function and the static layerTest declaration.

1. modify_pr — Reviewed the new usage service against the Effect service conventions. Two findings in apps/server/src/usage/UsageService.ts; the contract (packages/contracts/src/usage.ts), the pure helpers, and the web-side atoms/merge code look consistent with the conventions (subpath namespace imports, inline Context.Service interface, dependencies acquired via yield*, Schema.TaggedErrorClass with a message derived from structural attributes).


Agent Credits: 132 credits