Summary#
osy kits answers two questions offline. Which kits can this app use, and how do they fit together? — every
package in scope, the contracts each declares, which kits implement each, and what THIS app put in each contract-typed
setting. How do I use this one? — osy kits <name> prints the kit's purpose, then its test app (the smallest real
program that uses it), then the name of every test it keeps.
Signature#
osy kits [name] [--full] [--tests <file>] [--project <dir>] [--json]name is a package name (Osysharp.Mail), its last segment (mail) or its directory in this repository
(mail-resend). --project points at the app whose wiring to report (default: the current directory).
Description#
What is in scope, and who implements what?#
With no name, osy kits lists every kit this project can see — the ones in the repository's workspace, the ones this
app's lock pins, and the ones the platform carries — each with its version, where it comes from and its summary. A kit
that declares a contract (a public interface other kits implement) lists it with every implementation in scope; a
kit that implements one says which:
Osysharp.Mail 0.2.0 · workspace:kits/mail used by this app
mail an app sends — one message shape, a pluggable sender, an outbox that delivers after the commit …
contract IMailSender — implemented by ResendSender (Osysharp.Mail.Resend)
Osysharp.Mail.Resend 0.1.0 · workspace:kits/mail-resend
implements IMailSender (Osysharp.Mail) with ResendSenderWhat did this app wire?#
Run in an app, the listing opens with what the app put in each contract-typed field of an app setting — the
app.X = new Setup { Field = new Implementation { … } } lines — or, when it put nothing, the declaring kit's own words
for what that means:
What MailResendKitTests wired
app.Mail.Sender (IMailSender) = ResendSender (Osysharp.Mail.Resend)…and in an app whose app.Mail names no sender:
app.Mail.Sender (IMailSender) = none — every message is HELD in the outbox — the app works, and nothing is sent.The same answer is in osy model --json, as contracts and contractWiring, for a tool.
How do I learn one kit?#
osy kits <name> prints, in this order:
- Its purpose — the package's summary, then the comment block at the top of its
package.osy, where its author explains what it is for and what it deliberately is not. - Its contracts — each interface, its members, which of them have a default, and who implements it.
- Its test app, whole —
tests/app.osyand its model files. It is the smallest correct program that uses the kit: theuselines, the setting the app fills, the entity it extends. - Every test, by name, grouped by file — the promises the kit keeps, one line each; a parked one is marked.
--full prints the test files whole, and --tests <file> prints one. A kit fetched from a release shows them too:
they ship beside the package as the release's examples.zip, and are cached with it. A release published before kits
shipped their tests says so, rather than showing nothing. To connect the kit, osy kits <name> --surface shows each
seam with the test app's code that connects it (Connecting a kit — each seam with the code that connects it).
osy kits mail # purpose, contracts, the test app, the test names
osy kits mail --tests mail.test.osy # one test file, whole
osy kits mail --full --json # everything, for a toolA kit by what it does — osy kits find#
osy kits find "<need>" ranks every kit in scope by what it says it does — its summary and the comment at the top of its
package.osy, its need: lines, the names and first doc sentences of what it declares, its events' labels — so you can
ask in your own words. A word most kits share counts for little; a bridge ranks below the kits it completes (it arrives by
itself). Each match shows what the next step needs: the events it raises, its actions and slots, the kits it uses, the
bridges that complete it with kits this project already uses, and the domain of its test app — a kit proven by a cycle
workshop is not secretly a helpdesk. Unless --offline, the published packages on GitHub are searched too — and the
answer is kept for 30 minutes per question (~/.osy/package-search/), because GitHub allows 10 unauthenticated searches
a minute. When that limit is spent, the last answer to the same question is shown with its age, or, with none, one line
says the kits in scope were ranked alone; OSY_GITHUB_TOKEN (or gh auth login) raises the limit to 30.
$ osy kits find "ask customers how it went after the work is done"
Osysharp.SatisfactionSurveys 0.5.0 · workspace:kits/satisfaction-surveys used by this app
asking the person a piece of work served how it went — one good-or-bad answer and an optional comment per subject and …
matched: ask, customer, went, after, work, done
events FeedbackAnswered
uses Osysharp.Identity, Osysharp.Workflow, Osysharp.Mail, Osysharp.EmailTemplates, Osysharp.Ui
completes with what you have: Osysharp.Privacy.SatisfactionSurveys (completes Osysharp.Privacy + Osysharp.SatisfactionSurveys)
proven by: a cycle workshop's repair bookingsA kit matches when ONE thing it says — its name, its summary, one need: line, one event — holds most of the need's
words. When none does, the nearest are shown instead, under a line saying none says all of it: a kit NAMED for
one of the need's words (Osysharp.Csv for "import a CSV file of rows"), then its family (Osysharp.Payments.Stripe
for "card payments"), then a kit that says two of the need's rarer words together. Each shows the words it matched:,
so read its summary before deciding it does what you need. "email" is read as "mail", and -ize as -ise.
$ osy kits find "send notifications by email or sms"
No kit in scope says all of that — the nearest, each saying part of it (`matched:`). …
Osysharp.Sms 0.5.0 · workspace:kits/sms
matched: send, sms
Osysharp.Notifications 1.2.0 · workspace:kits/notifications
matched: notification, mail
Osysharp.Mail 0.11.0 · workspace:kits/mail
matched: send, mailWhen nothing is even near but ONE of the need's words finds a kit on its own, it names those words and the kits each
finds — so a three-word need that missed does not read as "no kit does this" (fewerWords in --json):
$ osy kits find "waiver consent signature"
No kit says all of that together — but fewer words find these. Read their summaries before writing your own:
osy kits find waiver → Osysharp.QualificationsWhen no word finds anything either, it says so: write the subsystem as a general kit beside your app (below), proven by a test app of a different domain.
When no kit covers it — a local kit#
What an app needs that no kit covers — its own kind of record (a pass, a credential, an agreement), an external API (a
health service, a carrier, a bank) — is best built as a kit BESIDE the app rather than woven into it. osy kits new
makes one, in one of two shapes:
osy kits new Passes # → kits/passes/, a DATA kit
osy kits new GoogleHealth --http api.health.test # → kits/googlehealth/, an INTEGRATION kit for that host
osy kits new Passes --owner Acme # the owner, said outrightThe package is named <Owner>.<Name>. The owner is read from the repository's origin (github.com/acme/fitness →
Acme), so the name already points where the kit would be published; with no origin on github.com it is the app's
own name (app Climb → Climb.Passes), and --owner names another. Any owner is fine while the kit stays in the app:
osy publish is where the owner is fixed, and it asks for one if the kit is still named after its app
(package.osy).
A data kit (the default) is an entity, the rules over it, and a test app in which it runs alone:
| file | what it is |
|---|---|
package.osy | the manifest — no egress, no secrets; it builds on Osysharp.Identity for the person who holds a row |
model/<Name>.osy | a virtual policy Manages<Name> the app answers, an entity whose rules name only that policy and the row's owner, an [Event] and the function that raises it |
tests/app.osy · tests/model/people.osy | the test app — a [Principal], roles, and its answer to the kit's policy |
tests/<name>.test.osy | a [TestFixture] that seeds two people, and two tests that run as each of them — both pass |
An integration kit (--http <host>) wraps one HTTP service behind a seam:
| file | what it is |
|---|---|
package.osy | the manifest — egress "<host>", and how a secret reaches it, stubbed with comments |
model/<Name>.osy | the SEAM (I<Name>Source), a real client over HTTP, and the function the app calls |
tests/model/fake.osy | a FAKE at the seam, so the tests never call the real service |
tests/<name>.test.osy | one passing test, against the fake |
Both print what to run next as commands that work from the app's folder:
osy test kits/passes/tests # the kit's own tests, in its test app
osy kits check kits/passes # whether it composes: it reaches into no sibling kit, and a test shows every seamIt resolves by convention. A project's own kits/* — the nearest kits/ from the project up to its repository's
root — is used with no osy.workspace (the file still works, for other layouts). Resolution is by the NAME the kit's
package.osy declares, never by the folder. Then, in the app: use Climb.Passes@0; — or, for an integration kit,
use Acme.GoogleHealth@0 { egress "api.health.test"; }.
What an integration kit buys, all of it the kit model's: only the kit can reach the host; its credential is a field the
app fills from its own secrets; its tests run against the fake; and a second app can use it as it is. osy lint
suggests it (reliability-integration-not-in-a-kit, CONSIDER) when an app's own manifest declares egress to a
third-party host.
kit or kits?#
osy kit is the UI controls (osy kit Card); osy kits is the packages (osy kits Osysharp.Mail). Each
names the other when handed the other's argument, so osy kit Mail says to run osy kits Mail.
See also#
- Connecting a kit — each seam with the code that connects it — each seam of a kit, with the test app code that connects it
- Composing kits — what they give together, and what is left for you —
osy compose: what kits give together and what is left for you - Contracts between kits — an interface in a package, and its version — what a contract between kits is, and how its version is checked
- Interfaces — a contract several types can satisfy — interfaces, and default members
- The Mail kit — send a message, keep a record, pick a sender — a contract kit in full (
IMailSender) - Reading a capability's source —
osy source, a capability's source - Osysharp.Ui (the UI kit) —
osy kit, the UI controls