@softspark/ai-toolkit 4.32.2 → 4.32.4
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/CHANGELOG.md +56 -0
- package/README.md +19 -20
- package/app/.claude-plugin/plugin.json +1 -1
- package/app/ARCHITECTURE.md +1 -1
- package/app/claude-app/skills/ai-toolkit-rules/SKILL.md +8 -1
- package/app/hooks/user-prompt-submit.sh +21 -0
- package/app/rules/common/security.md +2 -1
- package/app/rules/common/testing.md +6 -0
- package/app/skills/api-patterns/SKILL.md +37 -11
- package/app/skills/api-patterns/reference/error-contracts.md +88 -0
- package/app/skills/review/SKILL.md +5 -0
- package/app/skills/security-patterns/SKILL.md +2 -2
- package/app/skills/security-patterns/reference/input-validation.md +69 -1
- package/app/skills/testing-patterns/SKILL.md +19 -0
- package/app/surface.json +5 -0
- package/benchmarks/ecosystem-doctor-snapshot.json +13 -13
- package/kb/procedures/sop-post-release-testing.md +159 -58
- package/kb/procedures/sop-release-verification.md +38 -13
- package/kb/procedures/sop-release.md +9 -2
- package/kb/reference/architecture-overview.md +3 -3
- package/kb/reference/language-rules.md +7 -1
- package/kb/reference/skills-catalog.md +2 -2
- package/llms-full.txt +219 -80
- package/manifest.json +3 -3
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,62 @@ Versioning follows [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## v4.32.4 - Validation parity and API error contracts (2026-09-06)
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **Validation contracts:** Add practical guidance to `/api-patterns`,
|
|
15
|
+
`/security-patterns`, `/review` and `/testing-patterns` for a shared backend
|
|
16
|
+
and frontend rule source, operation-specific DTO groups/defaults, finite
|
|
17
|
+
collection bounds, Unicode units, explicit server-only checks and parity
|
|
18
|
+
fixtures. Local validation failures remain distinct from HTTP responses.
|
|
19
|
+
Cross-skill references resolve through the installed catalog so namespaced
|
|
20
|
+
adapters, including GitHub Copilot, can find the same guidance.
|
|
21
|
+
- **API error contracts:** Preserve the host's error representation and
|
|
22
|
+
distinguish domain refusals from technical failures. Cover actionable
|
|
23
|
+
messages, JSON types, background-job failures, unknown outcomes and safe
|
|
24
|
+
retries. Common security/testing rules retain shared-database test isolation.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- **Schema guidance:** Correct JSON Schema conditional requirements and Ajv
|
|
29
|
+
strict-mode semantics, retain domain checks alongside boundary validation,
|
|
30
|
+
and import `Field` in the Pydantic example. The FastAPI example catches a
|
|
31
|
+
specific request refusal while preserving its exception cause.
|
|
32
|
+
- **Release verification:** Compare inventories with the current artifact,
|
|
33
|
+
run installation smoke in a disposable environment, and fail when an
|
|
34
|
+
expected generated JSON file is missing. Preserve the operator's install
|
|
35
|
+
and authentication state during candidate and published-package checks.
|
|
36
|
+
- **Package metadata:** Refresh skill and hook inventory descriptions.
|
|
37
|
+
|
|
38
|
+
### Release checks
|
|
39
|
+
|
|
40
|
+
- **Ecosystem:** Refresh documentation hashes and local CLI version probes;
|
|
41
|
+
the review found no heading or capability-marker changes and changes no
|
|
42
|
+
generators, runtime integration paths or permission declarations.
|
|
43
|
+
- **Skill body budget:** Keep the existing 18,000-byte warning threshold;
|
|
44
|
+
the largest body remains 17,197 bytes, so the ratchet cannot tighten yet.
|
|
45
|
+
|
|
46
|
+
## v4.32.3 — Search-first stops firing on things nobody asked (2026-09-04)
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- **A background task finishing no longer demands a KB search.**
|
|
51
|
+
`user-prompt-submit.sh` set the search-required flag on any stdin longer
|
|
52
|
+
than 30 characters. The harness delivers background-task notifications, CI
|
|
53
|
+
events and replayed slash-command output on the same channel as a prompt, so
|
|
54
|
+
a task completing blocked `Stop` and asked for a search over text nobody
|
|
55
|
+
wrote. Harness event envelopes no longer set the flag.
|
|
56
|
+
- **A pasted credential no longer becomes a search query.** A 43-character API
|
|
57
|
+
key on its own line cleared the length gate, and the block message names the
|
|
58
|
+
prompt as the thing to search for — so the enforcement path asked for a live
|
|
59
|
+
secret to be sent to a retrieval service. Single-token prompts no longer set
|
|
60
|
+
the flag; a genuine one-word prompt is under the length gate anyway, so
|
|
61
|
+
nothing that was enforced before stops being enforced. Prose that merely
|
|
62
|
+
mentions a task notification still sets it, and that case is now a test.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
10
66
|
## v4.32.2 - Plugin exports land where you run the command (2026-09-04)
|
|
11
67
|
|
|
12
68
|
### Fixed
|
package/README.md
CHANGED
|
@@ -6,25 +6,24 @@
|
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](app/skills/)
|
|
8
8
|
[](app/agents/)
|
|
9
|
-
[](tests/)
|
|
10
|
+
|
|
11
|
+
## What's New in v4.32.4
|
|
12
|
+
|
|
13
|
+
**v4.32.4** documents validation parity and reliable API error handling:
|
|
14
|
+
|
|
15
|
+
- **One validation contract for frontend and backend.** Existing skills cover
|
|
16
|
+
DTO groups/defaults, nested collections, Unicode, explicit server-only checks
|
|
17
|
+
and shared regression fixtures, with backend enforcement kept authoritative.
|
|
18
|
+
- **Errors preserve the API contract.** Guidance separates local refusals,
|
|
19
|
+
domain conflicts and technical failures, retaining field paths, machine
|
|
20
|
+
codes and safe recovery when a write outcome is uncertain.
|
|
21
|
+
- **Schema examples match validator behavior.** Correct JSON Schema
|
|
22
|
+
conditional requirements and Ajv strict-mode guidance; keep state-dependent
|
|
23
|
+
domain checks alongside validation at the request boundary.
|
|
24
|
+
- **Release smoke tests verify the installed artifact.** Procedures use
|
|
25
|
+
disposable environments, current inventories and assertions for missing
|
|
26
|
+
generated files, without modifying the operator's live installation.
|
|
28
27
|
|
|
29
28
|
See [CHANGELOG.md](CHANGELOG.md) for full history.
|
|
30
29
|
|
|
@@ -206,7 +205,7 @@ ai-toolkit/
|
|
|
206
205
|
│ ├── agents/ # 44 agent definitions
|
|
207
206
|
│ ├── skills/ # 114 skills (task / hybrid / knowledge)
|
|
208
207
|
│ ├── rules/ # Source rules synced into Claude/editor rule files
|
|
209
|
-
│ ├── hooks/ # Hook scripts (
|
|
208
|
+
│ ├── hooks/ # Hook scripts (28 entries, 14 lifecycle events)
|
|
210
209
|
│ ├── claude-app/ # Generated Chat/Cowork plugin rules, hooks, instructions
|
|
211
210
|
│ ├── plugins/ # 2 experimental plugin packs (opt-in)
|
|
212
211
|
│ ├── output-styles/ # System prompt output style overrides
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "ai-toolkit",
|
|
4
4
|
"displayName": "AI Toolkit",
|
|
5
5
|
"description": "Professional-grade engineering skills, agents, rules, and lifecycle guardrails for Claude Code, Claude Chat, and Cowork.",
|
|
6
|
-
"version": "4.32.
|
|
6
|
+
"version": "4.32.4",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "SoftSpark",
|
|
9
9
|
"url": "https://github.com/softspark"
|
package/app/ARCHITECTURE.md
CHANGED
|
@@ -8,7 +8,7 @@ Universal multi-agent system for software development. Works across all reposito
|
|
|
8
8
|
|-----------|-------|
|
|
9
9
|
| Agents | See agents catalog |
|
|
10
10
|
| Skills | See skills catalog |
|
|
11
|
-
| Hooks | 14 events /
|
|
11
|
+
| Hooks | 14 events / 28 entries (SessionStart ×2, Notification ×1, PreToolUse ×5, UserPromptSubmit ×2, PostToolUse ×5, Stop ×4, TaskCompleted ×1, TeammateIdle ×1, SubagentStart ×1, SubagentStop ×1, PreCompact ×2, SessionEnd ×1, InstructionsLoaded ×1, ConfigChange ×1) plus statusLine |
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -352,7 +352,8 @@ Solo-safe core: everything here holds whether one person or twenty merge into
|
|
|
352
352
|
- Use HTTPS everywhere. No exceptions.
|
|
353
353
|
- Implement rate limiting and request throttling.
|
|
354
354
|
- Set CORS headers explicitly. Never use `*` in production.
|
|
355
|
-
- Return
|
|
355
|
+
- Return safe, actionable messages for known failures; use a neutral fallback when the cause is unknown or disclosure would reveal protected information.
|
|
356
|
+
- Keep SQL, stack traces and provider internals out of ordinary client responses, including 4xx and background-job error fields. Preserve original causes in access-controlled, redacted diagnostics.
|
|
356
357
|
- Use security headers: HSTS, X-Content-Type-Options, X-Frame-Options.
|
|
357
358
|
|
|
358
359
|
## Dependencies
|
|
@@ -403,6 +404,12 @@ Solo-safe core: everything here holds whether one person or twenty merge into
|
|
|
403
404
|
- Tests must be fast: unit tests <100ms each, test suite <60s.
|
|
404
405
|
- Avoid `sleep` in tests: use polling, events, or test clocks.
|
|
405
406
|
- Do not test implementation details (private methods, internal state).
|
|
407
|
+
- Run integration suites serially when they share or reset a database. Parallel runners need isolated databases/stores; a filtered test must not invalidate an in-progress full suite.
|
|
408
|
+
|
|
409
|
+
## API Error Paths
|
|
410
|
+
- For changed error handling, assert the actual status, public code/message, field paths, locale and recovery headers through the API boundary.
|
|
411
|
+
- Preserve JSON object/list types, including empty nested `{}` and `[]`, when testing response filters.
|
|
412
|
+
- Exercise real database constraints, idempotent replay and known versus uncertain write outcomes; schema-generated fixtures may omit migration-only indexes.
|
|
406
413
|
|
|
407
414
|
## Coverage
|
|
408
415
|
- Measure coverage but do not chase 100%: focus on critical paths.
|
|
@@ -29,7 +29,28 @@ PROMPT_LEN=${#PROMPT_TEXT}
|
|
|
29
29
|
STATE_DIR="$HOME/.softspark/ai-toolkit/state"
|
|
30
30
|
FLAG="$STATE_DIR/search-required-$(hook_session_id).flag"
|
|
31
31
|
mkdir -p "$STATE_DIR" 2>/dev/null
|
|
32
|
+
|
|
33
|
+
# The flag means "somebody asked a question the KB might answer". Length alone
|
|
34
|
+
# does not establish that. The harness delivers background-task notifications,
|
|
35
|
+
# CI events and replayed slash-command output on the same channel as a prompt,
|
|
36
|
+
# and a 43-character API key pasted on its own line clears the length gate too.
|
|
37
|
+
# Both were observed in one session: the first demanded a KB search for text
|
|
38
|
+
# nobody asked about, the second would have sent a live credential to a
|
|
39
|
+
# retrieval service as the query. Neither is a prompt.
|
|
40
|
+
PROMPT_HEAD="${PROMPT_TEXT#"${PROMPT_TEXT%%[![:space:]]*}"}"
|
|
41
|
+
PROMPT_IS_QUESTION=1
|
|
42
|
+
case "$PROMPT_HEAD" in
|
|
43
|
+
'<task-notification'*|'<ci-monitor-event'*|'<system-reminder'*|\
|
|
44
|
+
'<local-command-'*|'<command-name'*|'<command-message'*) PROMPT_IS_QUESTION=0 ;;
|
|
45
|
+
esac
|
|
46
|
+
# A single opaque token — API key, hash, URL, path — carries no question. A
|
|
47
|
+
# genuine one-word prompt is under the length gate anyway, so nothing is lost.
|
|
48
|
+
if [ "$(printf '%s' "$PROMPT_TEXT" | wc -w | tr -d ' ')" -le 1 ]; then
|
|
49
|
+
PROMPT_IS_QUESTION=0
|
|
50
|
+
fi
|
|
51
|
+
|
|
32
52
|
if [ "$PROMPT_LEN" -gt 30 ] && \
|
|
53
|
+
[ "$PROMPT_IS_QUESTION" -eq 1 ] && \
|
|
33
54
|
[ "${CLAUDE_SKIP_SEARCH_FIRST:-0}" != "1" ] && \
|
|
34
55
|
ai_toolkit_has_search_provider; then
|
|
35
56
|
{ printf '%s\n%s\n' "$(date -u +%s)" "$PROMPT_TEXT" > "$FLAG"; } 2>/dev/null
|
|
@@ -45,7 +45,8 @@ version: "1.0.0"
|
|
|
45
45
|
- Use HTTPS everywhere. No exceptions.
|
|
46
46
|
- Implement rate limiting and request throttling.
|
|
47
47
|
- Set CORS headers explicitly. Never use `*` in production.
|
|
48
|
-
- Return
|
|
48
|
+
- Return safe, actionable messages for known failures; use a neutral fallback when the cause is unknown or disclosure would reveal protected information.
|
|
49
|
+
- Keep SQL, stack traces and provider internals out of ordinary client responses, including 4xx and background-job error fields. Preserve original causes in access-controlled, redacted diagnostics.
|
|
49
50
|
- Use security headers: HSTS, X-Content-Type-Options, X-Frame-Options.
|
|
50
51
|
|
|
51
52
|
## Dependencies
|
|
@@ -46,6 +46,12 @@ paths:
|
|
|
46
46
|
- Tests must be fast: unit tests <100ms each, test suite <60s.
|
|
47
47
|
- Avoid `sleep` in tests: use polling, events, or test clocks.
|
|
48
48
|
- Do not test implementation details (private methods, internal state).
|
|
49
|
+
- Run integration suites serially when they share or reset a database. Parallel runners need isolated databases/stores; a filtered test must not invalidate an in-progress full suite.
|
|
50
|
+
|
|
51
|
+
## API Error Paths
|
|
52
|
+
- For changed error handling, assert the actual status, public code/message, field paths, locale and recovery headers through the API boundary.
|
|
53
|
+
- Preserve JSON object/list types, including empty nested `{}` and `[]`, when testing response filters.
|
|
54
|
+
- Exercise real database constraints, idempotent replay and known versus uncertain write outcomes; schema-generated fixtures may omit migration-only indexes.
|
|
49
55
|
|
|
50
56
|
## Coverage
|
|
51
57
|
- Measure coverage but do not chase 100%: focus on critical paths.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: api-patterns
|
|
3
|
-
description: "
|
|
3
|
+
description: "API design: naming, versioning, pagination, idempotency, OpenAPI, error contracts and safe retries. Triggers: API design, REST, GraphQL, OpenAPI, Swagger, error response, HTTP status, rate limit."
|
|
4
4
|
effort: medium
|
|
5
5
|
user-invocable: false
|
|
6
6
|
allowed-tools: Read
|
|
@@ -34,17 +34,24 @@ GET /api/v1/users/{id}/documents # User's documents
|
|
|
34
34
|
| 200 | OK | Successful GET/PUT/PATCH |
|
|
35
35
|
| 201 | Created | Successful POST |
|
|
36
36
|
| 204 | No Content | Successful DELETE |
|
|
37
|
-
| 400 | Bad Request |
|
|
37
|
+
| 400 | Bad Request | Malformed request or an established domain refusal |
|
|
38
38
|
| 401 | Unauthorized | Missing/invalid auth |
|
|
39
39
|
| 403 | Forbidden | No permission |
|
|
40
40
|
| 404 | Not Found | Resource doesn't exist |
|
|
41
|
-
|
|
|
41
|
+
| 405 | Method Not Allowed | Unsupported method; preserve Allow |
|
|
42
|
+
| 409 | Conflict | Duplicate resource or current-state conflict |
|
|
43
|
+
| 412 | Precondition Failed | Supplied concurrency version is stale |
|
|
42
44
|
| 422 | Unprocessable | Validation error |
|
|
45
|
+
| 428 | Precondition Required | Required concurrency precondition is missing |
|
|
43
46
|
| 429 | Too Many Requests | Rate limited |
|
|
44
47
|
| 500 | Internal Error | Server error |
|
|
48
|
+
| 503 | Service Unavailable | Dependency temporarily unavailable |
|
|
45
49
|
|
|
46
50
|
### Response Format
|
|
47
51
|
|
|
52
|
+
Follow the host's existing resource and collection contract. This envelope is
|
|
53
|
+
illustrative; do not impose it on a framework that already defines another shape.
|
|
54
|
+
|
|
48
55
|
```json
|
|
49
56
|
{
|
|
50
57
|
"data": {
|
|
@@ -65,6 +72,9 @@ GET /api/v1/users/{id}/documents # User's documents
|
|
|
65
72
|
|
|
66
73
|
### Error Response
|
|
67
74
|
|
|
75
|
+
Use the established error representation, which may be problem details,
|
|
76
|
+
framework validation errors, or a domain-specific envelope like this example:
|
|
77
|
+
|
|
68
78
|
```json
|
|
69
79
|
{
|
|
70
80
|
"error": {
|
|
@@ -78,6 +88,17 @@ GET /api/v1/users/{id}/documents # User's documents
|
|
|
78
88
|
}
|
|
79
89
|
```
|
|
80
90
|
|
|
91
|
+
When implementing or reviewing failure paths, read
|
|
92
|
+
[Error contracts and safe retries](reference/error-contracts.md). Classify from
|
|
93
|
+
the original cause, preserve public codes and JSON types, and distinguish an
|
|
94
|
+
unknown operation outcome from a confirmed refusal. Do not turn arbitrary
|
|
95
|
+
server failures into invalid-input responses.
|
|
96
|
+
|
|
97
|
+
When adding client-side validation, read `reference/input-validation.md` from
|
|
98
|
+
the installed `security-patterns` skill. Resolve that skill through the current
|
|
99
|
+
client's catalog, since adapters may namespace skill directory names.
|
|
100
|
+
Use one authoritative rule source and prove parity at the request boundary.
|
|
101
|
+
|
|
81
102
|
---
|
|
82
103
|
|
|
83
104
|
## FastAPI Implementation
|
|
@@ -88,6 +109,9 @@ from pydantic import BaseModel, Field
|
|
|
88
109
|
|
|
89
110
|
app = FastAPI(title="RAG-MCP API", version="1.0.0")
|
|
90
111
|
|
|
112
|
+
class InvalidSearchQuery(Exception):
|
|
113
|
+
"""Known request-level refusal from the application-owned search adapter."""
|
|
114
|
+
|
|
91
115
|
class SearchRequest(BaseModel):
|
|
92
116
|
query: str = Field(..., min_length=1, description="Search query")
|
|
93
117
|
limit: int = Field(10, ge=1, le=100, description="Max results")
|
|
@@ -115,8 +139,11 @@ async def search(request: SearchRequest):
|
|
|
115
139
|
try:
|
|
116
140
|
results = await perform_search(request.query, request.limit)
|
|
117
141
|
return SearchResponse(results=results, total=len(results))
|
|
118
|
-
except
|
|
119
|
-
raise HTTPException(
|
|
142
|
+
except InvalidSearchQuery as exc:
|
|
143
|
+
raise HTTPException(
|
|
144
|
+
status_code=400,
|
|
145
|
+
detail="The search query is not supported. Check its syntax.",
|
|
146
|
+
) from exc
|
|
120
147
|
```
|
|
121
148
|
|
|
122
149
|
---
|
|
@@ -146,7 +173,7 @@ In OpenAPI, pair `enum` with the value meanings in the description (or `x-enum-d
|
|
|
146
173
|
|
|
147
174
|
### Encode cross-field dependencies in the description
|
|
148
175
|
|
|
149
|
-
|
|
176
|
+
Document cross-field requirements where the dependent field is defined and encode them in the supported schema dialect. JSON Schema supports conditional requirements with `dependentRequired` or `if`/`then`; application state and opaque-token provenance still require prose and runtime checks. See [conditional schema validation](https://json-schema.org/understanding-json-schema/reference/conditionals).
|
|
150
177
|
|
|
151
178
|
```python
|
|
152
179
|
cursor: str | None = Field(
|
|
@@ -372,10 +399,10 @@ Accept: application/vnd.myapi.v1+json
|
|
|
372
399
|
## Hard Rules
|
|
373
400
|
|
|
374
401
|
- **MUST** version the API from day one (URL path or Accept header) — unversioned APIs break clients on every change
|
|
375
|
-
- **MUST** validate
|
|
402
|
+
- **MUST** validate input type, format and size at the API boundary; enforce state-dependent domain invariants in business logic as well
|
|
376
403
|
- **MUST** use PUT for full replacement and PATCH for partial update — confusing the two causes silent data loss
|
|
377
404
|
- **NEVER** return unbounded list responses — pagination (offset or cursor) is mandatory
|
|
378
|
-
- **NEVER** expose
|
|
405
|
+
- **NEVER** expose private implementation details in ordinary API errors, including 4xx and background-job error fields; preserve the host's public error representation
|
|
379
406
|
- **CRITICAL**: idempotency on POST/PUT/PATCH is non-negotiable when retries are possible — accept an `Idempotency-Key` header or design the endpoint to be naturally idempotent
|
|
380
407
|
- **CRITICAL**: rate limits exist from the first deploy, not "later" — unprotected endpoints get abused within hours
|
|
381
408
|
|
|
@@ -395,10 +422,10 @@ Accept: application/vnd.myapi.v1+json
|
|
|
395
422
|
## Gotchas
|
|
396
423
|
|
|
397
424
|
- CDN and load-balancer caches key on the full URL by default. If you version via both URL path and `Accept` header (e.g., `/api/v1/...` + `Accept: application/vnd.myapi.v2+json`), the edge returns the wrong payload for non-path versioning. Pick one versioning axis and stick to it.
|
|
398
|
-
- OpenAPI `additionalProperties: false`
|
|
425
|
+
- A published OpenAPI schema does not prove request enforcement. Ajv enforces `additionalProperties: false` when that schema is executed; its strict mode checks schema correctness and does not change validation results. Verify that the request boundary runs the intended schema and test unknown fields. See [Ajv strict mode](https://ajv.js.org/strict-mode.html) and [additionalProperties](https://ajv.js.org/json-schema.html#additionalproperties).
|
|
399
426
|
- `Idempotency-Key` only works if the server persists the mapping from key to response — purely in-memory implementations forget it on restart. Back it with Redis or the primary DB.
|
|
400
427
|
- HTTP methods are **case-sensitive** per RFC 7230 (all uppercase); some clients and proxies normalize, some don't. A `post` method reaches the server as-is through some edge proxies and hits a 405 instead of the POST route.
|
|
401
|
-
-
|
|
428
|
+
- Give rate-limited callers a meaningful `Retry-After`. For 503, include it when the server can provide a credible retry window; do not invent an outage duration. A retry header does not prove that repeating a write is safe.
|
|
402
429
|
|
|
403
430
|
## When NOT to Load
|
|
404
431
|
|
|
@@ -407,4 +434,3 @@ Accept: application/vnd.myapi.v1+json
|
|
|
407
434
|
- For **language-specific idioms** (Fastify middleware chains, ASP.NET minimal APIs, etc.) — pair this skill with `/typescript-patterns`, `/csharp-patterns`, etc.
|
|
408
435
|
- For **OpenAPI schema authoring** as the primary task — use `/docs` with OpenAPI output; this skill is design-only
|
|
409
436
|
- For **authentication deep-dives** beyond the starter API-key and JWT snippets — use `/security-patterns`
|
|
410
|
-
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Error Contracts and Safe Retries
|
|
2
|
+
|
|
3
|
+
Use this reference for API failure paths. Apply the host's current contract and
|
|
4
|
+
keep unrelated endpoints, schemas and workflows outside the requested change.
|
|
5
|
+
|
|
6
|
+
## Classify before wording
|
|
7
|
+
|
|
8
|
+
- Trace the original exception and persisted/provider state. A truncated message
|
|
9
|
+
or a generic unique-constraint error does not identify the affected entity.
|
|
10
|
+
- Prefer typed domain exceptions and structured database/provider codes. If a
|
|
11
|
+
constraint name is available only in text, read its diagnostic header rather
|
|
12
|
+
than matching a submitted value in the detail section.
|
|
13
|
+
- Distinguish malformed input, authorization, current-state conflict and a
|
|
14
|
+
dependency failure. Preserve deliberate domain-specific status conventions;
|
|
15
|
+
do not mechanically rewrite every existing 400.
|
|
16
|
+
- Catch known domain failures narrowly and preserve the exception cause. A
|
|
17
|
+
catch-all that copies arbitrary exception text into a 4xx response both leaks
|
|
18
|
+
internals and blames the caller for server failures.
|
|
19
|
+
|
|
20
|
+
## Give a supported explanation and a safe action
|
|
21
|
+
|
|
22
|
+
A useful message identifies the affected operation/resource, the cause that is
|
|
23
|
+
actually known, and the next safe action. Use a neutral fallback when the cause
|
|
24
|
+
is unknown or disclosing it would reveal protected information.
|
|
25
|
+
|
|
26
|
+
| Observation | Response direction |
|
|
27
|
+
|---|---|
|
|
28
|
+
| A resource already has an open session | Explain the existing-session conflict |
|
|
29
|
+
| A provider request received no response | Explain the missing response, not a supposed provider rejection |
|
|
30
|
+
| The date parser rejected a timestamp | Identify the date/time problem, not an unrelated score or amount |
|
|
31
|
+
| An internal persistence invariant failed | Report a server failure, not a missing client field |
|
|
32
|
+
| A refund may have been accepted remotely | Require reconciliation before another refund |
|
|
33
|
+
|
|
34
|
+
Keep SQL, traces, internal paths and provider internals out of ordinary failure
|
|
35
|
+
text. Preserve original causes in access-controlled, redacted diagnostics.
|
|
36
|
+
Sanitized administrator connection tests and expected row-level validation may
|
|
37
|
+
legitimately provide more detail; do not erase useful authorized diagnostics.
|
|
38
|
+
|
|
39
|
+
## Preserve the wire contract
|
|
40
|
+
|
|
41
|
+
- Keep existing envelopes, machine codes, field paths and supported media types.
|
|
42
|
+
A client-owned error code must not silently become localized server prose.
|
|
43
|
+
- Preserve JSON object/list types, including empty nested `{}` and `[]`.
|
|
44
|
+
Test the round trip when adding a response filter or normalizer.
|
|
45
|
+
- Keep recovery headers such as `Allow`, `Retry-After`, authentication challenges
|
|
46
|
+
and concurrency preconditions.
|
|
47
|
+
- Verify the host's actual locale selection and fallback. Do not invent a
|
|
48
|
+
profile/tenant/header precedence or assume every existing literal is translated.
|
|
49
|
+
- Cover router/firewall errors outside the main API framework and account for
|
|
50
|
+
cloned requests or error subrequests before relying on request attributes.
|
|
51
|
+
- Inspect error fields returned with HTTP 200 by background-job status APIs and
|
|
52
|
+
downloadable failure reports. Retain domain validation; sanitize unexpected
|
|
53
|
+
worker failures without changing retry/accounting behavior.
|
|
54
|
+
|
|
55
|
+
## Do not infer rollback from the response
|
|
56
|
+
|
|
57
|
+
A transaction can commit before serialization or response delivery fails. A
|
|
58
|
+
remote service can accept an operation before its reply is lost. An HTTP error,
|
|
59
|
+
including a 5xx, therefore does not prove that nothing happened.
|
|
60
|
+
|
|
61
|
+
Use the operation's existing idempotency/reconciliation contract. Confirm state
|
|
62
|
+
before creating another write. Do not recommend blind replay of an uncertain
|
|
63
|
+
payment, refund or other external mutation, and do not remove a database
|
|
64
|
+
invariant merely to make its error disappear.
|
|
65
|
+
|
|
66
|
+
## Verify behavior at the boundary
|
|
67
|
+
|
|
68
|
+
Choose tests for the paths actually changed:
|
|
69
|
+
|
|
70
|
+
- real constraints, including migration-only indexes omitted by test schemas;
|
|
71
|
+
- expected refusals versus unexpected runtime/provider failures;
|
|
72
|
+
- idempotent replay, conflicts and recovery after the conflict is resolved;
|
|
73
|
+
- HTTP status, public text/code, field paths, locale and recovery headers;
|
|
74
|
+
- supported response formats and empty object/list preservation;
|
|
75
|
+
- known versus uncertain write outcomes and background-job error fields;
|
|
76
|
+
- diagnostic retention without public technical details.
|
|
77
|
+
|
|
78
|
+
Test actual rate-limit exhaustion and recovery. A non-consuming peek may report
|
|
79
|
+
that the peek was accepted even when no request budget remains; the configured
|
|
80
|
+
limiter's behavior determines the guard.
|
|
81
|
+
|
|
82
|
+
Run tests serially when they share or reset a database. Parallel test runners
|
|
83
|
+
need isolated databases/stores; a filtered test must not reset the fixtures of
|
|
84
|
+
an in-progress full suite.
|
|
85
|
+
|
|
86
|
+
For additional project evidence, when RAG-MCP is available, retrieve
|
|
87
|
+
`shared/rag-mcp/best-practices/api-error-contracts.md`. The guidance above is
|
|
88
|
+
self-contained and does not require that integration.
|
|
@@ -125,7 +125,12 @@ After all reviewers complete:
|
|
|
125
125
|
- [ ] Backward compatibility preserved (no silent breaking changes)
|
|
126
126
|
- [ ] API versioning updated if contract changed
|
|
127
127
|
- [ ] Schema validation on request/response
|
|
128
|
+
- [ ] Client validation uses the authoritative input contract, including operation groups/defaults, finite bounds, nested paths and documented Unicode units; read `reference/input-validation.md` from the `security-patterns` skill located through the current client's installed catalog
|
|
129
|
+
- [ ] Shared backend/client fixtures and generation drift checks cover changed rules; unsupported/server-only checks are explicit, and local refusals do not masquerade as HTTP responses
|
|
128
130
|
- [ ] Error responses follow project convention
|
|
131
|
+
- [ ] Statuses and messages distinguish input/state refusals from infrastructure failures; original causes remain available in authorized diagnostics
|
|
132
|
+
- [ ] Error filtering preserves machine codes, field paths, JSON object/list types, locale and recovery headers; background-job error fields are covered too
|
|
133
|
+
- [ ] Retry advice reflects known persisted/provider state and does not invite blind replay of an uncertain mutation
|
|
129
134
|
- [ ] Wire-level contracts checked, not just code signatures: HTTP routes, webhook payloads, event/queue schemas
|
|
130
135
|
|
|
131
136
|
### Concurrency / Async
|
|
@@ -129,13 +129,13 @@ For authentication patterns (JWT, passwords, token strategy), see [reference/aut
|
|
|
129
129
|
|
|
130
130
|
For authorization patterns (RBAC, ABAC), see [reference/authorization.md](reference/authorization.md).
|
|
131
131
|
|
|
132
|
-
For input validation
|
|
132
|
+
For input validation, client/backend contract parity, finite bounds and Unicode gotchas, see [reference/input-validation.md](reference/input-validation.md).
|
|
133
133
|
|
|
134
134
|
For OAuth2 flows, CSRF protection, and audit logging, see [reference/oauth-csrf-audit.md](reference/oauth-csrf-audit.md).
|
|
135
135
|
|
|
136
136
|
## Rules
|
|
137
137
|
|
|
138
|
-
- **MUST** validate
|
|
138
|
+
- **MUST** validate input type, format and size at the trust boundary; domain logic must also enforce state-dependent invariants before side effects
|
|
139
139
|
- **MUST** use parameterized queries (prepared statements) for every SQL interaction — string concatenation is SQL injection
|
|
140
140
|
- **NEVER** store secrets (API keys, tokens, passwords) in code, config files, or git history — use the platform's secret manager
|
|
141
141
|
- **NEVER** log passwords, tokens, PII, or PHI — even at debug level. Logs reach aggregation systems, backups, and disk snapshots.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Validation Rules
|
|
4
4
|
```python
|
|
5
|
-
from pydantic import BaseModel, EmailStr, constr
|
|
5
|
+
from pydantic import BaseModel, EmailStr, Field, constr
|
|
6
6
|
|
|
7
7
|
class UserInput(BaseModel):
|
|
8
8
|
email: EmailStr
|
|
@@ -10,6 +10,74 @@ class UserInput(BaseModel):
|
|
|
10
10
|
age: int = Field(ge=0, le=150)
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
## Validation contract gotchas
|
|
14
|
+
|
|
15
|
+
These traps came from repeated corrections during a DTO-to-client validation
|
|
16
|
+
rollout. Apply them when a client mirrors server rules; they do not require a
|
|
17
|
+
new generator for every small form.
|
|
18
|
+
|
|
19
|
+
- **Two validators can still be two conflicting policies.** Derive portable
|
|
20
|
+
client checks from the server's authoritative schema or resolved metadata.
|
|
21
|
+
A shared schema is also valid when the backend actually enforces it. Bind
|
|
22
|
+
field feedback and serialized-payload preflight to that same contract.
|
|
23
|
+
Backend enforcement remains mandatory, including when client code is bypassed.
|
|
24
|
+
- **The entity may never be validated.** Trace the operation's real input object.
|
|
25
|
+
In API Platform, an input DTO can be validated before mapping to an entity.
|
|
26
|
+
Validation groups select constraints; denormalization groups select writable
|
|
27
|
+
fields. Output-only constraints add no protection to the request path.
|
|
28
|
+
- **PATCH does not make every property optional.** An update hydrated from
|
|
29
|
+
existing state and a newly constructed input DTO have different missing-field
|
|
30
|
+
semantics. Preserve actual defaults and distinguish omitted keys, null and
|
|
31
|
+
empty values. Resolve this per operation, not from the HTTP method alone.
|
|
32
|
+
- **A constraint name is not a finite bound.** Length(min), Count(min),
|
|
33
|
+
All(Email) and an unbounded regex do not cap input size. Check actual maximum
|
|
34
|
+
values, collection size, item shape/type and nested validation separately.
|
|
35
|
+
In Symfony, retain All/Collection/Valid and sequential evaluation semantics.
|
|
36
|
+
Bound body size and violation output too; stop expensive item validation
|
|
37
|
+
after an oversized list is refused.
|
|
38
|
+
- **A field name does not identify its storage or wire format.** Read the
|
|
39
|
+
mapper/processor and owning storage before choosing limits. An Id field can
|
|
40
|
+
accept an IRI; a timestamp ceiling does not validate a real date. Feature
|
|
41
|
+
limits and established domain error codes can be stricter or more specific
|
|
42
|
+
than a generic ceiling. Do not replace them accidentally in a bulk pass.
|
|
43
|
+
- **Unicode length has several units.** Specify bytes, code units, codepoints
|
|
44
|
+
or grapheme clusters and the normalization order. Test decomposed accents,
|
|
45
|
+
non-BMP characters, joined emoji and IME composition. A client must not
|
|
46
|
+
silently truncate a value the server accepts; preserve input when showing an
|
|
47
|
+
error. Do not normalize passwords or apply identifier alphabet rules to
|
|
48
|
+
names without a documented product policy.
|
|
49
|
+
- **Rule names hide runtime options.** Email modes, URL schemes, numeric
|
|
50
|
+
coercion, regex dialects and conditional validation can differ across
|
|
51
|
+
languages. Export supported options explicitly. Conditional rules are
|
|
52
|
+
audited data, never arbitrary server expressions evaluated in the client.
|
|
53
|
+
- **Some checks depend on server state or libraries.** Keep authorization,
|
|
54
|
+
availability, uniqueness and domain invariants on the server. If a portable
|
|
55
|
+
equivalent is unavailable, record the exact operation/field/rule and reason
|
|
56
|
+
in reviewed policy. Newly unsupported rules must surface as a failed
|
|
57
|
+
generation/check step, not become an always-valid adapter. Bodyless routes
|
|
58
|
+
and exclusions are separate measurements, not evidence of client coverage.
|
|
59
|
+
- **A local refusal is not an HTTP response.** Use a distinct local error type,
|
|
60
|
+
preserve field paths and rule codes, and show localized field feedback.
|
|
61
|
+
Do not invent an HTTP status or trigger token refresh/network retry for a
|
|
62
|
+
request that was never sent. Server refusals remain authoritative; keep
|
|
63
|
+
local/server error state distinguishable and test correction/resubmission.
|
|
64
|
+
- **Generation proves synchronization, not semantic equivalence.** Run shared
|
|
65
|
+
accepted/rejected fixtures through the real backend validator and client
|
|
66
|
+
evaluator, including both conditional branches and nested error paths.
|
|
67
|
+
Check deterministic generation and reviewed exclusions in CI. A snapshot
|
|
68
|
+
of the exporter tested against itself does not prove parity. HTTP tests
|
|
69
|
+
must also exercise deserialization and rejected-write persistence behavior.
|
|
70
|
+
|
|
71
|
+
Input validation does not replace parameterized queries or context-appropriate
|
|
72
|
+
output encoding. The backend may still reject a client-valid request because
|
|
73
|
+
state changed after local validation.
|
|
74
|
+
|
|
75
|
+
For regression selection, use the "Validation contract regressions" section
|
|
76
|
+
of the installed `testing-patterns` skill. For HTTP failure semantics, read
|
|
77
|
+
`reference/error-contracts.md` from the installed `api-patterns` skill.
|
|
78
|
+
Locate these skills through the current client's catalog because adapters may
|
|
79
|
+
namespace their directory names.
|
|
80
|
+
|
|
13
81
|
## SQL Injection Prevention
|
|
14
82
|
```python
|
|
15
83
|
# Bad
|
|
@@ -53,6 +53,25 @@ tests/
|
|
|
53
53
|
|
|
54
54
|
---
|
|
55
55
|
|
|
56
|
+
## Validation contract regressions
|
|
57
|
+
|
|
58
|
+
Mirrored validators can agree on a generated snapshot while disagreeing on real
|
|
59
|
+
inputs. Execute the same accepted/rejected fixtures against the real backend
|
|
60
|
+
validator and the client evaluator. Include N/N+1 boundaries, collection/item
|
|
61
|
+
limits, omitted/null/empty/default values, nested paths, conditional branches,
|
|
62
|
+
hydrated updates versus fresh DTOs, Unicode units and previously valid inputs.
|
|
63
|
+
|
|
64
|
+
Test the actual HTTP path for malformed input and unchanged persisted state
|
|
65
|
+
after a pre-write refusal. Test zero transport calls for a local refusal,
|
|
66
|
+
normal transport for a valid payload, and field correction/resubmission.
|
|
67
|
+
Run deterministic-generation and reviewed-exclusion checks separately from
|
|
68
|
+
behavioral parity. A schema/rule count is inventory, not assertion coverage.
|
|
69
|
+
|
|
70
|
+
For operation semantics and runtime mismatches, read
|
|
71
|
+
`reference/input-validation.md` from the installed `security-patterns` skill.
|
|
72
|
+
Resolve the skill through the current client's catalog; directory names may
|
|
73
|
+
carry an adapter-specific prefix.
|
|
74
|
+
|
|
56
75
|
## Language-Specific References
|
|
57
76
|
|
|
58
77
|
| Language | Reference | Key Topics |
|
package/app/surface.json
CHANGED
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"chaos",
|
|
17
17
|
"ci",
|
|
18
18
|
"ci-cd-patterns",
|
|
19
|
+
"claude-toolkit-rules",
|
|
19
20
|
"clean-code",
|
|
20
21
|
"command-creator",
|
|
21
22
|
"commit",
|
|
@@ -36,12 +37,14 @@
|
|
|
36
37
|
"docs",
|
|
37
38
|
"documentation-standards",
|
|
38
39
|
"ecommerce-patterns",
|
|
40
|
+
"edit-discipline",
|
|
39
41
|
"evaluate",
|
|
40
42
|
"evolve",
|
|
41
43
|
"explain",
|
|
42
44
|
"explore",
|
|
43
45
|
"fix",
|
|
44
46
|
"flutter-patterns",
|
|
47
|
+
"git-conventions",
|
|
45
48
|
"git-mastery",
|
|
46
49
|
"golang-rules",
|
|
47
50
|
"grill-me",
|
|
@@ -68,6 +71,7 @@
|
|
|
68
71
|
"observability-patterns",
|
|
69
72
|
"onboard",
|
|
70
73
|
"orchestrate",
|
|
74
|
+
"output-mode",
|
|
71
75
|
"panic",
|
|
72
76
|
"performance-profiling",
|
|
73
77
|
"persona",
|
|
@@ -81,6 +85,7 @@
|
|
|
81
85
|
"prompt-caching-patterns",
|
|
82
86
|
"python-rules",
|
|
83
87
|
"qa-session",
|
|
88
|
+
"quality-gates",
|
|
84
89
|
"rag-patterns",
|
|
85
90
|
"refactor",
|
|
86
91
|
"refactor-plan",
|