Skip to content

Codex/result wrapping#16

Merged
kastriotkastrati merged 6 commits into
mainfrom
codex/result-wrapping
Jun 28, 2026
Merged

Codex/result wrapping#16
kastriotkastrati merged 6 commits into
mainfrom
codex/result-wrapping

Conversation

@kastriotkastrati

Copy link
Copy Markdown
Contributor

No description provided.

Convert the inner layers (leaf utils, adapters, core domain, and the
Store/Platform/LlmAdapter ports) from throwing to returning Result<T, Error>.
Errors propagate as values (`if (!x.ok) return x`) until a boundary frame —
the queue/scheduled handlers, Durable Object alarm/RPC methods, and the HTTP
route handlers — which are the only places that throw (runtime-critical, to
drive msg.retry()/DO retry/5xx) or map an Err to a handled response.

This replaces the earlier pass that wrapped calls in Result.from(...) and
immediately re-threw the error (a behavior-preserving no-op that defeated the
point of Result). Adds an `unwrap` helper for the loop-island pattern, since
asyncMap/asyncForEach/asyncUnfold cannot short-circuit on an Err and raw loops
are banned by local/no-raw-loops.

- Store (28 methods), Platform (10), LlmAdapter.complete -> Result<T, Error>
- 82 inner functions converted; domain invariants use Err(new Error("MSG"))
  to keep the error channel uniformly Error (no string | Error unions)
- D1 reads are wrapped here (the queue/DO boundary needs read failures as
  values to drive retry), consciously overriding the generic "don't wrap
  reads" guidance for this port
- Fixes a type error in coordinator.ts where env-var narrowing was lost
  inside a Result.from(() => ...) closure

typecheck, lint, and all tests pass.
- extract markDelivered helper (dedup KV puts + name DEDUPE_TTL_SECONDS)
- multiline ternaries -> IIFEs; ?. chains -> guards; + concat -> template literal
- adapter-llm: redundant Result.from+unwrap -> asyncForEach; return inner.complete directly
- drop explicit return types on GitHubAdapter (let TS infer)
- enforce Array<T> via @typescript-eslint/array-type; convert repo-wide
- Inner fns return Err; errors propagate as returned values through loop/asyncMap/asyncUnfold callbacks instead of throwing inside them (fail-slow on idempotent ops, accepted).
- asyncUnfold infers its result as the union of STOP values (Step + Extract), removing per-call-site type hints.
- Drop over-broad Result.from wrappers around asyncUnfold in adapter-github; only the smallest throwable IO stays wrapped.
- parseNativeId / parseSlackNativeId / d1-store json+parse return Result
  instead of throwing; callers unwrap and propagate to boundaries
- configIdentitySource / safeParse / buildEngineContext / memberGate
  return Result; routes throw at the boundary, queue/cron propagate
- add findMap helper; rewrite findIdentity over it
- tests assert expect(x.ok) + x.data! instead of okOrThrow / throw
- drop okOrThrow
Hoist the missing-secret check out of the verify ternary so a missing
GITHUB_WEBHOOK_SECRET throws (500 server misconfig) instead of masking as
a 401 bad signature. Test env now binds a dummy secret so the unsigned
webhook test exercises real signature verification.
…ance

resolveSlackUserId extracted Platform.resolvePerson into a bare variable and
invoked it detached, so `this` was undefined and SlackAdapter's `this.config`
threw a TypeError on every roster-username/email DM and digest resolution,
crashing the unit of work. Call it as a method on `slack`.

isOptionalString rejected explicit null, so a roster row with a null optional
field (the natural JSON for an absent value) made configIdentitySource return
Err and 500'd every webhook. Treat null as absent; still rejects non-strings.

Both regressions were masked by the suite: route/aggregate mocked resolvePerson
with this-free closures, and every roster fixture used the array path that skips
row validation. Add a this-bound resolvePerson test and a null-field
roster-string test; both fail without the fixes.
@kastriotkastrati kastriotkastrati marked this pull request as ready for review June 28, 2026 15:52
@kastriotkastrati kastriotkastrati merged commit 2a40025 into main Jun 28, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant