# Adopt ContractSpec in an existing codebase

Wrap one behavior you already own, preserve its observable behavior, and expand from evidence.

Source context: Source-guided
Source: https://github.com/lssm-tech/contractspec/blob/d58fa5b7de53a947811e5cc68346be028ce18528/packages/foundation/contracts-runtime-kernel/src/operations/registry.ts

## Select a small adoption seam

Choose a function or endpoint that already has a clear owner and useful tests. Prefer a contained read or a reversible local action for your first exercise. A workflow that spans several providers, jobs and billing decisions makes it difficult to tell whether a problem comes from your application or the integration.

Write down the behavior callers depend on before changing the boundary: accepted inputs, returned data, error cases, authorization and externally visible effects. This is your compatibility checklist.

| Existing concern | ContractSpec responsibility | Application responsibility |
| --- | --- | --- |
| Request shape | Describe and validate the operation input | Map the existing request into that shape |
| Business logic | Bind the operation to a handler | Keep the implementation and domain invariants |
| Identity and permissions | Carry requirements and decision context | Authenticate the caller and provide trusted policy decisions |
| HTTP behavior | Project through a chosen adapter | Preserve intended routes, status semantics and clients |

## Wrap the implementation before replacing it

Define a contract that reflects the behavior you intend to support. Bind the existing service function through a small handler rather than moving the entire service into the contract declaration. Make input and output translations explicit.

Register the exact contract version and attach the handler to that registration. A declaration without a bound handler is discoverable information, not a runnable operation. Keep database clients and provider secrets in host-owned code.

## Compare at the boundary

Run the same representative requests against the old boundary and the new one in a disposable environment. Compare meaningful values and failures rather than incidental timestamps. Include a denied or malformed request and prove it cannot invoke the protected effect.

When transport names or response envelopes change, treat the difference as an API change. Add a deliberate compatibility mapping or update consumers together; do not assume shared types preserve a wire protocol.

## Expand when the first seam pays off

A useful next step is a second interface to the same registered behavior, such as a human form and an MCP tool. You can then see which rules stopped being duplicated and which responsibilities remain in the host.

Importer output can help inventory a larger system, but inferred contracts still need review. Existing code can reveal shape and routing while missing the business reason for a policy or the scope of an approval. Make those decisions explicit before expanding.

## Related examples

- [Write your first contract](https://lssm.co/examples/minimal-user-contract/): Follow user.create from an email schema to a typed handler. Start with the smallest maintained example.

