# Bridges — a package that arrives when an app uses the two it completes

> A bridge is a package whose manifest says `completes A@0, B@0;`. An app that uses every package it completes gets it without writing a `use` line: `osy lock` finds it, pins it, and says so. `without` in the app manifest keeps one out.

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

## Summary        {#summary}

Two kits that know nothing of each other often have something to say together: privacy requests that reach a
desk's conversations, an inbox whose people are told in the bell, a shop that checks age with BankID. That code lives
in a **bridge** — a package that `use`s both and declares it **completes** them. An app that uses both kits gets the
bridge by itself. Nobody has to know the bridge exists, and nothing breaks for an app that uses only one of the two.

```osy title="the app writes the kits it wants, and keeps one bridge out" test app=project-bridges
app Sales {
  model "model/**/*.osy";
  // A bridge that would arrive with this app's packages, kept out — `osy lock` says which bridges arrive.
  without Acme.Crm.Cpq;
}

entity Lead {
  [Required, MaxLength(120)] string Name;
}
```

## Signature      {#signature}

```osy syntax
// package.osy of a bridge
package Owner.A.B {
  version   "0.1.0";
  completes Owner.A@0, Owner.B@0;   // each one a package this bridge uses, at that major
  use Owner.A@0;
  use Owner.B@0;
  model "model/*.osy";
}

// app.osy of an app that keeps it out
app X { without Owner.A.B; }
```

## Description    {#description}

### What makes a package a bridge?    {#completes}

One line in its `package.osy`: `completes`, then the packages it completes, each with the major it was written for.
Each one must be a package the bridge itself `use`s, at that same major — a bridge composes over what it names, so it
has to depend on it. The compiler refuses anything else, naming the line to write:

| refusal | when |
|---|---|
| `PACKAGE_COMPLETES_UNUSED` | a package the bridge completes is not one of its `use` lines |
| `PACKAGE_COMPLETES_MAJOR` | the major differs from the one the bridge `use`s |
| `PACKAGE_COMPLETES_NO_MAJOR` | a package is named without its `@major` |
| `PACKAGE_COMPLETES_ONE` | only one package is named — the bridge would arrive in every app that uses it; an add-on an app chooses is an ordinary package with a `use` line |
| `PACKAGE_COMPLETES_REPEATED` · `PACKAGE_COMPLETES_ITSELF` | a package named twice, or the bridge naming itself |

A bridge may complete three packages; it arrives when an app uses all three.

### When does a bridge arrive?    {#arrives}

When an app's own `use` lines include every package the bridge completes, at the majors it names. A `tests use` line
does not count — the app you deploy is the same with or without its tests. `osy lock` asks every catalog the project
resolves packages from which packages complete the ones the app uses, resolves each one it is offered, reads the
bridge's own manifest, and includes the bridge when its whole `completes` set is there:

```console
$ osy lock
✓ Wrote osyrin.lock (54 pins)
  …
+ Osysharp.WorkQueues.Notifications 0.2.0 included: completes Osysharp.WorkQueues + Osysharp.Notifications (keep it out with `without Osysharp.WorkQueues.Notifications;` in app.osy)
+ Osysharp.Privacy.UserAccounts 0.2.0 included: completes Osysharp.Privacy + Osysharp.UserAccounts (keep it out with `without Osysharp.Privacy.UserAccounts;` in app.osy)
  · Osysharp.Privacy.Sms not included — completes Osysharp.Privacy + Osysharp.Sms, and this app does not use `Osysharp.Sms`.
  bridges searched in workspace, platform, github
```

An included bridge is a `use` in every respect but its spelling: it is composed, the app may import its names with
`using`, and it is installed and granted like any other. An app that sets a bridge's options names its setting class
as usual — `app.CrmCpq = new CrmCpqSetup { … };` — and writes nothing else. A bridge that needs a host or a secret is
granted on a `use` line of its own: write `use Owner.A.B@0 { egress "…"; }`, and it is then the app's choice like any
package it writes.

### What does the lock record?    {#lock}

The included bridge's entry says why it is there, and a `bridges` record says what the search saw — which catalogs
answered, which were skipped, what was offered and not included, and what the app left out:

