Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions .claude/skills/add-a-domain/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: add-a-domain
description: Add or extend an osapi API domain — a provider plus every layer it must appear in. Covers the provider implementation and its platform variants, agent processor and registry wiring, the OpenAPI spec and validation tags, the Echo handler with broadcast targeting, handler registration and startup wiring, the SDK service, CLI commands, and the docs and permission tables a new domain must be added to. Use when asked to add a domain, add a provider, add an operation or endpoint to an existing domain, wire a provider into the agent, add an SDK service, add CLI commands for a domain, or when asked what a new domain has to touch, why a domain feels half-finished, or which layer is missing. Also use when reviewing a domain for cross-layer consistency against an existing one.
description: Add or extend an osapi API domain, a provider plus every layer it must appear in. Covers the provider implementation and its platform variants, agent processor and registry wiring, the OpenAPI spec and validation tags, the Echo handler with broadcast targeting, handler registration and startup wiring, the SDK service, CLI commands, and the docs and permission tables a new domain must be added to. Use when asked to add a domain, add a provider, add an operation or endpoint to an existing domain, wire a provider into the agent, add an SDK service, add CLI commands for a domain, or when asked what a new domain has to touch, why a domain feels half-finished, or which layer is missing. Also use when reviewing a domain for cross-layer consistency against an existing one.
compatibility: Requires an osapi checkout with mise and just available. Commands run through `mise exec -- just`.
license: MIT
metadata:
Expand Down Expand Up @@ -46,14 +46,14 @@ Never copy a domain's files wholesale. Read one, then write the new one.
The layers depend on each other in one direction. Going out of order means
regenerating or rewriting.

1. **Provider** — the operations, with its own tests passing.
2. **Agent** — processor and registry, so a job reaches the provider.
3. **OpenAPI spec** — then `just generate`, which produces the server, the
1. **Provider**, the operations, with its own tests passing.
2. **Agent**, processor and registry, so a job reaches the provider.
3. **OpenAPI spec**, then `just generate`, which produces the server, the
combined spec, and the SDK's generated client together.
4. **Handler** — with validation and broadcast, then registration and startup.
5. **SDK service** — wrapping the generated client.
6. **CLI** — wrapping the SDK.
7. **Docs and tables** — feature page, CLI pages, permissions, navbars.
4. **Handler**, with validation and broadcast, then registration and startup.
5. **SDK service**, wrapping the generated client.
6. **CLI**, wrapping the SDK.
7. **Docs and tables**, feature page, CLI pages, permissions, navbars.

A new permission is decided at step 3 and lands in step 7. Write it down when
you choose it; it is the thing most often missed.
Expand Down
8 changes: 4 additions & 4 deletions .claude/skills/add-a-domain/references/agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ dispatch and facts wiring.

| What you need to know | Where |
| --- | --- |
| Two files connect a provider, and what does **not** change — `agent/types.go`, `agent/agent.go`, the `JobClient` interface | [FR-009](../../../../components/osapi/domains.md) |
| Two files connect a provider, and what does **not** change: `agent/types.go`, `agent/agent.go`, the `JobClient` interface | [FR-009](../../../../components/osapi/domains.md) |
| The `FactsAware` obligation: embed it, add the compile-time `FactsSetter` check | [FR-010](../../../../components/osapi/domains.md) |

This file holds the shapes and the file names. The rules above are stated once,
Expand Down Expand Up @@ -104,7 +104,7 @@ in full. What matters when adding an operation, and where each rule is stated:
| --- | --- |
| Delivery is at-least-once, and the agent checks for a recorded response before executing | [FR-009](../../../../components/osapi/job-system.md) |
| Which operations make that check load-bearing rather than theoretical | [FR-010](../../../../components/osapi/job-system.md) |
| A job that has run is terminal — a failure is reported, not retried by redelivery | [FR-011](../../../../components/osapi/job-system.md) |
| A job that has run is terminal, a failure is reported, not retried by redelivery | [FR-011](../../../../components/osapi/job-system.md) |
| What happens when the response cannot be written after the work ran | [FR-012](../../../../components/osapi/job-system.md) |
| Which failures terminate a message instead of redelivering it | [FR-013](../../../../components/osapi/job-system.md) |
| The consumer's delivery settings, as defaults a deployment may override | [FR-014](../../../../components/osapi/job-system.md) |
Expand All @@ -113,14 +113,14 @@ in full. What matters when adding an operation, and where each rule is stated:
Two consequences for a new operation, which are yours rather than the system's:

- **Redelivery is not your safety net.** The provider's idempotency is what makes a
repeat safe — the contract's own requirement, not this one.
repeat safe, the contract's own requirement, not this one.
- **`job retry` creates a new job** rather than replaying the old message, so
nothing per-domain handles it.

