# ADR 0002 — A generic core: the framework carries no domain model

> Source: https://github.com/max-listov/stitchkit/blob/master/docs/decisions/0002-generic-core.md

---

# ADR 0002 — A generic core: the framework carries no domain model

- **Status:** Accepted
- **Date:** 2025-05

## Context

stitchkit was extracted from several unrelated projects — a chat-bot platform, a
media-generation app, and others. An earlier in-house framework had baked one
project's domain model straight into the core:

- `EndpointScope = 'public' | 'client' | 'bot' | 'admin' | 'project'` — five
  hardcoded scopes, each with its own typed context (`botId`, `projectId`, …).
- `requiredFeature?: PlanFeature` on every endpoint — billing in the core.
- a `Source` enum imported from the database (`EMAIL`, `SCHEDULER`,
  `WEBHOOK`, …) used in the handler context.

None of it transferred. A media app has no bots; another project has no
"projects" in that sense. The domain model was dead weight everywhere but its
origin.

## Decision

The framework carries **no domain model**. Every domain-specific concept is
pushed to the application.

- **Scopes are free strings.** `scope?: string` on an endpoint — any value. The
  application defines what its scopes mean and enforces them in a `beforeHandle`
  hook (see ADR 0004, ADR 0008).
- **No billing in the core.** Plan/feature gates are an application concern.
  An app extends `EndpointDef` with its own fields and checks them in a hook.
- **Transport-only source.** `source: TransportSource = 'http' | 'mcp' |
  'agent'` — where the call physically entered. Application-level origins
  (a scheduler, a webhook) are not the framework's concern.
- **Extensible context.** One `HandlerContext` with `[key: string]: unknown` —
  each app adds its own fields (`userId`, `sessionId`, …) through hooks.

## Consequences

- The framework is reusable across unrelated domains without modification.
- Applications wire identity, scopes and billing themselves — more setup, but
  no framework dictates the domain.
- Type safety for app-specific context is preserved by ADR 0003 and the
  `createImplement<Ctx>()` factory: the app declares its context type once.
