# ADR 0094: Auth hook composition is owned and atomic

> Source: https://github.com/max-listov/stitchkit/blob/master/docs/decisions/0094-auth-hook-composition-is-owned-and-atomic.md

---

# ADR 0094 — Auth hook composition is owned and atomic

## Context

Applications with several authentication domains manually dispatched scopes
and intersected inferred context maps. That duplicated framework mechanics,
could run an unrelated resolver and could expose partial context when a later
domain failed.

## Decision

`composeAuthHooks({ hooks, defaultScope })` composes only Stitchkit-created
scoped auth hooks. Module-private ownership metadata and a nominal type brand
associate each declared scope with its hook; consumers do not maintain a second
scope registry.

For a request, only owners of the selected scope run, in declaration order.
Each evaluates against an isolated shadow context. Contributions are prepared
without mutation, checked for reserved keys and cross-owner collisions, and
committed to the real runtime context once after every owner succeeds. Unknown
scopes fail closed. A default scope is explicit and belongs to the composed
surface.

## Consequences

- `ComposedAuthScopes` is inferred from the hooks themselves; manual type
  intersections and dispatchers disappear.
- A failed composed request cannot expose a partial contribution to a handler.
- Domain policy and resolver side effects remain application-owned and cannot
  be rolled back by the framework.
- Isolation is per-key, not deep. The shadow copies property descriptors, and a
  contribution is the set of keys whose descriptor changed, compared by
  `Object.is`. A hook that mutates an existing context value **in place**
  (`ctx.user.role = 'admin'`) therefore reaches the shared object and is not
  reported as that hook's contribution. Framework-owned keys stay protected
  either way; hooks are expected to contribute new fields, not edit another
  owner's object.

