# Contracts between kits — an interface in a package, and its version

> A kit that needs something done declares an ordinary public interface; another kit implements it; the app picks one in a setting. A member added with a default is a minor version, a change that breaks an implementor is a major one, and `osy lock` and `osy publish` refuse it under anything less.

<!-- id: project-contracts · area: project · stability: stable · html: https://osysharp.com/reference/project/contracts/ -->

## Summary        {#summary}

A **contract** is an ordinary `public interface` in a versioned package — `IMailSender` in `Osysharp.Mail`. An
**adapter** is a class in another package that implements it — `ResendSender` in `Osysharp.Mail.Resend`, which writes
`use Osysharp.Mail@0;`. The **app** picks an implementation in a setting (`app.Mail = new MailSetup { Sender = new
ResendSender { … } };`). Nothing here is special syntax: it is interfaces, packages and app settings, and the version
is what keeps the three honest.

```osy title="a contract, a default it grew, and an implementation that predates the default" test app=project-contracts
// In a contract kit: what carries a notification.
public interface INotifier {
  string Key { get; }
  string Notify(string to, string text);
  // Added in a MINOR version, with a body — so every notifier written before it still compiles, and says this.
  string Describe() => "the '" + Key + "' notifier";
}

// In an adapter kit, written against the version before `Describe` existed — unchanged.
public class PagerNotifier : INotifier {
  public string Key { get; } = "pager";
  public string Notify(string to, string text) { return "paged " + to; }
}

string Which() { INotifier n = new PagerNotifier(); return n.Describe(); }   // "the 'pager' notifier"
```

## Signature      {#signature}

```osy syntax
package Acme.Notify       { version "0.2.0"; model "model/*.osy"; }            // public interface INotifier { … }
package Acme.Notify.Pager { version "0.1.0"; use Acme.Notify@0; model "…"; }  // class PagerNotifier : INotifier
app.Notify = new NotifySetup { Notifier = new PagerNotifier { … } };          // the app's choice
```

## Description    {#description}

### Which changes need a major version?    {#versions}

For a CALLER of a package, adding a member is harmless. For an IMPLEMENTOR it is the breaking change: an adapter
written against the previous version does not declare the new member. So a contract's version answers to its
implementers:

| change to a public interface | breaks an implementor? | version |
|---|---|---|
| a member added **with** a default body | no — it inherits the default | minor |
| a default's body changed, or a default added to an existing member | no | minor (or patch) |
| a new contract | no — nothing implements it yet | minor |
| a member added **without** a default | yes — every implementor lacks it | **major** |
| a member removed, or its parameters or return type changed | yes | **major** |
| a member's default removed | yes — implementors that relied on it lack a body | **major** |
| the contract removed, or made non-public | yes | **major** |

The members counted include those an interface inherits from another interface of the same package
(`IWritable : IReadable`) — an implementor owes those too.

### Where is it checked?    {#checked}

**At `osy lock`**, which is where a new copy of a package is accepted into a project: the copy the lock pinned before
is compared with the new one, and a change that breaks implementers under the same major is refused, naming the
contract, the member and the rule:

```console
CONTRACT_BREAKS_IMPLEMENTERS: Osysharp.Mail 0.2.0 breaks every implementor of its contract: 'IMailSender.Region'
was added with no default (`string Region()`). An adapter built against Osysharp.Mail 0.1.0 would stop compiling,
so this is a MAJOR change — make it 1.0.0, or give `Region` a default body in the interface — which every existing
implementor then inherits, and which is a minor change.
```

Every verb that resolves a RELEASE goes through the same check — `osy lock`, `osy update`, and the pin `osy compile`
and a deploy record for a `use` not pinned yet — so no verb walks around it. A previous copy that is no longer in the
local package cache cannot be compared, and is not treated as a break. A package in your own workspace is read from its
source and never pinned, so it has no previous copy to compare with here; its release is compared at `osy publish`.

**At `osy publish`**, the release being created is compared with the newest one already published, by the same rule
and before any other surface check.

### What does an adapter that no longer fits see?    {#adapter}

An implementation missing a member is refused **at compile**, naming the contract, the member's shape, and — for an
adapter from another package — the version it was built against and the version this app composes:

```console
'Osysharp.Mail.Resend.ResendSender' implements 'Osysharp.Mail.IMailSender', which requires 'Region' with no default
— and 'Osysharp.Mail.Resend.ResendSender' does not declare it. Add `public string Region() { … }` (no `override`:
a contract member is implemented, not overridden). 'ResendSender' comes from Osysharp.Mail.Resend 0.1.0, built
against `Osysharp.Mail@0`, and 'IMailSender' is Osysharp.Mail 0.2.0's. …
```

When the adapter's constraint does not admit the contract's version at all, it says so and names the major to use:
`… built against Osysharp.Mail@0 — and this app composes Osysharp.Mail 1.0.0, outside that constraint … Use a version
of Osysharp.Mail.Resend built for Osysharp.Mail@1, or depend on Osysharp.Mail@0.`

### How do I find the contracts and their implementations?    {#finding}

`osy kits` lists every contract in scope with every implementation, and what this app wired in each setting;
`osy model --json` carries the same as `contracts` and `contractWiring` ([Finding a kit, and learning one from its tests](https://osysharp.com/reference/local/finding-a-kit/)).

### Can a contract keep a member for its own package to call?    {#internal}

Yes: declare the member `internal`. Any package may implement it, and only the contract's package may call it — so
every call goes through that package's own public function ([internal — a member only its own package may call](https://osysharp.com/reference/project/internal-members/)).

## See also       {#see-also}
- [internal — a member only its own package may call](https://osysharp.com/reference/project/internal-members/) — a contract member only its own package may call
- [Interfaces — a contract several types can satisfy](https://osysharp.com/reference/class/interfaces/) — interfaces and default members
- [package.osy](https://osysharp.com/reference/project/package/) — `package.osy`, `use`, publishing
- [Finding a kit, and learning one from its tests](https://osysharp.com/reference/local/finding-a-kit/) — `osy kits`
- [The Mail kit — send a message, keep a record, pick a sender](https://osysharp.com/reference/ui/mail-kit/) — `Osysharp.Mail`, a contract kit
