You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 970a024
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: .macroscope/check-run-agents/effect-service-conventions.md
+13Lines changed: 13 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -44,6 +44,18 @@ Review changed TypeScript for the conventions below. They apply when a pull requ
44
44
- Use plain `make` and `layer` in a module named for its implementation (`BunPtyAdapter.ts`). Keep implementation-specific names when one abstract port module holds several implementations (`makeCloudflaredRelayClient`, `layerCloudflared` in `RelayClient.ts`). `infra/relay/src/db.ts` may keep its inline `Layer.succeed(RelayDb, db)`.
45
45
- When a service moves, delete the old files and update every consumer, including orchestration, MCP, tests, and integration harnesses. Do not leave compatibility re-export shims.
46
46
47
+
## Effect functions and tracing
48
+
49
+
- Reusable functions use named `Effect.fn("Service.operation")`, not functions wrapping
50
+
`Effect.gen`. Inline programs, callbacks, and layer construction can keep `Effect.gen`.
51
+
- Named `Effect.fn` creates a span; the unnamed form does not. Default to named spans for service
52
+
operations and I/O. `Effect.fnUntraced` is deliberate for per-chunk/per-event work, tight loops,
53
+
or helpers duplicating an existing tracing boundary; require a nearby reason for new uses.
54
+
- Preserve existing span names/options and avoid adding duplicate spans. Pass combinators after
55
+
the generator body; transforms receive the original function arguments after the effect.
56
+
- Preserve generic signatures, laziness, resource scope, and interruption behavior. An explicit
57
+
generator return type is `Effect.fn.Return<A, E, R>`.
58
+
47
59
## Dependency acquisition and runtime boundaries
48
60
49
61
- Production service construction acquires its Effect dependencies from the environment with `yield* Foo.Foo`, and `make`/`layer` types expose those requirements. Flag a factory that takes `Foo["Service"]` (or an object of Effect-returning methods) as a parameter when that value is a service dependency. Passing service instances explicitly in tests is fine; passing pure configuration, immutable domain values, or deliberate callback strategies is not service injection.
@@ -57,6 +69,7 @@ Review changed TypeScript for the conventions below. They apply when a pull requ
57
69
- Define service failures with `Schema.TaggedError` and structured attributes: operation or stage, resource path or entity identifier, normalized category or status. Derive `message` from those attributes only. Never derive it from `cause`, `cause.message`, or a stringified defect, and do not add a `detail` field that copies `cause.message`.
58
70
- When wrapping a real failure, keep the immediate underlying error as `cause` so the chain and stack survive. Make `cause` required if every construction wraps a failure. Pure validation or domain errors created without an underlying failure need no cause.
59
71
- Keep attributes and log annotations safe and bounded: no raw wire payloads, command arguments or output, signed URLs, credentials, query strings, or arbitrary defect text. Preserve the exact value only as `cause`; expose normalized categories, lengths, counts, and safe URL protocol or hostname where useful.
72
+
The documented exception is `DeviceBootError.diagnostics`: device hub launch commands and failure text are intentionally exposed to authorized device users and agents, capped at 2,000 characters and included in the error message. Do not apply this exception to other service errors.
60
73
- At a translation boundary, pass through an already structured domain error when it is part of the target error channel; wrap only unknown or lower-level failures. Map failures where the context is known instead of wrapping a whole multi-step pipeline in one generic error.
61
74
- Do not encode the same distinction twice with both a specific error tag and a single-value `operation`, `reason`, `kind`, or `phase` literal. Split into separate error classes when a discriminator drives caller control flow or the user-facing message; a discriminator used only for diagnostics may stay a field. Caller-visible messages exposed through HTTP, RPC, persisted state, or UI are behavior and must survive a structural refactor.
62
75
- Do not add a helper whose only behavior is `(...args) => new SomeError({ ...args })`. Construct the error at the failure boundary. Keep a mapper only when it performs real normalization, passes through domain errors, or adds reusable context; when such a mapper belongs to the target error type, prefer a static factory on that class.
0 commit comments