3.5 KiB
CONTRACT — rest-api-rule
- date: 2026-09-27 | flow: feat (ad-hoc dispatch, /feat gates replayed by the orchestrator) | branch: feature/agent-skills-borrow
- status: active
REQUEST (verbatim — IMMUTABLE)
Write
rules/rest-api.md, a path-scoped user rule distilled from addyosmani/agent-skillsapi-and-interface-design(commit 2686b620fc1fed2e8f60c704839c766b8594c6b6), the wayrules/web-building.mdis written:paths:frontmatter, English, <= 45 lines, 80-char lines. Keep: contract-first order (typed interface → schemas with server-generated fields apart → error codes → validate at boundaries only); one error envelope{ error: { code, message, details? } }with the HTTP map 400 invalid / 401 auth / 403 forbidden / 404 missing / 409 conflict / 422 semantic / 500 server; every list endpoint paginated (page,pageSize,totalItems,totalPages), filters as query params; idempotency (key derived from intent, atomic claim via unique constraint, payload guard, explicit in-flight duplicate policy 409 / wait / 202, retention beyond the longest retry path incl. dead-letter); naming (plural nouns, camelCase params and fields, UPPER_SNAKE enums, is/has/can booleans); one Hyrum's law line. Drop the upstream one-version rule: versioning points to the CLAUDE.md heading "Web APIs — always versioned". User go 2026-09-27 ("ok pour les 4", case 2 item 4).
CLARIFICATIONS
- Globs:
["**/api/**", "**/routes/**", "**/controllers/**", "**/*.route.*", "**/*.controller.*", "**/openapi.*", "**/*.openapi.*"]. - Fetch the upstream text with curl at the pinned commit to distill from; never vendor it.
- Cite the doctrine as
CLAUDE.md § Web APIs — always versioned(exact heading, so the doctrine-citers census resolves it). - No routing line in CLAUDE.global.md, no README change, rules/README.md unchanged; CHANGELOG Unreleased entry.
ACCEPTANCE CRITERIA
- Shape: frontmatter, paths JSON list, <= 45 lines, body lines <= 80 chars (the one-line
paths:frontmatter is exempt, repo precedent: web-building.md / web-security.md line 2). CHECK: f=rules/rest-api.md; [ -f "$f" ] && [ "$(head -1 "$f")" = "---" ] && python3 -c 'import re,json; s=open("rules/rest-api.md").read(); m=re.search(r"^paths: (.*)$",s,re.M); assert len(json.loads(m.group(1)))>=5' && [ "$(wc -l < "$f")" -le 45 ] && ! awk 'NR>3 && length>80' "$f" | grep -q . && echo RULE_SHAPE EXPECT: RULE_SHAPE EVIDENCE: MET exit=0 marker-found :: RULE_SHAPE - Content present, one-version rule absent. CHECK: f=rules/rest-api.md; ok=1; for k in "code" "message" "409" "422" "pageSize" "totalItems" "idempoten" "unique" "camelCase" "UPPER_SNAKE" "Hyrum" "always versioned"; do grep -qi -- "$k" "$f" || { echo "missing $k"; ok=0; }; done; grep -qi "extend rather than fork" "$f" && { echo "one-version rule present"; ok=0; }; [ "$ok" -eq 1 ] && echo RULE_CONTENT EXPECT: RULE_CONTENT EVIDENCE: MET exit=0 marker-found :: RULE_CONTENT
- Doctrine citation resolves. CHECK: out=$(make test suite=lib/tests/doctrine-citers.test.sh 2>&1); echo "$out" | grep -qE "FAIL=[1-9]" && { echo "$out" | tail -10; exit 1; }; echo CITERS_OK EXPECT: CITERS_OK EVIDENCE: MET exit=0 marker-found :: CITERS_OK
FILE SCOPE
- rules/rest-api.md (new), CHANGELOG.md (Unreleased entry)
PLAN
- curl the upstream SKILL.md at the pin into the scratch dir; read it.
- Write the rule: title "REST API — contract, errors, lists, idempotency"; sections Contract first · Errors · Lists · Idempotency · Naming · Versioning (one line pointing to the doctrine heading).
- CHANGELOG line; run criteria 1-3.