Spike: radar proposals on @alchemy.run/domain
Proposals and votes for the tech radar, modelled with Alchemy’s domain library and
specified as Stories, to learn whether the library is ready. Run on 2026-10-30 against
@alchemy.run/domain 2.0.0-beta.84, Effect 4.1.0, @effect/vitest 4.1.0 and Vitest
5.0.5.
The code is on the branch spike/radar-proposals (spikes/radar-proposals/), kept apart
from main: it adds a workspace that is not meant to ship. There,
bun run --filter @opsydyn/radar-proposals-spike test # 5 storiesbun run --filter @opsydyn/radar-proposals-spike typecheckThe decision it informs is in the tech radar plan
(2026-10-28-tech-radar-app.md),
under “Final infra stage: reviewing @alchemy.run/domain”.
What it models
spikes/radar-proposals/src/proposal.ts:
Proposalaggregate:ProposeMove(an entry from one ring to another, with a reason),CastVote(once per voter, replying with the tally) andCloseVote(accepted with three in favour and more for than against, rejected otherwise). Rejections:AlreadyProposed,SameRing,NotOpen,AlreadyVoted.ProposalTallyview: one proposal’s entry, target ring, votes and status, which the radar would watch over a WebSocket beside the entry’s card.Radarmodel: both.
spikes/radar-proposals/test/proposal.test.ts covers each command’s happy path, every rejection and the view.
What it found
- Effect only appears to line up. With FoldKit 0.170 the library resolves the
repository’s Effect 4.1.0 and the Stories pass, but they never load Alchemy’s own runtime.
Alchemy
2.0.0-beta.84importseffect/Crypto, which 4.1 removed: the same day, its CLI failed every tech radar deploy on 4.1 until infra went back to Effect 4.0.7. Hosting the model needs Alchemy itself, so condition 1 of the review is not met. - Vitest 5 works.
@effect/vitest4.1 runs the Stories on Vitest 5.0.5, and apps/web’s 408 tests pass on it, once its on-disk module cache is off (vanilla-extract’s.css.tsfiles skip their compiler when cached). - The published Story API is the documented one.
given/when/then/rejected/replied/viewall behave as the package’s own skill describes;expectCall/resolveare what this release ships for external calls. - Modelling is quick. Five stories passed on the second run. The one failure was mine:
spreading the command into an event copies the command’s
_tag, which the event’s schema rejects. Build events field by field. - It is light. About 1.3 MB installed, and
alchemycomes along as a required peer. - Lint. The repository’s rules raise one error, on the library’s documented
import * as Schema from 'effect/Schema'(no namespace imports from Effect packages); named imports fix it.
Not tried
- Hosting: the model in its own Worker with Durable Objects beside
Website.Astro(Website.Foldkitis the documented pairing). - Sign-in: every write needs it (
cloudflare-domain-better-auth). - The FoldKit client: commands as FoldKit Commands with
Succeeded*/Failed*Messages, the tally as a Subscription. - The
ExternalSystemthat opens a markdown pull request for an accepted proposal.
Verdict
Condition 1 (one Effect) is not met: Alchemy has to run on Effect 4.1 first. Update,
2026-11-02: it is now. Alchemy 2.0.0-beta.85 moved to Effect 4.1, and the infra workspace
shares the apps’ Effect (see docs/releases/alchemy-beta-85.md). The Vitest
cost is paid. Condition 2 (a few
stable releases) still stands: beta.84 is still the only release. Condition 3 (a write
feature) is now approved in principle. The next step, when the library has moved on, is a
hosting spike: the Radar model in a Worker beside the site, behind sign-in.