Files
clean-architecture-frontend…/docs/architecture/contract-ownership.md
T

4.2 KiB

Contract ownership

src/contracts is not a default destination for every shared-looking type. A contract belongs there only when the change authority is genuinely shared across layers, capabilities, build tooling, or runtime composition.

Decision rule

For every proposed contract, ask:

  1. Which requirement can cause this type or policy to change?
  2. Is there one clear capability or feature owner?
  3. Does another runtime/tooling boundary consume the same semantic contract?
  4. Is the contract a wire/artifact authority shared by browser runtime and build/release tooling?

The placement rule is:

  • one feature owner -> keep it under that feature,
  • one reusable capability owner -> keep it under that adapter/capability and export it through the capability public entry point,
  • multiple independent capability/layer owners -> src/contracts,
  • browser/build/release wire or artifact authority -> src/contracts even when the browser source graph alone looks small.

Consumer count alone is not sufficient. Scripts, generated artifacts and release gates are semantic consumers too.

Audit result

The September 2026 architecture review triggered an import-graph audit of all 39 contract files.

Capability-owned contract moved

cursor-pagination.ts had one production owner: src/adapters/query-cache/cursor-pagination-runtime.ts.

It moved to:

src/adapters/query-cache/cursor-pagination-contract.ts

and is exported through:

src/adapters/query-cache/index.ts

Tests use that public capability entry point. Pagination vocabulary no longer occupies the global contract bucket merely because it is reusable inside one adapter.

Contracts intentionally kept global

The following examples have multiple semantic owners and remain global:

  • errors.ts — application, presentation and several adapters,
  • result.ts — common success/failure carrier below application,
  • boundary-mapper.ts — HTTP, browser RPC, realtime and feature registries,
  • mutation-intent.ts — application, presentation, HTTP, platform and bootstrap,
  • exact-snapshot.ts — HTTP, query-cache and browser-transfer,
  • rest-profiles.ts — auth, HTTP, bootstrap and feature contract validation,
  • query-invalidation.ts / query-keys.ts — query-cache, presentation, bootstrap and features,
  • cache-invalidation.ts — cross-context wire protocol plus query invalidation policy,
  • storage-keys.ts — browser storage and cross-context invalidation,
  • telemetry.ts / diagnostics.ts — runtime adapters, application and bootstrap.

Some contracts appear to have few browser-source consumers but are still shared authorities:

  • env.ts is consumed by bootstrap, runtime-schema/security tests and registry governance,
  • deployment-admission.ts is shared by runtime-config generation and release admission,
  • release-tokens.ts is shared by runtime coherence tooling and tests,
  • service-worker-static-manifest.ts is a runtime-neutral canonical format shared by build generation, validation and service-worker evidence.

Moving those based only on src/** import counts would split one semantic authority across processes.

Feature contracts

Feature-specific contracts stay inside the vertical slice:

features/<feature>/contracts

The reference feature owns routes, schemas, mapper definitions, message catalogs and contribution identities. Central installed files aggregate those contributions; they do not own their semantics.

The message catalog is a deliberate special case: the compiled catalog remains total even when a feature is build-time disabled so the typed message lookup does not become partial. The central message file therefore aggregates compiled message keys rather than treating runtime installation as message ownership.

Review rule for future additions

A new file under src/contracts should be rejected during review when all of the following are true:

  • one feature or one capability is the only semantic owner,
  • no build/release/runtime wire authority needs the same definition,
  • moving the definition to that owner does not create an inward dependency violation.

Do not move a contract merely to reduce the number 39. The goal is explicit ownership, not a smaller directory.