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:
- Which requirement can cause this type or policy to change?
- Is there one clear capability or feature owner?
- Does another runtime/tooling boundary consume the same semantic contract?
- 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/contractseven 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.tsis consumed by bootstrap, runtime-schema/security tests and registry governance,deployment-admission.tsis shared by runtime-config generation and release admission,release-tokens.tsis shared by runtime coherence tooling and tests,service-worker-static-manifest.tsis 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.