# Writing a kit — its parts, and every seam an app connects

> The page to read before writing a kit. What `osy kits new` makes, and each seam in a few lines with the one page that covers it: the app's answer to your policies, a field only your function changes, rows that belong to other people, calling another kit, events, settings, the app's own rules found by interface, an app's own subtype, personal data and its bridge, pages and their screenshots, and testing a rule behind a deny.

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

## Summary        {#summary}
A kit is one capability — its data, the rules over it, its pages — that any app can use with one `use` line. It names
nothing of the app: where it needs the app's answer (who manages it, who may see whose rows) it asks with a policy.

## Description    {#description}

### What does `osy kits new` make?   {#anatomy}
`osy kits new Lockers` writes `kits/lockers/`: `package.osy`, `model/` and `tests/` — an app of its own that uses
only the kit. `osy test kits/lockers/tests` runs it; `use <Owner>.Lockers@0;` uses it. See [package.osy](https://osysharp.com/reference/project/package/).

### How does a kit ask the app who may do something?   {#policies}
`policy ManagesLockers;` — every app must answer. `virtual policy ManagesLockers => false;` — nobody, until the app
says. A parameterised one's default must READ its parameter (`virtual policy SeesLockersOf(Principal holder) =>
holder == user || ManagesLockers;`); one that ignores it is refused in a `where`, because it never reads the row.
See [[security-naming-a-policy#supplied-default]].

### How do I make a field only my function changes?   {#except-via}
`deny update Left except via Spend;`, with `[Server, Authorize(P)]` on `Spend`. The deny steps aside only for
`Spend`'s own writes — the caller still needs an `allow update Left` — and the kit's own workflow, running as the
system, needs neither. See [deny … except via](https://osysharp.com/reference/security/except-via/).

### How does someone change a row that belongs to another person?   {#others-rows}
Through the function the deny names — an `allow … where Owner == user` opens nothing past it; who may call it is its
`[Authorize]`. Work that must see rows the caller cannot belongs in the kit's workflow, which runs as the system. See
[What may go past the caller's own rights — and what never does](https://osysharp.com/reference/security/past-the-callers-rights/).

### My kit calls another kit's guarded function — who may?   {#calling-a-kit}
Its `[Authorize]` is checked at every call, nested ones too. When a step must happen whoever asks, make the request a
row and let its workflow's `enter` take the step — as the system, in the same commit. See [enter and exit (a state's arrival and departure hooks)](https://osysharp.com/reference/workflow/enter-exit/).

### How do I tell the app something happened?   {#events}
Declare `[Event("…")] public sealed class X { … }`, `Raise(new X { … })` in your function; the app hears it with
`[On] void H(X e)`, after the commit. See [[Event] — what happens to a record, declared once](https://osysharp.com/reference/function/event/) and [[On] — hear an event, after the commit that raised it](https://osysharp.com/reference/function/on/).

### What can the app configure?   {#settings}
A class marked `[AppSetting("Name")]`, filled by `app.Name = new … { … };`, read with `App.Setting<T>()` on the server;
a member typed as a class with `virtual` methods lets the app override behaviour. See [settings a package asks its app for (app settings)](https://osysharp.com/reference/config/app-settings/).

### How does the app plug in a rule of its own?   {#interfaces}
Declare `public interface IShiftRule { string? Refusal(Shift s, Principal p); }` and ask every
`App.Implementations<IShiftRule>()`: the app's classes are found, never wired. When another kit can answer the same
question (a qualifications kit for a shift's requirements), ship a bridge — `completes <Kit>@0, <OtherKit>@0;` with the one
class — which arrives by itself in an app using both ([Bridges — a package that arrives when an app uses the two it completes](https://osysharp.com/reference/project/bridges/)). A bridge may also hear one kit's event and
call the other's guarded function (`[On] void Grant(PurchasePaid e) { GrantCredits(…); }`): a handler runs with the
app's authority.

### What happens when an app derives from my entity?   {#inheritance}
`entity Employee : User` is its own type: your grants do not carry over, your DENIES do, and the app cannot widen
what your rules allow. So a field your functions write for any caller (a count of places sold) belongs on a row of
its own, not on the derivable entity. See [[entity-inheritance#security]].

### How do people's data reach a privacy request?   {#privacy}
Say whose a row is (`[DataSubject]`, `[PersonalData(…)]`) and classify every personal field, free text included
(`[Classification(Personal.Content)]`), so the audit keeps no copy. When erasing must go through your function, ship
a bridge `Osysharp.Privacy.<Kit>` (`completes Osysharp.Privacy@0, <Kit>@0;`) with an `IPersonalDataHolder`
([Bridges — a package that arrives when an app uses the two it completes](https://osysharp.com/reference/project/bridges/)). See [Declared personal data — say whose a row is, and the platform answers for it](https://osysharp.com/reference/security/declared-personal-data/).

### Where do its pages go?   {#pages}
At the kit's own `[Route]`, in `[Layout(WorkspaceFrame)]`, around `[Composable]` screens, theme tokens only, so a look
re-skins them; an app takes one over by declaring a component of the same name. A component keeps the state it
mounted with when a parameter changes, so open one record's detail in a `Dialog` rather than a side panel fed a new
id. One `Ui.*` test per page;
`osy test --pixels` shots go in the README (from `tests/osy-shots/` to the kit's `docs/`). See [component](https://osysharp.com/reference/ui/component/).

### How do I test a rule that sits behind a deny?   {#testing}
Seed the first row in a fixture (it writes past security), then try the second as a person, through the function. A
fixture's READS obey the rules: keep the rows you made in variables. See [[testing-fixtures#behind-a-deny]].

```osy title="a field only its function changes, proved as the person" run app=local-writing-a-kit
[Principal] entity Member { [Unique, MaxLength(80)] string Name; security { allow read when IsAuthenticated; } }

entity Tally {
  [Required] Member Holder;
  int Left = 3;
  security {
    allow read, create, update when IsAuthenticated where Holder == user;
    deny update Left except via Spend;
  }
}

[Server, Authorize(IsAuthenticated)]
void Spend(Tally t) { t.Left = t.Left - 1; }

void SetLeft(Tally t) { t.Left = 9; }

[TestFixture]
void OneTally() { new Tally { Holder = new Member { Name = "Ida" } }; }

principal Ida => Member.Single(m => m.Name == "Ida");

[Test(OneTally)]
[runas(Ida)]
void Only_the_function_changes_it() {
  var t = Tally.Single(x => x.Holder.Name == "Ida");
  Assert.Denied(() => SetLeft(t));
  Spend(t);
  Assert.Equal(2, Tally.Single(x => x.Id == t.Id).Left);
}
```

### Is it ready to publish?   {#publish}
`osy kits check kits/lockers` composes it the way an app would and flags what an app would trip on
([Checking a kit composes — osy kits check](https://osysharp.com/reference/local/checking-a-kit/)); `osy publish` runs its tests and tags the release ([[project-package#publishing]]).

## See also       {#see-also}
- [Finding a kit, and learning one from its tests](https://osysharp.com/reference/local/finding-a-kit/) — before writing one: is there a kit already?
- [Connecting a kit — each seam with the code that connects it](https://osysharp.com/reference/local/connecting-a-kit/) — each seam as an app sees it
- [What may go past the caller's own rights — and what never does](https://osysharp.com/reference/security/past-the-callers-rights/) — everything that writes past a caller's own rights
