# Checking a kit composes — osy kits check

> Checks that a kit composes with the kits around it: it uses no other product kit it does not complete (KIT_REFERENCES_SIBLING), it never names a member a bridge adds to its own entities (KIT_NAMES_A_BRIDGES_MEMBER), a test shows every seam an app connects (KIT_SEAM_UNTESTED — required to publish), and its test app is about something of its own (KIT_TEST_APP_DOMAIN). It also compiles the kit into an app that keeps every default it offers (KIT_DOES_NOT_COMPOSE).

<!-- id: local-checking-a-kit · area: local · stability: stable · html: https://osysharp.com/reference/local/checking-a-kit/ -->

## Summary        {#summary}

Kits are valuable together only if each one can be added without dragging another in. `osy kits check` reads the kit
in a directory — its `package.osy` and its model — and reports what would stop it composing, each finding with its rule,
where it is, and what to write instead. It exits 1 on any MUST finding, so a kit's CI can hold it.

```console
$ osy kits check kits/widgets
MUST KIT_REFERENCES_SIBLING  package.osy:5  `Acme.Widgets` uses `Acme.Shop`, which is a vertical (`role vertical;`) — a product kit other kits meet only through a bridge. A kit that reaches into a sibling vertical makes every app that wants one take the other.
      fix: move what needs `Acme.Shop` into a bridge — a package that `use`s both and says `completes Acme.Widgets@0, Acme.Shop@0;` — which arrives by itself in an app that uses both; or, if `Acme.Widgets` exists only to complete it, add `Acme.Shop` to its own `completes` line.
✗ Acme.Widgets — 1 finding.
```

## Signature      {#signature}

```console
osy kits check [<dir>]      # the kit in <dir> (default: the current directory)
  --offline                 # find the bridges that complete it in the workspace and the platform only, not GitHub
  --json                    # { kit, role, findings: [{ rule, tier, file, line, message, fix }], notes }
```

## Description    {#description}

### What is a vertical, and why may a kit not use one?    {#sibling}

A kit says what it is to the kits around it with `role` in its `package.osy` — a bare word:

| `role` | what it is | may another kit `use` it? |
|---|---|---|
| `hub` | it holds the nouns other kits meet at — people, organisations, work items | yes |
| `horizontal` (the default) | something kits build on freely — the UI kit, mail, reports, automations | yes |
| `vertical` | a product — a shop, a knowledge base, a conversation desk | only a [bridge](#bridges) that completes it, or an extension of it |

**`KIT_REFERENCES_SIBLING`** (MUST) refuses a kit that `use`s a vertical it neither completes nor extends. An extension
is a kit whose name continues the vertical's — `Osysharp.Shop.Discounts` on `Osysharp.Shop`. Two verticals meet in a
bridge instead, which arrives by itself in an app that uses both ([Bridges — a package that arrives when an app uses the two it completes](https://osysharp.com/reference/project/bridges/)): that is what keeps the number of
dependencies growing with the kits, not with every pair of them.

### What may a kit not say about its own entities?    {#bridges}

A bridge may add optional members to another kit's entities ([Extension members](https://osysharp.com/reference/entity/extension-members/)): `Osysharp.Crm.Cpq` adds
`LatestQuote` to CRM's `Opportunity`. **`KIT_NAMES_A_BRIDGES_MEMBER`** (MUST) refuses the CRM kit naming `LatestQuote`
itself. CRM's own compile has no bridge in it, but an app that uses CRM and CPQ does — so a name that only resolves with
the bridge is a kit that works in one composition and not in another. The code that needs the member belongs in the
bridge.

The bridges that complete the kit are found where `osy lock` finds them — the workspace, the platform's packages, and
GitHub (`--offline` leaves GitHub out) — and the notes list the ones it looked at.

### Every seam is shown by a test    {#seams}

A kit's tests are its worked examples: `osy kits <kit> --surface` prints, beside each seam, the test app code that
connects it and the tests that show it working ([Connecting a kit — each seam with the code that connects it](https://osysharp.com/reference/local/connecting-a-kit/)). **`KIT_SEAM_UNTESTED`** names a seam an app
connects — an event, a policy the kit asks for, a setting, a slot, a contract, an abstract base, an extension member, an
action, a page the kit links to — that no test of the kit's test app reaches. It is SHOULD here and **MUST at
`osy publish`**: a seam is never published without its example. Each finding says what to add for that kind of seam:

```console
SHOULD KIT_SEAM_UNTESTED  model/reviews.osy:26  no test shows `ReviewWithdrawn` (event: [Event("Review withdrawn")] ReviewWithdrawn { Review }). An app connecting this kit is shown each seam by the test that exercises it (`osy kits --surface`); this one it would only be told about.
      fix: in the test app, hear it — `[On] void WhenReviewWithdrawn(ReviewWithdrawn e) { … }` writing something of the app's own — and add a test that makes it happen and asserts what the handler left.
```

**`KIT_TEST_APP_DOMAIN`** (SHOULD) — a kit that works on an app's records (it takes an `Entity`, offers an abstract
base, or raises an event about any record) has a test app with a record of its own, so its tests prove it works for an
app that is not about the kit. An adapter — a mail sender, a carrier — has no app record to be about and is not asked.

### Does it compile for an app that keeps every default?    {#defaults}
A kit's test app usually overrides each `virtual policy` the kit offers — that is how it shows the seam — so a default
that does not compile in a consumer stays hidden there, and the first app that keeps it fails instead, in a kit file
its author never wrote. **`KIT_DOES_NOT_COMPOSE`** (MUST) composes the kit into an app that defines only what the kit
asks it for and overrides nothing, and reports every error that app meets, on the kit's own line. It is the same
composition `osy validate` checks in the kit's directory.

```console
$ osy kits check kits/crates --offline
MUST KIT_DOES_NOT_COMPOSE  model/crates.osy:9  in an app that uses `Acme.Crates` and keeps every default it offers (no `virtual policy` overridden): a security policy predicate must be Boolean, but is 'string'
```

### What a clean run says    {#clean}

```console
$ osy kits check kits/work-queues --offline
  · bridges that complete Osysharp.WorkQueues: Osysharp.WorkQueues.Notifications, Osysharp.WorkQueues.Reports
✓ Osysharp.WorkQueues (horizontal) — composable: it reaches no sibling vertical, names no member a bridge adds to it, and a test shows every seam.
```

## See also       {#see-also}
- [Bridges — a package that arrives when an app uses the two it completes](https://osysharp.com/reference/project/bridges/) — `completes`, and how a bridge arrives
- [package.osy](https://osysharp.com/reference/project/package/) — `package.osy`
- [Finding a kit, and learning one from its tests](https://osysharp.com/reference/local/finding-a-kit/) — `osy kits`: the kits in scope and their contracts
- [Connecting a kit — each seam with the code that connects it](https://osysharp.com/reference/local/connecting-a-kit/) — `osy kits <kit> --surface`: each seam with the code and tests that show it
- [Extension members](https://osysharp.com/reference/entity/extension-members/) — members a bridge adds to another package's entity