```json
{
  "name": "Osysharp.WorkQueues.Notifications",
  "kind": "package",
  "version": "0.2.0",
  "hash": "sha256:…",
  "source": "workspace:kits/work-queues-notifications",
  "included": "completes Osysharp.WorkQueues + Osysharp.Notifications"
},
…
"bridges": {
  "searched": ["workspace", "platform", "github"],
  "notIncluded": [
    { "name": "Osysharp.Privacy.Sms",
      "reason": "completes Osysharp.Privacy + Osysharp.Sms, and this app does not use `Osysharp.Sms`." }
  ],
  "leftOut": ["Acme.Crm.Cpq"]
}
```

**`osy lock` is the only verb that searches.** Every compile after it — `osy validate`, `osy test`, `osy compile`, a
deploy, the server — reads the bridges from the pinned packages themselves, and never reaches a network for them. A
compile that re-pins a package changed in your own repository keeps the lock's bridges as they are, re-checking each
against the app's `use` and `without` lines. To look again — a bridge was published since — run `osy lock`.

### Where are bridges found?    {#catalogs}

In every place the project resolves packages from, in the same order a `use` resolves:

| catalog | what it holds |
|---|---|
| **workspace** | the packages in your own repository — its `kits/*`, and whatever an `osy.workspace` lists |
| **platform** | the packages the platform ships with the CLI |
| **github** | every published package: a repository carrying the `osy-package` topic. `osy publish` gives a bridge one more topic per package it completes — `osy-completes-osysharp-inbox` — and the lock reads them all in **one** search, then asks only a matching repository for its releases. When GitHub's search does not answer (rate-limited, down), the published package index is read instead, and the lock records that it was |

An organisation's own catalog answers the same question when it is configured. A catalog that could not be asked is
recorded under `skipped`, so a reader of the lock knows a bridge published there may be missing from it.

### Locking without a network    {#offline}

`osy lock --offline` searches only the workspace and the platform's packages for bridges, and records that the public
search was skipped:

```json
"bridges": {
  "searched": ["workspace", "platform"],
  "skipped": ["github: offline — the public catalog was not searched for bridges. Run `osy lock` with a network to look."]
}
```

### How do I keep a bridge out?    {#without}

`without <Package>;` in the `app { }` manifest — the example above. The bridge is not pinned, not composed, and its
names cannot be imported; the lock lists it under `leftOut`. A `without` that names a package the manifest also `use`s
is refused (`WITHOUT_AND_USE`): one line has to go. A `without` that names no bridge any catalog offered leaves nothing
out, and the lock says so under `notIncluded`, so a typo is not silent.

⚠ This is the top-level `without`, which keeps a whole **package** out. Inside a `use` body, `without` leaves out one
of that package's contributions to a page ([[Slot] and contributions — a place on a page that other kits, and the app, add to](https://osysharp.com/reference/ui/contributions/)).

### Why did a bridge not arrive?    {#not-included}

Read the lock's `notIncluded` list: each bridge a catalog offered carries the reason — a package it completes that
this app does not use, or one it completes at another major (`it completes Acme.Crm@1, and this app has Acme.Crm
0.3.0 — another major, which this 0.1.0 was not written for`). If it is not listed at all, no catalog offered it: run
`osy lock` with a network, and check the bridge is published with its `osy-completes-…` topics.

### Publishing a bridge    {#publish}

`osy publish` sets `osy-package` and one `osy-completes-<owner>-<name>` topic per package the bridge completes, beside
any topics the repository already has. A topic is at most 50 characters; a name too long for that ends in a short hash
of the full name, computed the same way by the publisher and the lock. Check a bridge composes with
[Checking a kit composes — osy kits check](https://osysharp.com/reference/local/checking-a-kit/) before publishing it.

## See also       {#see-also}
- [package.osy](https://osysharp.com/reference/project/package/) — `package.osy`, `use`, publishing
- [app.osy](https://osysharp.com/reference/project/manifest/) — the `app { }` manifest
- [Checking a kit composes — osy kits check](https://osysharp.com/reference/local/checking-a-kit/) — `osy kits check`: a kit reaches no sibling vertical, and names no member a bridge adds
- [Extension members](https://osysharp.com/reference/entity/extension-members/) — a bridge adding optional members to another package's entity
- [Contracts between kits — an interface in a package, and its version](https://osysharp.com/reference/project/contracts/) — a contract one kit declares and another implements
