mandrel 2.6.0 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/README.md +104 -330
- package/.agents/agents/auditor.md +135 -0
- package/.agents/agents/plan-critic.md +80 -0
- package/.agents/audit-checklists/dependencies.md +7 -0
- package/.agents/audit-checklists/documentation.md +1 -0
- package/.agents/docs/SDLC.md +69 -162
- package/.agents/docs/configuration.md +133 -499
- package/.agents/docs/quality-gates.md +59 -180
- package/.agents/instructions.md +170 -295
- package/.agents/rules/changelog-style.md +8 -66
- package/.agents/rules/ci-remediation.md +65 -124
- package/.agents/rules/gherkin-standards.md +10 -31
- package/.agents/rules/git-conventions-reference.md +28 -61
- package/.agents/rules/git-conventions.md +1 -1
- package/.agents/rules/orchestration-error-handling.md +5 -15
- package/.agents/rules/security-baseline.md +7 -13
- package/.agents/rules/shell-conventions.md +4 -13
- package/.agents/rules/test-seams.md +2 -2
- package/.agents/rules/testing-standards.md +7 -17
- package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +7 -2
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +144 -12
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +11 -1
- package/.agents/scripts/lib/observability/runtime-friction.js +97 -36
- package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +29 -5
- package/.agents/skills/core/api-and-interface-design/SKILL.md +23 -297
- package/.agents/skills/core/api-and-interface-design/reference.md +76 -0
- package/.agents/skills/core/browser-testing-with-devtools/SKILL.md +20 -327
- package/.agents/skills/core/browser-testing-with-devtools/reference.md +74 -0
- package/.agents/skills/core/code-review-and-quality/reference.md +16 -398
- package/.agents/skills/core/debugging-and-error-recovery/reference.md +14 -281
- package/.agents/skills/core/documentation-and-adrs/reference.md +42 -338
- package/.agents/skills/core/git-workflow-and-versioning/SKILL.md +28 -326
- package/.agents/skills/core/idea-refinement/SKILL.md +67 -193
- package/.agents/skills/core/security-and-hardening/SKILL.md +15 -31
- package/.agents/skills/core/security-and-hardening/reference.md +15 -273
- package/.agents/skills/skills.index.json +5 -5
- package/.agents/skills/stack/qa/gherkin-authoring/SKILL.md +16 -222
- package/.agents/skills/stack/qa/gherkin-authoring/reference.md +157 -0
- package/.agents/skills/stack/qa/playwright/SKILL.md +0 -29
- package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +19 -23
- package/.agents/skills/stack/qa/qa-harness/SKILL.md +35 -53
- package/.agents/skills/stack/qa/vitest/SKILL.md +0 -29
- package/.agents/workflows/audit-accessibility.md +34 -108
- package/.agents/workflows/audit-architecture.md +35 -123
- package/.agents/workflows/audit-clean-code.md +19 -78
- package/.agents/workflows/audit-data-model.md +32 -100
- package/.agents/workflows/audit-dependencies.md +47 -111
- package/.agents/workflows/audit-devops.md +16 -83
- package/.agents/workflows/audit-documentation.md +46 -93
- package/.agents/workflows/audit-navigability.md +26 -80
- package/.agents/workflows/audit-performance.md +40 -106
- package/.agents/workflows/audit-privacy.md +17 -80
- package/.agents/workflows/audit-quality.md +35 -99
- package/.agents/workflows/audit-security.md +20 -78
- package/.agents/workflows/audit-seo.md +20 -98
- package/.agents/workflows/audit-sre.md +20 -88
- package/.agents/workflows/audit-to-stories.md +1 -8
- package/.agents/workflows/audit-ux-ui.md +17 -80
- package/.agents/workflows/deliver.md +54 -9
- package/.agents/workflows/git-cleanup.md +50 -275
- package/.agents/workflows/helpers/audit-lens-core.md +230 -0
- package/.agents/workflows/helpers/code-review.md +11 -23
- package/.agents/workflows/helpers/deliver-story-reference.md +114 -17
- package/.agents/workflows/helpers/deliver-story.md +26 -186
- package/.agents/workflows/helpers/qa-core.md +174 -0
- package/.agents/workflows/helpers/qa-run-scenario-reference.md +35 -0
- package/.agents/workflows/helpers/qa-run-scenario.md +11 -25
- package/.agents/workflows/helpers/worktree-lifecycle.md +6 -67
- package/.agents/workflows/mandrel-update.md +7 -13
- package/.agents/workflows/plan.md +17 -15
- package/.agents/workflows/qa-assist.md +140 -269
- package/.agents/workflows/qa-explore.md +125 -316
- package/.agents/workflows/qa-run.md +180 -380
- package/docs/CHANGELOG.md +18 -0
- package/package.json +1 -1
- package/.agents/workflows/helpers/audit-dual-path.md +0 -59
- package/.agents/workflows/helpers/audit-self-check.md +0 -70
- package/.agents/workflows/helpers/audit-severity-scale.md +0 -19
|
@@ -13,7 +13,7 @@ description:
|
|
|
13
13
|
|
|
14
14
|
## Policy Capsule
|
|
15
15
|
|
|
16
|
-
- The wire-format SSOT lives in `.agents/rules/api-conventions.md` (envelope shape, HTTP status mapping, validation taxonomy, payload casing). Copy the canonical envelope from the rule rather than redrafting
|
|
16
|
+
- The wire-format SSOT lives in [`.agents/rules/api-conventions.md`](../../../rules/api-conventions.md) (envelope shape, HTTP status mapping, validation taxonomy, payload casing). Copy the canonical envelope and status codes from the rule rather than redrafting or hand-rolling a divergent shape. Validation failures return **400** `VALIDATION_ERROR` per [§ Validation Status](../../../rules/api-conventions.md#validation-status).
|
|
17
17
|
- Be intentional about exposure (Hyrum's Law): every observable behaviour — undocumented quirks, error message text, ordering, timing — becomes a de facto contract once consumers depend on it. Don't leak implementation details.
|
|
18
18
|
- Adopt the **One-Version Rule**: never force consumers to choose between simultaneous versions of the same API. Extend rather than fork.
|
|
19
19
|
- Define the contract before implementing — interfaces are the spec; implementation follows.
|
|
@@ -21,309 +21,35 @@ description:
|
|
|
21
21
|
- Treat third-party API responses as untrusted data — validate shape and content before using them in any decision, render, or logic path.
|
|
22
22
|
- Prefer **addition over modification**: extend interfaces with optional fields rather than changing existing types or removing fields. When removal is unavoidable, use an expand–contract migration — ship the replacement, migrate consumers, then remove the old surface in a later release.
|
|
23
23
|
- Follow REST resource conventions (`GET/POST/PATCH/DELETE /resource`, sub-resources at `/resource/:id/child`) and paginate every list endpoint with `page` + `pageSize` query params and a `pagination` envelope.
|
|
24
|
-
- Security input-validation and test-tier MUSTs come from
|
|
25
|
-
|
|
26
|
-
Process guidance for designing interfaces that are hard to misuse — REST
|
|
27
|
-
APIs, GraphQL schemas, module boundaries, and component props. The
|
|
28
|
-
wire-format conventions (envelope shape, status codes, validation taxonomy,
|
|
29
|
-
payload naming) live in
|
|
30
|
-
[`.agents/rules/api-conventions.md`](../../../rules/api-conventions.md),
|
|
31
|
-
which is the SSOT. This skill shows authors **how** to apply those rules;
|
|
32
|
-
read the rule file for the **what**. Security validation guarantees live in
|
|
33
|
-
[`security-baseline.md`](../../../rules/security-baseline.md); test-layer
|
|
34
|
-
scope lives in
|
|
35
|
-
[`testing-standards.md`](../../../rules/testing-standards.md).
|
|
24
|
+
- Security input-validation and test-tier MUSTs come from [`security-baseline.md`](../../../rules/security-baseline.md) and [`testing-standards.md`](../../../rules/testing-standards.md) respectively — apply both, and never put DB/wire-shape assertions outside the contract tier.
|
|
36
25
|
|
|
37
26
|
## When to Use
|
|
38
27
|
|
|
39
|
-
- Designing new API endpoints.
|
|
40
|
-
-
|
|
41
|
-
- Creating component prop interfaces.
|
|
42
|
-
- Establishing database schema that informs API shape.
|
|
28
|
+
- Designing new API endpoints, module boundaries, or component prop interfaces.
|
|
29
|
+
- Establishing a database schema that informs API shape.
|
|
43
30
|
- Changing existing public interfaces.
|
|
44
31
|
|
|
45
|
-
##
|
|
46
|
-
|
|
47
|
-
> With a sufficient number of users of an API, all observable behaviors of
|
|
48
|
-
> your system will be depended on by somebody, regardless of what you promise
|
|
49
|
-
> in the contract.
|
|
50
|
-
|
|
51
|
-
Every observable behavior — undocumented quirks, error message text, timing,
|
|
52
|
-
ordering — becomes a de facto contract once users depend on it. Implications:
|
|
53
|
-
|
|
54
|
-
- **Be intentional about what you expose.** Every observable behavior is a
|
|
55
|
-
potential commitment.
|
|
56
|
-
- **Don't leak implementation details.** If users can observe it, they will
|
|
57
|
-
depend on it.
|
|
58
|
-
- **Plan for deprecation at design time.** Remove things users depend on via
|
|
59
|
-
expand–contract: add the replacement, migrate consumers behind a deprecation
|
|
60
|
-
window, then delete the old surface — never break a published contract in one
|
|
61
|
-
step.
|
|
62
|
-
- **Tests are not enough.** Even with perfect contract tests, "safe" changes
|
|
63
|
-
can break real users who depend on undocumented behavior.
|
|
64
|
-
|
|
65
|
-
## 2. The One-Version Rule
|
|
66
|
-
|
|
67
|
-
Avoid forcing consumers to choose between multiple versions of the same
|
|
68
|
-
dependency or API. Diamond dependency problems arise when different consumers
|
|
69
|
-
need different versions of the same thing. Design for a world where only one
|
|
70
|
-
version exists at a time — extend rather than fork.
|
|
71
|
-
|
|
72
|
-
## 3. Contract First
|
|
73
|
-
|
|
74
|
-
Define the interface before implementing it. The contract is the spec —
|
|
75
|
-
implementation follows.
|
|
76
|
-
|
|
77
|
-
```typescript
|
|
78
|
-
interface TaskAPI {
|
|
79
|
-
// Creates a task and returns the created task with server-generated fields
|
|
80
|
-
createTask(input: CreateTaskInput): Promise<Task>;
|
|
81
|
-
|
|
82
|
-
// Returns paginated tasks matching filters
|
|
83
|
-
listTasks(params: ListTasksParams): Promise<PaginatedResult<Task>>;
|
|
84
|
-
|
|
85
|
-
// Returns a single task or throws NotFoundError
|
|
86
|
-
getTask(id: string): Promise<Task>;
|
|
87
|
-
|
|
88
|
-
// Partial update — only provided fields change
|
|
89
|
-
updateTask(id: string, input: UpdateTaskInput): Promise<Task>;
|
|
90
|
-
|
|
91
|
-
// Idempotent delete — succeeds even if already deleted
|
|
92
|
-
deleteTask(id: string): Promise<void>;
|
|
93
|
-
}
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## 4. Wire Format — Defer to the Rule
|
|
97
|
-
|
|
98
|
-
The response envelope, HTTP status code mapping, validation-status taxonomy,
|
|
99
|
-
and payload-naming conventions are non-negotiable and live in the rule:
|
|
100
|
-
|
|
101
|
-
- Envelope shape (success flag, `error.code`, `error.message`,
|
|
102
|
-
`error.details`):
|
|
103
|
-
[`api-conventions.md` § Response Envelope](../../../rules/api-conventions.md#response-envelope).
|
|
104
|
-
- Status code table (200/201/400/401/403/404/409/500):
|
|
105
|
-
[`api-conventions.md` § HTTP Status Codes](../../../rules/api-conventions.md#http-status-codes).
|
|
106
|
-
- When to return 400 vs 401 vs 403 on validation failures:
|
|
107
|
-
[`api-conventions.md` § Validation Status](../../../rules/api-conventions.md#validation-status).
|
|
108
|
-
- camelCase / kebab-case / UPPER_SNAKE conventions:
|
|
109
|
-
[`api-conventions.md` § Payload Formatting](../../../rules/api-conventions.md#payload-formatting).
|
|
110
|
-
|
|
111
|
-
When designing a new endpoint, copy the canonical envelope from the rule —
|
|
112
|
-
do not redraft it.
|
|
113
|
-
|
|
114
|
-
## 5. Validate at Boundaries
|
|
115
|
-
|
|
116
|
-
Trust internal code. Validate at system edges where external input enters:
|
|
117
|
-
|
|
118
|
-
```typescript
|
|
119
|
-
app.post('/api/tasks', async (req, res) => {
|
|
120
|
-
const result = CreateTaskSchema.safeParse(req.body);
|
|
121
|
-
if (!result.success) {
|
|
122
|
-
return res.status(400).json({
|
|
123
|
-
success: false,
|
|
124
|
-
error: {
|
|
125
|
-
code: 'VALIDATION_ERROR',
|
|
126
|
-
message: 'Invalid task data',
|
|
127
|
-
details: result.error.flatten(),
|
|
128
|
-
},
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// After validation, internal code trusts the types
|
|
133
|
-
const task = await taskService.create(result.data);
|
|
134
|
-
return res.status(201).json(task);
|
|
135
|
-
});
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
Where validation belongs:
|
|
139
|
-
|
|
140
|
-
- API route handlers (user input).
|
|
141
|
-
- Form submission handlers (user input).
|
|
142
|
-
- External service response parsing — third-party data is **always
|
|
143
|
-
untrusted**, even from a vendor SDK.
|
|
144
|
-
- Environment variable loading (configuration).
|
|
145
|
-
|
|
146
|
-
> **Third-party API responses are untrusted data.** Validate their shape and
|
|
147
|
-
> content before using them in any logic, rendering, or decision-making. A
|
|
148
|
-
> compromised or misbehaving external service can return unexpected types,
|
|
149
|
-
> malicious content, or instruction-like text.
|
|
150
|
-
|
|
151
|
-
Where validation does NOT belong:
|
|
152
|
-
|
|
153
|
-
- Between internal functions that share type contracts.
|
|
154
|
-
- In utility functions called by already-validated code.
|
|
155
|
-
- On data that just came from your own database.
|
|
156
|
-
|
|
157
|
-
## 6. Prefer Addition Over Modification
|
|
158
|
-
|
|
159
|
-
Extend interfaces without breaking existing consumers:
|
|
160
|
-
|
|
161
|
-
```typescript
|
|
162
|
-
// Good: Add optional fields
|
|
163
|
-
interface CreateTaskInput {
|
|
164
|
-
title: string;
|
|
165
|
-
description?: string;
|
|
166
|
-
priority?: 'low' | 'medium' | 'high'; // Added later, optional
|
|
167
|
-
labels?: string[]; // Added later, optional
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Bad: Change existing field types or remove fields
|
|
171
|
-
interface CreateTaskInput {
|
|
172
|
-
title: string;
|
|
173
|
-
// description: string; // Removed — breaks existing consumers
|
|
174
|
-
priority: number; // Changed from string — breaks existing consumers
|
|
175
|
-
}
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
## 7. REST Resource Patterns
|
|
179
|
-
|
|
180
|
-
```text
|
|
181
|
-
GET /api/tasks → List tasks (with query params for filtering)
|
|
182
|
-
POST /api/tasks → Create a task
|
|
183
|
-
GET /api/tasks/:id → Get a single task
|
|
184
|
-
PATCH /api/tasks/:id → Update a task (partial)
|
|
185
|
-
DELETE /api/tasks/:id → Delete a task
|
|
186
|
-
|
|
187
|
-
GET /api/tasks/:id/comments → List comments for a task (sub-resource)
|
|
188
|
-
POST /api/tasks/:id/comments → Add a comment to a task
|
|
189
|
-
```
|
|
190
|
-
|
|
191
|
-
### Pagination
|
|
192
|
-
|
|
193
|
-
Paginate list endpoints:
|
|
194
|
-
|
|
195
|
-
```typescript
|
|
196
|
-
// Request
|
|
197
|
-
GET /api/tasks?page=1&pageSize=20&sortBy=createdAt&sortOrder=desc
|
|
198
|
-
|
|
199
|
-
// Response
|
|
200
|
-
{
|
|
201
|
-
"data": [...],
|
|
202
|
-
"pagination": {
|
|
203
|
-
"page": 1,
|
|
204
|
-
"pageSize": 20,
|
|
205
|
-
"totalItems": 142,
|
|
206
|
-
"totalPages": 8
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
### Filtering
|
|
212
|
-
|
|
213
|
-
Use query parameters for filters:
|
|
214
|
-
|
|
215
|
-
```text
|
|
216
|
-
GET /api/tasks?status=in_progress&assignee=user123&createdAfter=2025-01-01
|
|
217
|
-
```
|
|
218
|
-
|
|
219
|
-
### Partial Updates
|
|
220
|
-
|
|
221
|
-
`PATCH` accepts partial objects — only update what's provided:
|
|
222
|
-
|
|
223
|
-
```typescript
|
|
224
|
-
PATCH /api/tasks/123
|
|
225
|
-
{ "title": "Updated title" }
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
## 8. TypeScript Interface Patterns
|
|
229
|
-
|
|
230
|
-
### Discriminated Unions for Variants
|
|
231
|
-
|
|
232
|
-
```typescript
|
|
233
|
-
type TaskStatus =
|
|
234
|
-
| { type: 'pending' }
|
|
235
|
-
| { type: 'in_progress'; assignee: string; startedAt: Date }
|
|
236
|
-
| { type: 'completed'; completedAt: Date; completedBy: string }
|
|
237
|
-
| { type: 'cancelled'; reason: string; cancelledAt: Date };
|
|
238
|
-
|
|
239
|
-
function getStatusLabel(status: TaskStatus): string {
|
|
240
|
-
switch (status.type) {
|
|
241
|
-
case 'pending':
|
|
242
|
-
return 'Pending';
|
|
243
|
-
case 'in_progress':
|
|
244
|
-
return `In progress (${status.assignee})`;
|
|
245
|
-
case 'completed':
|
|
246
|
-
return `Done on ${status.completedAt}`;
|
|
247
|
-
case 'cancelled':
|
|
248
|
-
return `Cancelled: ${status.reason}`;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
### Input/Output Separation
|
|
254
|
-
|
|
255
|
-
```typescript
|
|
256
|
-
// Input: what the caller provides
|
|
257
|
-
interface CreateTaskInput {
|
|
258
|
-
title: string;
|
|
259
|
-
description?: string;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Output: what the system returns (includes server-generated fields)
|
|
263
|
-
interface Task {
|
|
264
|
-
id: string;
|
|
265
|
-
title: string;
|
|
266
|
-
description: string | null;
|
|
267
|
-
createdAt: Date;
|
|
268
|
-
updatedAt: Date;
|
|
269
|
-
createdBy: string;
|
|
270
|
-
}
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
### Branded Types for IDs
|
|
274
|
-
|
|
275
|
-
```typescript
|
|
276
|
-
type TaskId = string & { readonly __brand: 'TaskId' };
|
|
277
|
-
type UserId = string & { readonly __brand: 'UserId' };
|
|
278
|
-
|
|
279
|
-
// Prevents accidentally passing a UserId where a TaskId is expected
|
|
280
|
-
function getTask(id: TaskId): Promise<Task> { ... }
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
## 9. Common Rationalizations
|
|
284
|
-
|
|
285
|
-
| Rationalization | Reality |
|
|
286
|
-
| ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- |
|
|
287
|
-
| "We'll document the API later" | The types ARE the documentation. Define them first. |
|
|
288
|
-
| "We don't need pagination for now" | You will the moment someone has 100+ items. Add it from the start. |
|
|
289
|
-
| "PATCH is complicated, let's just use PUT" | PUT requires the full object every time. PATCH is what clients actually want. |
|
|
290
|
-
| "We'll version the API when we need to" | Breaking changes without versioning break consumers. Design for extension from the start. |
|
|
291
|
-
| "Nobody uses that undocumented behavior" | Hyrum's Law: if it's observable, somebody depends on it. Treat every public behavior as a commitment. |
|
|
292
|
-
| "We can just maintain two versions" | Multiple versions multiply maintenance cost and create diamond dependency problems. Prefer the One-Version Rule. |
|
|
293
|
-
| "Internal APIs don't need contracts" | Internal consumers are still consumers. Contracts prevent coupling and enable parallel work. |
|
|
294
|
-
|
|
295
|
-
## 10. Red Flags
|
|
296
|
-
|
|
297
|
-
- Endpoints that return different envelope shapes depending on conditions
|
|
298
|
-
(the rule fixes the shape — see § 4).
|
|
299
|
-
- Validation scattered throughout internal code instead of at boundaries.
|
|
300
|
-
- Breaking changes to existing fields (type changes, removals).
|
|
301
|
-
- List endpoints without pagination.
|
|
302
|
-
- Verbs in REST URLs (`/api/createTask`, `/api/getUsers`).
|
|
303
|
-
- Third-party API responses used without validation or sanitization.
|
|
32
|
+
## Wire format — defer to the rule
|
|
304
33
|
|
|
305
|
-
|
|
34
|
+
The response envelope, HTTP status-code mapping, validation-status taxonomy,
|
|
35
|
+
and payload-naming conventions are non-negotiable and live in the rule. When
|
|
36
|
+
designing a new endpoint, **copy the canonical envelope from the rule** — do
|
|
37
|
+
not redraft it, and do not invent a project-specific validation status:
|
|
306
38
|
|
|
307
|
-
|
|
39
|
+
- Envelope shape: [`api-conventions.md` § Response Envelope](../../../rules/api-conventions.md#response-envelope).
|
|
40
|
+
- Status table (200/201/400/401/403/404/409/500): [`§ HTTP Status Codes`](../../../rules/api-conventions.md#http-status-codes).
|
|
41
|
+
- Validation status (**400** `VALIDATION_ERROR`): [`§ Validation Status`](../../../rules/api-conventions.md#validation-status).
|
|
42
|
+
- Casing conventions: [`§ Payload Formatting`](../../../rules/api-conventions.md#payload-formatting).
|
|
308
43
|
|
|
309
|
-
-
|
|
310
|
-
- [ ] Error responses follow the envelope in
|
|
311
|
-
[`api-conventions.md` § Response Envelope](../../../rules/api-conventions.md#response-envelope).
|
|
312
|
-
- [ ] Status codes match
|
|
313
|
-
[`api-conventions.md` § HTTP Status Codes](../../../rules/api-conventions.md#http-status-codes).
|
|
314
|
-
- [ ] Validation runs at the API boundary and returns the canonical
|
|
315
|
-
`VALIDATION_ERROR` shape on failure.
|
|
316
|
-
- [ ] List endpoints support pagination.
|
|
317
|
-
- [ ] New fields are additive and optional (backward compatible).
|
|
318
|
-
- [ ] Naming follows
|
|
319
|
-
[`api-conventions.md` § Payload Formatting](../../../rules/api-conventions.md#payload-formatting).
|
|
320
|
-
- [ ] API documentation or types are committed alongside the implementation.
|
|
44
|
+
## Long-form reference — read on demand
|
|
321
45
|
|
|
322
|
-
|
|
46
|
+
The elaboration behind the capsule — Hyrum's-Law implications, the
|
|
47
|
+
contract-first stance, and where boundary validation does and does not belong,
|
|
48
|
+
plus the pre-PR authoring checklist — lives in the on-demand sibling
|
|
49
|
+
[`reference.md`](reference.md). Generic REST-catalog and TypeScript-idiom
|
|
50
|
+
snippets are intentionally omitted (frontier-known; the rule owns the wire
|
|
51
|
+
format). Open a section only when the task engages it.
|
|
323
52
|
|
|
324
|
-
-
|
|
325
|
-
|
|
326
|
-
-
|
|
327
|
-
[`security-baseline.md`](../../../rules/security-baseline.md).
|
|
328
|
-
- Test-layer scope:
|
|
329
|
-
[`testing-standards.md`](../../../rules/testing-standards.md).
|
|
53
|
+
- [Hyrum's Law and the One-Version Rule](reference.md#hyrums-law-and-the-one-version-rule)
|
|
54
|
+
- [Contract First and Boundary Validation](reference.md#contract-first-and-boundary-validation)
|
|
55
|
+
- [Authoring Checklist](reference.md#authoring-checklist)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# API & Interface Design — Reference (on-demand)
|
|
2
|
+
|
|
3
|
+
**Read this when** a task engages one of the sections below and the Policy
|
|
4
|
+
Capsule in [`SKILL.md`](SKILL.md) does not settle it on its own. The capsule
|
|
5
|
+
is the contract; this file is the reference material behind it. Generic
|
|
6
|
+
REST-verb tables and TypeScript pattern catalogs are deliberately not
|
|
7
|
+
reproduced here — they are frontier-known, and the wire format is owned by the
|
|
8
|
+
SSOT rule, [`api-conventions.md`](../../../rules/api-conventions.md).
|
|
9
|
+
|
|
10
|
+
## Hyrum's Law and the One-Version Rule
|
|
11
|
+
|
|
12
|
+
> With a sufficient number of users of an API, all observable behaviors of your
|
|
13
|
+
> system will be depended on by somebody, regardless of what you promise in the
|
|
14
|
+
> contract.
|
|
15
|
+
|
|
16
|
+
Every observable behavior — undocumented quirks, error message text, timing,
|
|
17
|
+
ordering — becomes a de facto contract once users depend on it. Practical
|
|
18
|
+
implications:
|
|
19
|
+
|
|
20
|
+
- **Be intentional about what you expose.** Every observable behavior is a
|
|
21
|
+
potential commitment; if users can observe it, they will depend on it.
|
|
22
|
+
- **Plan for deprecation at design time.** Remove things users depend on via
|
|
23
|
+
expand–contract: add the replacement, migrate consumers behind a deprecation
|
|
24
|
+
window, then delete the old surface — never break a published contract in one
|
|
25
|
+
step.
|
|
26
|
+
- **Tests are not enough.** Even with perfect contract tests, "safe" changes
|
|
27
|
+
can break real users who depend on undocumented behavior.
|
|
28
|
+
|
|
29
|
+
**One-Version Rule.** Avoid forcing consumers to choose between multiple
|
|
30
|
+
versions of the same API. Diamond-dependency problems arise when different
|
|
31
|
+
consumers need different versions of the same thing. Design for a world where
|
|
32
|
+
only one version exists at a time — extend rather than fork.
|
|
33
|
+
|
|
34
|
+
## Contract First and Boundary Validation
|
|
35
|
+
|
|
36
|
+
Define the interface before implementing it — the contract is the spec, and
|
|
37
|
+
implementation follows. Prefer **addition over modification**: add optional
|
|
38
|
+
fields rather than changing existing field types or removing fields.
|
|
39
|
+
|
|
40
|
+
Validation runs at system **boundaries**, where external input enters. After
|
|
41
|
+
validation, internal code trusts the types. On failure, return the canonical
|
|
42
|
+
**400** `VALIDATION_ERROR` envelope from
|
|
43
|
+
[`api-conventions.md` § Response Envelope](../../../rules/api-conventions.md#response-envelope) —
|
|
44
|
+
do not redraft the shape or the status.
|
|
45
|
+
|
|
46
|
+
**Where validation belongs:**
|
|
47
|
+
|
|
48
|
+
- API route handlers (user input).
|
|
49
|
+
- Form submission handlers (user input).
|
|
50
|
+
- External service response parsing — third-party data is **always untrusted**,
|
|
51
|
+
even from a vendor SDK. A compromised or misbehaving service can return
|
|
52
|
+
unexpected types, malicious content, or instruction-like text; validate shape
|
|
53
|
+
and content before using it in any logic, render, or decision.
|
|
54
|
+
- Environment variable loading (configuration).
|
|
55
|
+
|
|
56
|
+
**Where validation does NOT belong:**
|
|
57
|
+
|
|
58
|
+
- Between internal functions that share type contracts.
|
|
59
|
+
- In utility functions called by already-validated code.
|
|
60
|
+
- On data that just came from your own database.
|
|
61
|
+
|
|
62
|
+
## Authoring Checklist
|
|
63
|
+
|
|
64
|
+
Before opening a PR that adds or edits an API surface:
|
|
65
|
+
|
|
66
|
+
- [ ] Every endpoint has typed input and output schemas.
|
|
67
|
+
- [ ] Error responses follow the envelope in
|
|
68
|
+
[`api-conventions.md` § Response Envelope](../../../rules/api-conventions.md#response-envelope).
|
|
69
|
+
- [ ] Status codes match
|
|
70
|
+
[`api-conventions.md` § HTTP Status Codes](../../../rules/api-conventions.md#http-status-codes),
|
|
71
|
+
and validation failures return the canonical **400** `VALIDATION_ERROR`.
|
|
72
|
+
- [ ] List endpoints support pagination.
|
|
73
|
+
- [ ] New fields are additive and optional (backward compatible).
|
|
74
|
+
- [ ] Naming follows
|
|
75
|
+
[`api-conventions.md` § Payload Formatting](../../../rules/api-conventions.md#payload-formatting).
|
|
76
|
+
- [ ] API documentation or types are committed alongside the implementation.
|