55 of 85 role skill bindings pointed at skills that were never authored,
so 10 of 11 team templates bound a smaller context bundle than their role
prompts assumed. Three roles bound nothing at all (gpu.bench_engineer,
threejs.shader_author, threejs.perf_engineer) while their prompts described
procedures they had no way to read.
The loader comment at team_template_loader.rs:167 already diagnosed this —
snake_case slugs in TOML against kebab-case skill files — and it was
half-fixed: the kebab names were corrected, the snake_case ones left.
It was invisible because both existing tests assert authored ⊆ referenced
(30/30, green) and the second explicitly declines to check the other
direction. So the failing half was the half nobody asserted.
Resolved every name by one of three explicit choices:
- 23 skills authored where the role genuinely needed the procedure
(gpu, threejs, research, analysis, frontend, mobile, backend, platform)
- renames onto authored skills where one existed in substance, including
the four-near-duplicate cases that collapse onto one real skill
- 22 aspirational references deleted — a binding an agent cannot read is
a promise, not a capability
Two tests now hold it. The unit test checks referenced ⊆ authored against
the files. The new integration test runs both loaders in boot order and
asserts the bindings survive the trip through the database, which is a
different question: resolution goes through skills_catalog rows, so a skill
file that exists but fails to ingest still leaves the role empty.
Negative controls: the unit test failed naming all 55; the integration test
fails naming the exact role when one name is reverted.
threejs.shader_author and .perf_engineer gained a second and third skill
after the collapse — pin_in_context pins idx < 2, so a role left with one
skill silently pins less than the policy intends.
Co-Authored-By: Claude Opus 5 <[email protected]>
51 lines
2.3 KiB
Markdown
51 lines
2.3 KiB
Markdown
---
|
|
name: openapi-contract-first
|
|
description: Designing an HTTP API as a written contract before the handler exists, and keeping the document true afterwards.
|
|
when_to_use: You are the API designer adding or changing an endpoint.
|
|
tags: [backend, api]
|
|
---
|
|
|
|
# The contract is the deliverable; the handler implements it
|
|
|
|
Writing the schema first surfaces the disagreements while they are still cheap —
|
|
what is optional, what the error shape is, what happens on conflict.
|
|
|
|
## Decide these five before writing code
|
|
|
|
1. **Resource and method.** `POST /missions` creates; `PATCH /missions/{id}`
|
|
partially updates. If you need a verb, the resource is probably wrong.
|
|
2. **The error shape, once, for the whole API.** One envelope everywhere. A
|
|
client that must handle three error shapes will handle one and log the others.
|
|
3. **Which fields are optional, and what absent means.** Absent, `null` and empty
|
|
are three different things and clients will discover the difference in
|
|
production.
|
|
4. **The status codes you actually use.** 201 with a Location for creates, 409
|
|
for conflicts, 422 for validation. Returning 200 with `{"error": ...}` makes
|
|
every client parse the body to learn what happened.
|
|
5. **Pagination, from the first endpoint.** Retrofitting it is a breaking
|
|
change — see `api-pagination-day-1`.
|
|
|
|
## The document must stay true
|
|
|
|
A stale spec is worse than none: clients trust it and are wrong. Generate it
|
|
from the types where the framework allows, and where it is hand-written, make a
|
|
test fail when handler and document disagree.
|
|
|
|
The failure mode to design against is a field added to the response and never
|
|
to the spec. That is invisible until an integrator asks why the documented shape
|
|
does not match, which is much later and much more expensive.
|
|
|
|
## Compatibility rules
|
|
|
|
Additive is safe: new optional request fields, new response fields. Everything
|
|
else is a breaking change, including narrowing an enum, making an optional field
|
|
required, and changing a field's type — even `int` to `string` for an id.
|
|
|
|
If a break is genuinely needed, version the path. Silently changing a shape and
|
|
telling clients in a changelog is how integrations fail on a Friday.
|
|
|
|
## Examples are part of the contract
|
|
|
|
One realistic request and response per endpoint answers more questions than the
|
|
schema does, and a wrong example is caught immediately by anyone who tries it.
|