## Facts

`provider.WireProviderFacts(a.GetFacts, registry.AllProviders()...)` injects
facts into every registered provider — one call, in `internal/agent/agent.go`. A
facts into every registered provider, one call, in `internal/agent/agent.go`. A
provider registered through the registry is covered; one constructed and passed
somewhere else is not. The obligation on the provider struct itself is
[FR-010](../../../../components/osapi/domains.md), and what a provider does with facts is
Expand Down
12 changes: 6 additions & 6 deletions .claude/skills/add-a-domain/references/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ does not: where the files go, what they are called, and the scaffolding to start
from.

The split is deliberate. A rule restated here would drift from the one in the
corpus, and the copy an agent happened to load would win — which is the failure
corpus, and the copy an agent happened to load would win, which is the failure
[003-corpus-backfill](../../../../CONSTITUTION.md)
exists to end.

Expand All @@ -25,7 +25,7 @@ exists to end.
| Path parameters are the trap, and what actually validates one | FR-012 |
| Separate verbs for create and update, and why a combined upsert is forbidden | FR-013 |
| The six API design guidelines, including path versus query parameters | FR-014 |
| What `{hostname}` accepts — a literal, `_any`, `_all`, a label selector | FR-015 |
| What `{hostname}` accepts, a literal, `_any`, `_all`, a label selector | FR-015 |
| Broadcast is mandatory, and both paths return the same collection shape | FR-016 |
| The job client has four methods, and an operation adds none | FR-017 |
| `Handler()` returns route closures, and the `Server` struct does not change | FR-018 |
Expand Down Expand Up @@ -90,7 +90,7 @@ handlers = append(handlers,

Stated here because they are real and nothing else states them. Both belong in
the corpus and neither is there, which is recorded rather than left to be
discovered — the same treatment FR-019 gives the absent `sdk-standards`
discovered, the same treatment FR-019 gives the absent `sdk-standards`
capability.

**A custom validation rule is a registered validator.** It belongs in
Expand All @@ -106,7 +106,7 @@ corpus does not say is that a domain must not write its own target parser.
that differ in how much damage they can do want two permissions however similar
their shape. A new one must be added to the built-in role expansion, the
permission constants, the SDK, and the roles tables in
`features/authentication.md` and `usage/configuration.md` — see
`features/authentication.md` and `usage/configuration.md`, see
[docs.md](docs.md). A permission that exists in the spec but in no role reaches
nobody.

Expand All @@ -115,9 +115,9 @@ nobody.
Testing conventions are osapi's `CONTRIBUTING.md`, under "Testing". What this
layer adds to a public suite:

- `TestXxxHTTP` — raw HTTP through the full Echo middleware stack: valid input
- `TestXxxHTTP`, raw HTTP through the full Echo middleware stack: valid input
succeeds, invalid input returns 400 with the message.
- `TestXxxRBACHTTP` — no token is 401, a token without the permission is 403, a
- `TestXxxRBACHTTP`, no token is 401, a token without the permission is 403, a
token with it succeeds.

Cover validation failure, success, a provider error surfaced from the job, and
Expand Down
4 changes: 2 additions & 2 deletions .claude/skills/add-a-domain/references/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ The obligations this layer carries are stated in
| `--json` on every command, and flags rather than positional arguments for IDs | FR-021 |
| `cli.PrintKV` for a single resource, `cli.PrintCompactTable` for rows | FR-021 |
| Every response code the spec declares handled in the status switch | FR-021 |
| What `--target` accepts — a literal, `_any`, `_all`, a label selector | FR-015 |
| What `--target` accepts, a literal, `_any`, `_all`, a label selector | FR-015 |
| What verifies a finished domain, and what Step 8 alone misses | FR-024 |

## Files
Expand Down Expand Up @@ -49,7 +49,7 @@ operators read these side by side.

## Three rules the corpus does not yet hold

Stated here because they are real and nothing else states them — not the corpus,
Stated here because they are real and nothing else states them, not the corpus,
not osapi's `CONTRIBUTING.md`. Recorded as unstated rather than left to be
discovered, the same treatment FR-019 gives the absent `sdk-standards`
capability.
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/add-a-domain/references/docs.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Docs and tables

The Docusaurus site is user-facing: what a feature does, how to call it, what the
SDK exposes. Development guidance is not there — it is in the corpus, and this
SDK exposes. Development guidance is not there, it is in the corpus, and this
skill cites it.

A domain that works but appears in none of these is invisible to everyone who did
Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/add-a-domain/references/sdk.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ binding rules were "the `sdk-standards` capability in this repository", that the
bound `osapi-orchestrator` too, and that the capability won any disagreement.
Nothing of the sort has been written. The claim was also on osapi's
`adding-an-api-domain.md`, so two documents deferred to a specification that reads
as settled and does not exist — recorded as
as settled and does not exist, recorded as
[FR-019](../../../../components/osapi/domains.md)'s gap rather than repeated here.

What that means in practice: the conventions below and in FR-019 and FR-020 are
Expand Down
106 changes: 53 additions & 53 deletions .claude/skills/document/references/voice.md
Original file line number Diff line number Diff line change
@@ -1,51 +1,54 @@
# The voice

An engineer explaining a system to another engineer who has to work on it.
How every document in this repository is written, and every skill that writes
one. Cited rather than restated: if you are about to paraphrase this into a
skill or a page, link here instead.

The test: **would a senior engineer write this in an internal design doc?** If it
reads like a paper, rewrite it. If it reads like documentation written for
beginners, rewrite it. If it reads like something trying to sound technical,
rewrite it.
A plain engineering voice. Something engineers would want to read and maintain,
not an academic paper, not marketing, not AI prose.

**The engineering is not simplified. The language is.**

The guidelines, short form:
## Writing style

- Simple direct language. Normal engineering terms, not fancier synonyms.
- Do not oversimplify. The reader should still get the real tradeoffs,
constraints and architecture.
- Simple, direct language over jargon. Use the normal engineering term.
- Do **not** oversimplify the technical detail. Keep the architecture,
constraints, tradeoffs and reasoning intact.
- Concrete: what it does, why it exists, how it works, what the tradeoffs are.
- The reader is an experienced engineer who does not know this system.
- No buzzwords unless they earn their place.
- No padding with obvious statements or generic best practice.
- Not every idea as a numbered list. Prose, diagrams, tables, short lists, where
each is clearest.
- Precise without being formal for its own sake.
- State opinions and decisions. Name the tradeoff and the side you took.
- Concrete examples over explanations of terminology.
- No buzzwords: leverage, robust, seamless, scalable, paradigm, orchestration as
a synonym for running things, surface as a synonym for API.
- No padding with generic statements or obvious best practice.
- Not everything as a numbered list. Prose, diagrams, tables and short lists,
whichever makes it clearer.
- Technically precise without being formal for its own sake.
- State the decision. Where there is a tradeoff, say what it is and which side
you took.
- Concrete examples over explaining terminology.
- Tight. Every paragraph explains the system, justifies a decision, or clarifies
a tradeoff.

## What to do
The test:

**Plain words.** Use the normal engineering term. "Use" not "leverage", "help"
not "facilitate", "is" not "serves as". If a fancier synonym is clearer, use it;
it rarely is.
> Would a senior engineer actually write this in an internal design doc?

If it reads like an academic paper, rewrite it. If it reads like documentation
for beginners, rewrite it. If it reads like something trying to sound technical,
rewrite it.

**Concrete over abstract.** Name the file, the function, the number. "The
controller waits 30 seconds, set by `controller.api.job_timeout`" beats "the
controller has a configurable timeout".
## The unslop pass is required

**Say the tradeoff.** Where there was a real choice, say what it cost. "Two
buckets rather than one means a reader of a result does not walk the status
history; it also means the result's TTL is a separate setting nobody remembers to
set." A decision with no cost stated reads as if there was nothing to decide.
After writing, run the whole document through `unslop`. It is an editing pass,
not a suggestion.

**Keep the real detail.** Simplifying until the tradeoffs disappear is worse than
being dense. The reader is experienced; they are not familiar with this system.
It removes AI phrasing, verbosity, corporate and academic language, repeated
explanations, fake transitions, filler, excessive headings and bullets, jargon,
and prose that is too polished to be natural. It keeps the technical meaning.

**Lead with what will bite them.** A rule with a silent failure mode is worth
more than three paragraphs about structure.
Then read the result again and fix anything it bent out of shape. **Do not let a
shorter document lose technical information.**

## What to avoid
## The specific tells

**Significance instead of substance.** Do not write "that indirection is the
whole reason the API cannot do the work, and everything below follows from it".
Expand All @@ -56,40 +59,37 @@ consequences is useful; asserting that there are consequences is not.
**Commentary about the document.** "This section is short because the subsystem
is thin" tells the reader nothing about the system. Cut it.

**Buzzwords.** leverage, robust, seamless, scalable, paradigm, holistic,
orchestration as a synonym for "running things", surface as a synonym for "API".

**Em dashes.** Use a comma or end the sentence. `just check-docs` fails on them.

**Bold labels that restate the line after them.** "**Performance:** performance
improved by..." A bold lead-in that names a thing and is followed by new detail
is fine.

**Everything as a numbered list.** Use prose where the ideas connect, a table
where the data is tabular, a list where the items are genuinely parallel. Three
nested lists in a row means the structure is doing the thinking.

**Padding.** Every paragraph explains the system, justifies a decision, or
clarifies a tradeoff. If it does none of those, delete it.

**Hedged numbers.** "roughly 108 fields" invites nobody to check it, and a count
in this repository stayed wrong for weeks behind a tilde. State the number and
the command.
here stayed wrong for weeks behind a tilde. State the number and the command.

**Aphorisms.** "Prose is a lead; the code is the source" sounds like wisdom and
tells you nothing to do. Write the instruction: read the code, then run
something that would fail if you were wrong.

## Worked example

Weak:

> The job system leverages a robust queuing paradigm to facilitate scalable
> execution across the fleet. This architectural decision underscores the
> system's commitment to reliability.
```
The job system leverages a robust queuing paradigm to facilitate scalable
execution across the fleet. This architectural decision underscores the system's
commitment to reliability.
```

Better:

> Work reaches a host by being queued, not by being called. The controller writes
> a job, announces it, and waits; an agent picks it up and writes a response
> back. Everything awkward about the system comes out of that split:
> at-least-once delivery, the idempotency providers owe, two independent
> timeouts, and a per-host result instead of one answer.
```
Work reaches a host by being queued, not by being called. The controller writes a
job, announces it, and waits; an agent picks it up and writes a response back.
Everything awkward about the system comes out of that split: at-least-once
delivery, the idempotency providers owe, two independent timeouts, and a per-host
result instead of one answer.
```

The second one is shorter, names the mechanism, and tells you what to expect.
Shorter, names the mechanism, and tells you what to expect.
2 changes: 1 addition & 1 deletion .claude/skills/org-status/references/issues.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ gh pr list --repo "osapi-io/$r" --state open \

An issue named there is in flight, and belongs in the pull request block rather
than in a block of its own. An issue whose fix has already merged is still open
only because nobody closed it — say so, because it reads as outstanding work.
only because nobody closed it, say so, because it reads as outstanding work.

## Trackers

Expand Down
6 changes: 3 additions & 3 deletions .claude/skills/org-status/references/merging.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ repository often are not.

Two bumps that touch the same file cannot both be merged from the state they
were built in. The first merge moves the default branch and the second is now
based on something that no longer exists, so it conflicts — or worse, merges
based on something that no longer exists, so it conflicts, or worse, merges
cleanly and drops the first one's edit. `mergeable` says `CLEAN` for both right
up until the first one lands, which is what makes this easy to get wrong.

Expand All @@ -56,9 +56,9 @@ So decide by the files, not by the colour of the tick:
gh pr view <number> --repo "osapi-io/$r" --json files -q '.files[].path'
```

- **No overlap** — merge them together. Four bumps each touching a different
- **No overlap**, merge them together. Four bumps each touching a different
`examples/<name>/go.mod` do not interact.
- **Overlap** — serialize, one merge at a time:
- **Overlap**, serialize, one merge at a time:
1. merge the first
2. `@dependabot rebase` the next, and wait for its checks
3. merge it, and repeat
Expand Down
8 changes: 4 additions & 4 deletions .claude/skills/org-status/references/pull-requests.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ gh pr list --repo "osapi-io/$r" --state open \

## Fields worth reporting

- `isDraft` — a draft is not waiting on review. Say so rather than counting it
- `isDraft`, a draft is not waiting on review. Say so rather than counting it
as pending.
- `mergeable` — `CONFLICTING` means it needs a rebase before anything else.
- `mergeable`: `CONFLICTING` means it needs a rebase before anything else.
`UNKNOWN` means GitHub is still computing it, so re-query rather than
reporting it as a problem.
- `reviewDecision` — empty string means no review has been requested or given.
- `reviewDecision`, empty string means no review has been requested or given.
`APPROVED` means it is ready to merge.
- `createdAt` — sort oldest first. Age is the signal.
- `createdAt`, sort oldest first. Age is the signal.

## Checks on a PR

Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/org-status/references/quality.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ go directive: the fix is the same command in each, and a new linter release puts
all of them behind at once.

A stale pin is not cosmetic. A linter predating the toolchain crashes rather
than reporting findings — v2.12.2 died inside `buildir` under Go 1.27 — and the
than reporting findings, v2.12.2 died inside `buildir` under Go 1.27, and the
crash arrives as a red build on an unrelated pull request.

This drift was invisible for as long as it existed, because the shared `deps`
Expand Down
Loading
Loading