git:20260809.786f3c8 to git:20261016.0b768f9

109 added, 220 removed. Audit A to A.

---
name: meta-planning-api-planning
- description: Backend specification planning frameworks. Use when a spec touches API endpoints, database schema, middleware, or auth. Covers endpoint contracts with request/response shapes, error catalogs, auth per endpoint, schema design with constraints and indexes, migration strategy, and middleware pipeline ordering.
+ description: Backend specification frameworks — endpoint contracts, error catalogs, auth per endpoint, schema with constraints and indexes, migration strategy, middleware order. Load when a spec adds or changes an API endpoint, a database table, or a request pipeline.
---
# API Planning Frameworks
- > **Quick Guide:** Specify every endpoint as a complete contract — method, path, auth requirement, request shape, success response, and an error catalog with a status per condition. Specify schema as exact columns with constraints, relationships, indexes, and a migration strategy. Order the middleware pipeline explicitly. Apply a framework only when the spec touches its artifact class — an endpoint-only change needs no schema section.
+ > **Quick Guide:** Specify each endpoint as a contract a consumer can code against without asking: method, path, auth, rate limit, request, success response, and a status for every failure. Specify each table as typed, constrained columns, indexes with the query each serves, and a migration strategy, and state the middleware order. Three facts change the answer more often than expected: a partial update is `PATCH`, not `PUT`; a unique index that includes a nullable `deletedAt` does not stop duplicate live rows in most SQL databases; and renaming a column without an outage takes three phases, not one migration.
- ---
+ **Detailed Resources:**
- <critical_requirements>
+ - [examples/core.md](examples/core.md) — the API Contract, Database Schema and Middleware Requirements section templates, and a worked specification built from them
- ## CRITICAL: Before Specifying Backend Contracts
+ ---
- > **All specifications must be grounded in the codebase's real routes, schemas, and middleware** — reference specific files with line numbers
+ ## Which path applies
- **(You MUST give every endpoint a complete contract: method, path, auth requirement, request shape, success response shape, and an error catalog)**
+ Ask each question on its own; a spec that adds an endpoint and a table carries both sections. A section with nothing to say is left out rather than filled.
- **(You MUST state the auth requirement per endpoint — which middleware, which permission — never "endpoints should be protected")**
+ - **It adds or changes an endpoint** — one API Contract block per endpoint (Patterns 1–3), from the [API Contract template](examples/core.md#api-contract-section).
+ - **It adds or changes a table, column or index** — one Database Schema block per table (Patterns 4–5), from the [Database Schema template](examples/core.md#database-schema-section).
+ - **It adds middleware or reorders a pipeline** — a Middleware Requirements section (Pattern 6), from the [Middleware Requirements template](examples/core.md#middleware-requirements-section).
+ - **None of the three** — nothing in this skill applies, and none of its sections belongs in the spec.
- **(You MUST specify schema as exact columns with types, constraints, relationships, indexes, and a migration strategy)**
+ ---
- **(You MUST catalog error responses per endpoint — a status code per condition with its response body shape)**
+ <critical_requirements>
- **(You MUST apply each framework only when the spec touches its artifact class — an unused section is omitted, never filled)**
+ ## Before writing a backend specification
- </critical_requirements>
+ **Read the closest existing route, schema file and middleware chain, and cite each by file and line range.** Their naming, pagination, response envelope and auth wiring are the vocabulary the spec reuses; a shape invented beside them diverges, and every consumer inherits the inconsistency.
- ---
+ **Give every endpoint its whole contract — method and path, auth, rate limit, request, success response and error catalog.** A part the spec leaves out is decided by the implementer, and decided again, differently, by each consumer.
- **Auto-detection:** API spec, endpoint design, REST contract, request response shape, database schema spec, migration plan, middleware ordering, auth requirements, error catalog
+ **Name the consumers of every contract the spec changes, and what breaks for each.** Frontends, other services and external clients code against these shapes and status codes, and a change that names none ships without knowing who it breaks.
- **When to use:**
+ **Write schema as typed columns with their constraints, indexes with the query each serves, and a migration strategy.** A nullable, unique or foreign-key decision the spec omits is made by whoever writes the migration, and reversing it later is a second migration against production data.
- - Specifying new or changed API endpoints (request/response contracts)
- - Specifying database tables, columns, relationships, or indexes
- - Specifying auth and permission requirements per endpoint
- - Specifying middleware pipelines and their ordering
- - Specifying error response catalogs
- - Planning migrations (reversibility, data migration, downtime)
+ </critical_requirements>
- **When NOT to use:**
+ ---
- - When implementing backend code (use the relevant API implementation skill)
- - For the frontend that consumes the API (use the web planning skill)
- - For model-calling capabilities behind an endpoint (use the ai planning skill)
- - For the planning PROCESS itself — research, scope fencing, success criteria — which the PM agent carries
+ **Auto-detection:** `<api_contract>`, `<database_schema>`, `<middleware_requirements>`, API spec, endpoint contract, request and response shape, error catalog, status code per condition, auth per endpoint, rate limit, schema spec, soft delete, unique index, migration strategy, backfill, expand and contract, middleware order
- **Key patterns covered:**
+ **Applies to:**
- - Endpoint contract completeness (method, path, auth, shapes, errors)
- - Auth specification per endpoint
- - Error response catalogs
- - Database schema design (columns, constraints, relationships, indexes)
- - Migration strategy
- - Middleware pipeline ordering
- - Consumer-contract awareness (who breaks on change)
+ - A new or changed endpoint's request, response, status codes, auth and rate limit
+ - Tables, columns, relationships and indexes, and the migration that delivers them
+ - The order of a request pipeline, and where validation and transactions sit in it
- **Detailed Resources:**
+ **Handled elsewhere:**
- - [examples/core.md](examples/core.md) - Per-artifact spec section templates and a worked example specification
+ - Implementation — handler bodies, query code and migration files follow from the spec, which states contracts rather than code
+ - The interface that consumes the API — its states, forms and client state are specified separately; this skill names it as a consumer and stops there
+ - Model calls behind an endpoint — prompts, model choice and evaluation are specified separately; this skill covers the endpoint that exposes them
+ - The specification around these sections — goal, scope fence, out-of-scope list and success criteria belong to the planning process that loads this skill
---
<philosophy>
- ## Philosophy
-
- **An API contract is a promise to consumers you cannot see.** Frontends, other services, and external clients all code against the shapes and status codes the spec defines. An ambiguous contract does not stay ambiguous — it gets resolved differently by the implementer and each consumer.
-
- **When specifying backend work:**
-
- - Read the closest existing route first; its naming, middleware chain, and response envelope are the vocabulary the spec must reuse
- - Name the downstream consumers of every contract, and what breaks for each if the shape changes
- - Specify the error catalog with the same care as the success path — consumers branch on status codes
- - Treat the schema as a contract too: a column without constraints is a decision deferred to whoever writes the migration
-
- **When NOT to specify:**
-
- - Don't add endpoints beyond the smallest set that achieves the goal
- - Don't design schema columns for data no requirement names
- - Don't invent new middleware when an existing chain covers the requirement
- - Don't specify implementation (handler bodies, ORM calls) — contracts and schemas, not code
+ An API contract is a promise to consumers you cannot see. An ambiguous one does not stay ambiguous: the implementer resolves it one way and each consumer another, and the mismatch surfaces later as a bug in someone else's code.
- **Core principles:**
+ So a backend spec is as much about what it leaves out:
- - **Auth is per endpoint**: "protected" is not a specification; the middleware and permission are
- - **Errors are a catalog**: every condition a consumer can hit has a status code and a body shape
- - **Schema constraints are requirements**: nullable, unique, and FK decisions belong in the spec
- - **Migrations are planned, not improvised**: reversibility, data migration, and downtime are stated up front
+ - **The smallest set of endpoints that meets the goal** — each extra one is surface to secure, test and keep compatible
+ - **No columns for data no requirement names** — a speculative column still needs a constraint decision and a migration
</philosophy>
---
<patterns>
- ## Core Patterns
+ ## Core patterns
- ### Pattern 1: Endpoint Contract Completeness
+ ### Pattern 1: Endpoint contract
- Every endpoint the spec introduces or changes carries all six parts.
+ Every endpoint the spec adds or changes carries all six parts.
```markdown
- ## Endpoint Contract
-
- For EACH endpoint:
-
- [ ] Method and exact path, with path parameters named (`GET /api/v1/users/:userId`)
- - [ ] Auth requirement: middleware name + permission/role, or explicitly public
+ - [ ] Auth: middleware and permission, or explicitly public (Pattern 2)
- [ ] Rate limit, or explicitly none
- - [ ] Request shape: every parameter with location (path/query/body), type, required flag, constraints
- - [ ] Success response: status code and exact body shape with field types
- - [ ] Error catalog: a row per condition (see Pattern 3)
+ - [ ] Request: each parameter's location (path/query/body), type, required flag, constraints
+ - [ ] Success response: status code and body shape with field types
+ - [ ] Error catalog: a row per condition (Pattern 3)
```
```
BAD: "Create an endpoint for user management"
- GOOD: "GET /api/v1/users — paginated list with cursor-based pagination following
- routes/jobs.ts:45-67. Response shape matches JobListResponse."
+ GOOD: "GET /api/v1/users — cursor pagination as in routes/jobs.ts:45-67, at most 50
+ per page. Response shape matches JobListResponse."
```
- **Why this matters:** each missing part becomes an invention. An invented pagination style or response envelope diverges from the codebase's own, and consumers inherit the inconsistency permanently.
+ A list endpoint's constraints include its maximum page size; without one, a client can ask for the whole table in a single request.
- ---
+ Where the codebase generates a machine-readable API description from its routes — an OpenAPI document, say — the changed endpoint appears in it, and the spec's success criteria include that the description still generates. Consumers and client generators read that document, not the spec.
- ### Pattern 2: Auth Per Endpoint
+ Full template: [examples/core.md](examples/core.md#api-contract-section)
- State the requirement per endpoint, naming real middleware.
+ ### Pattern 2: Auth per endpoint
+ Name the middleware and the permission for each endpoint. "Protected" leaves each route to decide for itself, and one ships public.
+
```
BAD: "Endpoints should be protected"
GOOD: "GET /api/v1/users requires authMiddleware. DELETE /api/v1/users/:id requires
authMiddleware + adminGuard. Public: POST /api/v1/auth/login."
```
- **Rules the spec must state:**
-
- - Which middleware, from which file, applied to which route group or individual route
- - The permission model: role, ownership (`ownerGuard` — user edits own resource only), or tenancy
- - Which fields are private (returned only to the owner or an admin) versus public
- - What an unauthorized versus a forbidden request returns — 401 and 403 are different promises
-
- ---
-
- ### Pattern 3: Error Response Catalog
+ - **Which middleware, from which file**, on which route group or route.
+ - **The permission model on every endpoint** — role, ownership (`ownerGuard`: the caller's own resource only) or tenancy: the object-level rule where the endpoint takes an id, and the row scope (whose rows the caller sees) where it returns a collection. Authentication alone lets any signed-in caller reach any object by changing the id, which is the first entry in OWASP's API Security Top 10 (2023), and a collection with no stated scope returns every caller's rows.
+ - **Which fields are private** to the owner or an admin. An endpoint whose fields differ by caller has two response shapes, and the spec gives both.
+ - **What 401 and 403 each mean.** 401 says the credentials are missing or invalid, so the client signs in again, and it carries a `WWW-Authenticate` header; 403 says the caller is known and refused, so signing in again changes nothing.
- Every endpoint's failure surface, as a table consumers can branch on.
+ ### Pattern 3: Error catalog
- | Status | Condition | Response Body |
- | ------ | ----------------------------------------- | ----------------------------------- |
- | 400 | Validation failure (schema parse error) | `{ error: string, details: [...] }` |
- | 401 | Missing or expired token | `{ error: string }` |
- | 403 | Authenticated but insufficient permission | `{ error: string }` |
- | 404 | Resource not found | `{ error: string }` |
- | 409 | Unique constraint violation | `{ error: string }` |
- | 422 | Business rule violation | `{ error: string }` |
+ Every condition a consumer can hit gets a status and a body, in a table the consumer branches on. Without one, every client wraps every call in a generic catch.
- **Rules the spec must state:**
+ | Status | Condition | Body |
+ | ------ | ------------------------------------- | --------------------------------------- |
+ | 400 | Request fails schema validation | `{ error: string, details: [...] }` |
+ | 401 | Missing or expired credentials | `{ error: string }` |
+ | 403 | Authenticated, but not permitted | `{ error: string }` |
+ | 404 | Resource not found | `{ error: string }` |
+ | 409 | Duplicates an existing resource | `{ error: string }` |
+ | 422 | Valid request a business rule refuses | `{ error: string }` |
+ | 429 | Rate limit exceeded | `{ error: string }`, with `Retry-After` |
- - Reuse the codebase's error envelope — one error shape per API, not per endpoint
- - One status per condition class a consumer handles differently; two conditions handled identically share a status
- - Validation failures name the offending fields in `details`, in the shape the existing error handler emits
- - Whether 404 is returned for a resource that exists but is not visible to the caller (existence leakage is a decision)
+ - **One envelope for the whole API**, reused from the codebase. Where there is none yet, RFC 9457 Problem Details (`application/problem+json`, with `type`, `title`, `status`, `detail` and `instance`) is the standard shape to adopt.
+ - **One status per condition the consumer handles differently.** Two conditions handled identically share a status.
+ - **Validation failures name the offending fields**, in the shape the existing error handler emits.
+ - **404 or 403 for an object that exists but is not the caller's.** 403 confirms it exists, and RFC 9110 lets a server answer 404 to hide it. Decide per resource.
+ - **Every row is a test case** — the spec's success criteria can name the request that produces each one.
- ---
+ Full template: [examples/core.md](examples/core.md#api-contract-section)
- ### Pattern 4: Database Schema Design
+ ### Pattern 4: Schema as columns
- Specify tables as exact columns, never as prose.
+ Specify each table as exact columns, never as prose.
```markdown
- ## Schema Review Checklist
-
- For EACH table the spec adds or changes:
-
- - [ ] Every column: name, type (with length/precision), constraints (NOT NULL, UNIQUE, FK, default)
+ - [ ] Each column: name, type with length or precision, NOT NULL / UNIQUE / FK / default
- [ ] Pattern source: the existing schema file whose conventions it follows
- - [ ] Audit columns per the codebase convention (createdAt, updatedAt)
- - [ ] Soft delete per the codebase convention (deletedAt), and the isNull check on every query
- - [ ] Relationships: cardinality and the FK or join table that carries each
- - [ ] Indexes: columns, type, and the query each index serves
+ - [ ] Audit and soft-delete columns per that convention, and the `deletedAt is null` filter every query applies — reads, updates and deletes alike
+ - [ ] Relationships: cardinality, and the FK or join table that carries each
+ - [ ] Indexes: columns, kind (ordinary, or the full-text / inverted kind a search or containment query needs), unique or not, which rows, and the query or rule each serves
```
```
BAD: "Add a users table"
- GOOD: "Add users table following db/schema/jobs.ts:12-45. Soft delete (deletedAt),
- audit columns, composite unique index on (email, deletedAt)."
+ GOOD: "Add users following db/schema/jobs.ts:12-45: audit columns, soft delete
+ (deletedAt), email unique among live rows — a unique index on email
+ restricted to rows where deletedAt is null."
```
- **Why this matters:** a column that arrives without constraints gets its NOT NULL, uniqueness, and FK decisions made by whoever types the migration — and changed later at the cost of a second migration against production data.
-
- ---
+ Full template: [examples/core.md](examples/core.md#database-schema-section)
- ### Pattern 5: Migration Strategy
+ ### Pattern 5: Migration strategy
- Every schema change states three things before implementation starts:
+ Every schema change states three things before implementation starts, so an irreversible step is a decision rather than something found during the deploy.
| Concern | State |
| -------------- | ---------------------------------------------------------------------- |
| Reversibility | Reversible (and how), or irreversible and why that is acceptable |
| Data migration | None, or describe: source of the backfilled values, and the batch plan |
| Downtime | None, or why it is required and the window |
- **Rules the spec must state:**
-
- - New NOT NULL columns on existing tables need a default or a backfill step — state which
- - Renames are two deploys (add + dual-write, then remove), or a breaking change named as such
- - Which environments the migration has been sized against, when tables are large
+ - **A new NOT NULL column on a populated table names its default or its backfill**, since the existing rows have no value to satisfy it.
+ - **A rename or type change states its expand, migrate and contract steps** — add the new column and write to both; backfill and move every read; drop the old column once no running version reads it.
+ - **Reversible means the data survives the round trip, and it is shown rather than asserted** — the spec's success criteria name a run of the migration down and up again. A down migration that drops a table or column also drops whatever was written to it after the up migration ran.
+ - **A migration that touches a large table states the environment it was sized against, and whether it runs online** — a backfill, a type change or other table rewrite, and an index or constraint alike. One timed against a small table can lock or rewrite the production table for far longer.
- ---
+ Full template: [examples/core.md](examples/core.md#database-schema-section)
- ### Pattern 6: Middleware Pipeline
+ ### Pattern 6: Middleware pipeline
- Order is behavior. State the pipeline explicitly per route group.
+ Order is behaviour, so the spec states the pipeline for each route group it touches.
```markdown
- ## Request Pipeline Order
-
- 1. Rate limiting — if applicable
- 2. Auth middleware — which one
- 3. Input validation — schema reference
- 4. Business logic handler
- 5. Response serialization
+ 1. Rate limit keyed by IP — if the group has one
+ 2. Auth — which middleware
+ 3. Rate limit keyed by user or API key — if the group has one
+ 4. Input validation — which schema
+ 5. Handler
+ 6. Response serialization
```
- **Rules the spec must state:**
+ - **Reuse before adding.** New middleware appears only where existing middleware cannot cover the requirement; the spec names what was checked, and the file each reused piece comes from.
+ - **Validation runs before the handler, against a named schema**, so no handler sees unvalidated input.
+ - **Multi-step operations name their transaction boundary** — which steps commit together, so a failure halfway leaves nothing half-written.
- - New middleware only when existing middleware cannot cover the requirement — name what was checked
- - Which existing middleware is reused, from which file
- - Where validation happens (before the handler, with which schema) so handlers never see unvalidated input
- - Transaction boundaries for multi-step operations — which steps commit together
+ Full template: [examples/core.md](examples/core.md#middleware-requirements-section)
</patterns>
---
- <decision_framework>
-
- ## Decision Framework
-
- ### Which Spec Sections Does This Feature Need?
-
- Apply a framework only when the spec touches its artifact class. The per-artifact section templates live in [examples/core.md](examples/core.md).
-
- ```
- Does the spec add or change an endpoint?
- ├─ YES → API Contract section (Patterns 1-3), one block per endpoint
- └─ Does it add or change tables, columns, or indexes?
- ├─ YES → Database Schema section (Patterns 4-5), one block per table
- └─ Does it add or reorder middleware?
- ├─ YES → Middleware Requirements section (Pattern 6)
- └─ NO → None of these frameworks applies; do not force one in
- ```
-
- ### Common Spec Failures
-
- | Failure | Consequence |
- | ---------------------------------------- | ------------------------------------------------------------------- |
- | "User data" instead of an exact shape | The implementer and each consumer resolve the ambiguity differently |
- | "Protected" instead of named middleware | Auth drifts per endpoint; a route ships public that should not be |
- | No error catalog | Consumers cannot branch; every client wraps calls in generic catch |
- | Schema as prose | Constraint decisions deferred to the migration author |
- | No migration strategy | Irreversible change discovered during deploy |
- | Endpoint set larger than the requirement | Unused surface to secure, test, and maintain |
- | No named consumers | A shape change ships without knowing who breaks |
-
- </decision_framework>
-
- ---
-
<red_flags>
- ## RED FLAGS
-
- **High Priority Issues (a spec with one of these is incomplete):**
-
- - An endpoint without a request shape, response shape, or error catalog
- - Auth stated as "protected" without naming middleware and permission
- - A schema change without column constraints or a migration strategy
- - A new NOT NULL column on an existing table with no default and no backfill plan
- - Validation placement unstated — handlers seeing unvalidated input
-
- **Medium Priority Issues:**
-
- - A response envelope that differs from the codebase's existing one
- - An index without the query it serves
- - Soft-delete tables without the isNull convention stated for queries
- - Multi-step operations without a transaction boundary decision
- - 401 vs 403 conflated
+ ## Red flags
- **Common Mistakes:**
+ **Breaks at runtime:**
- - Designing pagination differently from the sibling endpoints
- - Specifying a join table where the codebase uses an FK convention (or vice versa)
- - Leaving rate limits unstated on public endpoints
- - Forgetting the "not visible vs not found" existence-leakage decision
+ - A unique index on `(email, deletedAt)` meant to keep emails unique among live rows — most SQL databases treat NULLs as distinct in a unique index, so two live rows with the same email both insert. Specify the rule as "unique among rows where `deletedAt` is null" and how the database enforces it: a partial (filtered) unique index, where it has one.
+ - A `PUT` whose body fields are all optional — `PUT` replaces the resource with the body, so a client that omits a field expects it cleared while the server keeps it. Partial updates are `PATCH`.
+ - A column renamed in one migration — instances still running the previous release query a column that no longer exists, for as long as the deploy takes. Specify the expand, migrate and contract steps (Pattern 5).
+ - A `POST` that creates or charges with no retry rule — `POST` is not idempotent, so a client that times out and retries creates a second one. Specify how a retry is recognised, usually by a client-sent idempotency key.
+ - A public endpoint whose rate limit is "none" — nothing stops a client calling it in a loop, and sign-in and sign-up endpoints are where they do.
- **Gotchas & Edge Cases:**
+ **Surprising behaviour:**
- - A unique constraint on a soft-delete table usually needs the deletedAt column in the index
- - Renames are two deploys; a spec that renames in one is specifying a breaking change
- - An endpoint that returns different fields to owners and strangers is two response shapes — specify both
+ - A rate limit keyed by user but placed before auth keys by client IP instead, without an error, because there is no identity yet — everyone behind one address shares one allowance.
+ - Whether an index build blocks writes depends on the database and on how the index is built, so "Downtime: none" for a new index on a large table is a claim to check against the database the project runs.
</red_flags>
-
- ---
-
- <critical_reminders>
-
- ## CRITICAL REMINDERS
-
- > **All specifications must be grounded in the codebase's real routes, schemas, and middleware**
-
- **(You MUST give every endpoint a complete contract: method, path, auth requirement, request shape, success response shape, and an error catalog)**
-
- **(You MUST state the auth requirement per endpoint — which middleware, which permission)**
-
- **(You MUST specify schema as exact columns with types, constraints, relationships, indexes, and a migration strategy)**
-
- **(You MUST catalog error responses per endpoint — a status code per condition with its response body shape)**
-
- **(You MUST apply each framework only when the spec touches its artifact class — an unused section is omitted, never filled)**
-
- **Failure to specify these contracts produces APIs whose implementers invent shapes, whose consumers break on drift, whose auth gaps ship silently, and whose migrations cannot be rolled back.**
-
- </critical_reminders>