0001. Core, adapters and extensions (three rings)

0001. Core, adapters and extensions (three rings)

Status

Accepted

Date

2026-10-04

Deciders

operator (Saulo Vallory). The operator corrected the first version of the research synthesis on 2026-10-01; the rule was decided before the rulings of 2026-10-04 and recorded in the project’s agent instructions.

Context

Mesh is a planned TypeScript framework modelled on Ash, the Elixir framework in which one resource file declares data, operations and rules and everything else is derived. Mesh talks to databases, may one day sit behind a server or other transport, and runs jobs. The question is which parts Mesh is built on directly (core), which it reaches only through a contract it owns (adapters), and which are optional features (extensions).

The first synthesis called Bun “integral” because its name would appear in generated code. The operator reviewed that and the synthesis was revised: “That was the wrong test” (research synthesis, Step 2 preamble).

Decision

The rule, as recorded in the agent instructions and in the synthesis (Step 2 preamble): hardcoded (core) means Mesh cannot run without it; anything replaceable is an adapter behind a core contract; anything optional is an extension; and “It was in the plan” is never a reason to hardcode something. The original wording of the adapter sentence listed the runtime among the replaceable parts, and the synthesis ring table also put the authoring syntax in the adapter ring. Later rulings removed both: Mesh runs on Bun only (ADR-0025) and MX, which parses resource files, is core (ADR-0043). The rule itself stands; those rulings applied it. What the rings hold is in research synthesis, section 15, and roadmap, section 3. In v1 the adapters are the SQL data layers (ADR-0014); a transport (ADR-0005) and a job runner come later. Of the synthesis’s adapter slots, the front end and the expression parser are gone (ADR-0043), the runtime host is Bun only, the actor resolver was dropped (ADR-0007), and the tracer slot has no package because generated code calls the OpenTelemetry API directly (ADR-0029).

Options considered

Option A: Three rings (chosen)

Dimension Assessment
Complexity Higher: each replaceable part needs a contract owned by core
Cost More design up front; each contract needs tests (ADR-0013)
Reversibility High for adapters: swapping a database touches one package
Fit with serving any kind of program (ADR-0005) Good: no transport is assumed

Pros: A database or server can change without touching core. Mesh can serve programs with no web server.
Cons: Contracts cost effort and can be wrong. Ash’s data-layer contract has 46 callbacks, 44 optional, and degrades inconsistently (research synthesis, section 2.2); a contract designed too early repeats that.

Option B: A web framework as the core (Elysia)

Dimension Assessment
Complexity Lower: plugins, scoped lifecycle and dependency injection exist
Cost Low to start
Reversibility Low: everything hangs off a request
Fit with Mesh’s actions Poor: actions must run with no request (jobs, tests, agent tools, daemons)

Pros: Elysia has 12 official plugins and 96 community plugins (research synthesis, section 11).
Cons: Everything hangs off an HTTP request, with ten request phases. One person wrote 86% of its commits and it had one release in the last 90 days (same section).

Option C: Core owns the data layer; only features are plugins

This is the shape of Rails or Django: one query library built in, extensions for features.

Dimension Assessment
Complexity Lowest: fewer contracts
Cost Lowest now, highest to undo
Reversibility Low: Drizzle and SQLite types leak into core
Fit with the research Weak: Ash keeps its data layer behind a behaviour, with databases as separate packages (research synthesis, sections 2.2 and 4)

Pros: Fastest to a running example.
Cons: Hardcodes the risky part. The research lists Drizzle v1 as a release candidate whose relations v2 is a mandatory upgrade (same file, section 12, risk 1).

Trade-off analysis

Three rings cost contract design that a monolith avoids. The cost is accepted where something can really change: the database and query library, and transports. Where the operator later ruled that something cannot change (Bun, MX), it is core, and no contract is built for it. The roadmap makes each package state why it sits in its ring (roadmap, section 2, principle 4).

Consequences

  • Easier: replacing a database; core has no web concepts.
  • Harder: each contract is designed before a second implementation exists (one real data adapter until M9; roadmap, M3 risks).
  • Revisit: ADR-0030 (established tools first) pulls toward adopting tools; this rule says to put each behind a contract.

Action items

  • M1: state in each package, as it is created, which ring it is in and why (M1 and M2 create the first packages).
  • M3: write the data-layer contract (ADR-0013).