---
id: w1/m2.5/t001
title: Leaf kernel + thin composition root + registration contract
worker: worker1
status: done
estimate: 40m
depends_on: []
---

## Objective

Lay the skeleton every feature extraction plugs into: a **leaf kernel** package that feature packages import, and a **thin composition root** that imports the features (so there is no import cycle). Define the registration-fragment contract that lets a feature contribute REST routes, GraphQL fields, and MCP tools to _single_ shared roots.

## Context

- Today `internal/api` is both the domain (`core.go`) and the composition root (`server.go`) and every adapter — a flat ~20-file package. To split by feature without a cycle, the shared pieces must move to a **leaf** the features import; the root must sit **above** the features.
- Load-bearing invariant (`internal/api/CLAUDE.md`): three transports are single artifacts. Fragments _register into_ shared roots (`graphql.Fields` merged into root Query/Mutation; handlers registered on one `mux`; tools registered on one MCP registry) — they never stand up their own server.
- The auth gate, CORS, and `newSchema()` assembly live in `server.go` today and stay in the root.

## Steps

1. Create leaf `internal/core`: the shared base (`Client`, `Namespace`, `now()`), the `authorize()` gate signature + `Checker` interface, the cross-feature error sentinels (`ErrNotFound`/`ErrBadRequest`/`ErrForbidden`/`ErrAuthzUnavailable`/…), and the **registration interfaces** (e.g. `RESTRegistrar`, `GraphQLProvider`, `MCPRegistrar`).
2. Move the OpenFGA checker into `internal/authz` (imports `core` for the `Checker` contract / relations). Keep `relCanView/…` here.
3. Create the composition root (slim `internal/api` or new `internal/server`): `Server`, auth middleware, CORS, and the assembly that merges each feature's fragments into one GraphQL schema + one mux + one MCP registry behind one auth gate. It imports feature packages; features must **not** import it.
4. Keep everything building/green with the current single feature still living in the old files (features move in t002–t005).

## Files

- `lego/backend/internal/core/` (new leaf: base, gate, errors, registration contract)
- `lego/backend/internal/authz/` (moved from `internal/api/authz.go`)
- `lego/backend/internal/api/` or `internal/server/` (composition root: `server.go` slimmed to assembly)

## Acceptance criteria

- [ ] `go build ./... && go test ./...` green in `lego/backend`.
- [ ] Import direction is one-way: `feature → core`, `root → {features, core}`; `go vet`/build proves no cycle.
- [ ] The registration contract is defined and the root assembles the (still single) surfaces through it.

## Out of scope

- Moving any feature's business logic yet (t002–t005).
- Any wire/behavior change.
