Install
$ agentstack add skill-genkovich-sdd-api ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Skill: api
Projects the upstream artifacts into one interface contract. By default that's an HTTP/OpenAPI contract; this skill is interface-kind aware — and the kind comes from the surface(s) design declared in sad.md frontmatter target_surfaces, read here, not re-derived (→ [../_shared/surfaces.md](../_shared/surfaces.md)). For a non-HTTP project it produces the matching contract form (or steps aside):
- HTTP / REST (default) →
contracts/openapi.yaml(OpenAPI 3.1) +api-sync-report.md. - gRPC / RPC → a
.proto(or the repo's IDL) with the same derive-and-drift discipline. - CLI →
contracts/cli.md— the command/flag/exit-code surface derived from the AC. - Library / SDK →
contracts/public-api.md— the public signatures/types the feature exposes. - Event-only / worker → just
contracts/events.md(no request/response surface). - No external interface (pure internal logic) → skip with a one-line note in the report; go straight to
tasks.
Whatever the form, the contract is derived from data-model.md + the sad.md §6 sequences + the spec's AC, never typed by hand — generation that diverges from the model or the sequences is the bug this skill exists to catch. The rest of this file details the HTTP path (the common case); the same derive → drift-check → reconcile loop applies to the other forms with the form-appropriate artifact.
This skill keeps only its own machinery. Question phrasing is shared → [../_shared/ask-style.md](../shared/ask-style.md). Depth (events doc only when async; one resource vs full surface) follows the size matrix → [../_shared/size-matrix.md](../shared/size-matrix.md). The drift-resolution dialog reuses the shared 4-state actions — keep it short, point the machinery to _shared.
Owner
Backend Lead (drives the interface). The PM confirms each endpoint maps to a real user story; a frontend / consumer engineer is the first reader — the contract is locked before they start integration.
Inputs
- `` — same feature slug used by every earlier stage.
- Gate (hard-refuse if missing):
docs/features//data-model.md. It is the source of typed fields and constraints; without it the contract would be invented field-by-field. If absent → STOP and point: «rundata-modelfirst — the contract is derived from its entities». - (Expected)
sad.mdfrontmattertarget_surfaces— picks the contract form (step 1). Absent or empty → warn («surfaces undeclared — re-rundesign, or proceeding asbackend-service») and treat as[backend-service], falling back to the architecture-map derivation (→ [../_shared/surfaces.md](../_shared/surfaces.md)). - (Expected)
docs/features//sad.md§6 — the MermaidsequenceDiagramblocks. Theiralt/elsebranches become the errorresponses; an async participant (`/) on a mutating flow marks its endpointIdempotency-Key-required and seedsevents.md. Absent → note the gap (error branches derived fromspec.md` §5 only — likely misses authorization branches) and still generate. - (Expected)
docs/features//spec.md— §4 user stories give the endpoint list; §5 acceptance criteria give the shape of each happy + error outcome. The spec deliberately holds no HTTP/status/error-code/SQL detail — that mapping is this skill's job. - (Optional)
docs/features//.size— depth hint. Absent → default to M (full surface) and say so loudly in the handoff — «size M (default — no.size; run/sdd:classify-size)».docs/features//adr/*.md— override defaults (versioning, error format, auth scheme) when an ADR mandates it;docs/features//CONTEXT.md— glossary terms become schema names verbatim. Existingcontracts/openapi.yaml→ diff and update in place, never overwrite whole-cloth.
Protocol
- Gate + interface kind + read.
test -f docs/features//data-model.md→ fail = refuse with the pointer above. Determine the interface kind — readsad.mdfrontmattertarget_surfacesFIRST (design already declared it; the surface picks the contract form per [../_shared/surfaces.md](../shared/surfaces.md):backend-service→ OpenAPI / gRPC / events per its sub-kind;cli→contracts/cli.md;worker→contracts/events.md;library-sdk→contracts/public-api.md; a UI surface —web-frontend/mobile-app/desktop-app— consumes the backend contract, it does not author one). Fall back to deriving the kind fromdocs/architecture-map.md+ the spec's capabilities only if the SAD or the field is absent (a greenfield run wheredesignwas skipped). HTTP/REST → the OpenAPI path below (the default, detailed here); gRPC/CLI/library/event-only → produce the matching contract form (see the intro) with this same derive→drift→reconcile loop; no external interface (pure internal logic) → skip totaskswith a one-line note in the report — this self-skip isapi's N/A condition in the [size-matrix fast lane](../shared/size-matrix.md). Then readdata-model.md(entities, fields, types, constraints),sad.md§6 (flows +alt-branches + async actors),spec.md§4/§5. Surface a one-line "found / missing" note for sad.md and spec.md — never refuse on their absence, only narrow the derivation and record the gap. - Copy the template. [
./templates/openapi.yaml](./templates/openapi.yaml) →docs/features//contracts/openapi.yaml. If async flows exist, also [./templates/events.md](./templates/events.md) →contracts/events.md. Fillinfo.descriptionfromspec.md§1 (why this API exists). - Derive endpoints + schemas. One endpoint (or more) per §4 user story. Every request/response field traces to a
data-model.mdentity column — copy its constraints across (maxLength/pattern/enumfrom the model's bounded types). Never invent a field with no origin in any input — ask the user where it comes from.$refevery shared schema; no inline duplication. Lists paginate by cursor (?after=&before=&limit=), wrapped in{items, has_next, has_prev, next_cursor}. - Derive error responses from the sequences. Each endpoint covered by a §6 flow: turn every
alt … else … endbranch into aresponsesentry. The error body is the unified envelope{code, message, details?};codefollows the neutral conventionmodule.error_name(snake_case, e.g.lesson.not_owned,lesson.invalid_state) — a naming rule, not a language artifact. Map status by class (4xx client / 5xx server). This closes the spec's usual blind spot — §5 lists the happy path + a couple of errors; the sequences enumerate the authorization and concurrent-state branches the spec omits. - Async + idempotency. A mutating endpoint whose §6 flow shows a retry note or an async actor is marked
Idempotency-Key-required (state the TTL). For each async message, fill anevents.mdentry: event namemodule.action.vN, payload schema, producer, consumers, retry / dead-letter behaviour. - Examples + placeholder data. Every operation carries a request example + a success example + an error example, using placeholder values only (
@example.test,+380 00 000 00 00,Test User) — never real PII. - Inline DRIFT CHECK (bidirectional) + write the report. Compare the generated contract against the read artifacts and write
docs/features//contracts/api-sync-report.md— see [./references/drift-check.md](./references/drift-check.md). It has a field-origins table (one row peroperation.field:path | origin | confidence) and a checklist. The check runs both directions:
- forward (contract derived correctly): endpoint↔model, error-code↔repo, validation↔constraint, OpenAPI↔sequence.
- back-feed (coverage cross-check): every
spec.md§5 AC maps to ≥1 operation/response; every operation maps to a §4 user story + ≥1 AC; everysad.md§6alt-branch has a response, and any error/authorization response the contract needs but no §6 flow shows is a sequence gap. A gap here is not an api bug — it's a hole upstream: surface it and resolve it as Save-as-OQ with the upstream stage as owner — the OQ row names the producing stage as owner (specifyfor a missing AC,sequencesfor a missing branch) with due «before the contract is finalized», so the source gets fixed through the standard 4-state machine, not a fifth action.
A core finding failing (or ≥3 flags total) pauses the run — resolve each via the shared 4-state actions ([../_shared/ask-style.md](../_shared/ask-style.md)): Accept / Fix (the contract) / Save-as-OQ / Drop. A fix that belongs upstream (the spec's AC, the sequence) is the Save-as-OQ variant with the upstream stage as owner (see step 7's back-feed) — never a fifth action. Never silently edit the sources — surface the mismatch and let the human pick the right artifact (the contract, the spec's AC, or the sequence).
- Lint + write + commit. Suggest
spectral lint contracts/openapi.yaml(add it to the project's check target if not yet wired). On a clean check, the files are written; propose commitapi: contract. Then emit the stage-handoff block per [../_shared/handoff.md](../_shared/handoff.md) — What I did + Review (contracts/openapi.yaml,api-sync-report.md, +events.mdif async) + Run next (/clear, then/sdd:tasks).
Reconcile mode
/sdd:api --reconcile. Re-derives after an upstream artifact changed (typically data-model.md arrived or was tightened after a thinner first pass). It re-reads inputs, tightens loose types where the model now has a constraint, refreshes the field-origins confidence column, and — the load-bearing part — surfaces any field that had an inferred origin but now disagrees with the model. That disagreement is real drift, not stale incompleteness. info.version is never bumped silently; the user does that with a CHANGELOG line.
Definition of Done
docs/features//contracts/openapi.yamlwritten: OpenAPI 3.1,BearerAuthglobal with public endpoints declaring explicitsecurity: [], every error response the{code, message, details?}envelope, every operation with examples, all shared types via$ref.api-sync-report.mdwritten alongside: field-origins table + the 4-point drift checklist, every core finding ✓ or explicitly resolved with the user.- Every endpoint maps to a §4 user story; every field traces to a
data-model.mdcolumn; every errorcodeexists in the repo's error definitions (checked in the form the repo uses). contracts/events.mdpresent iff the feature has async flows; each event has a payload schema, producer, consumers, retry / DLQ note.
Anti-patterns
- Contract written by hand, then the model/sequences bent to fit it. The arrow is one-way: model + sequences + spec → contract.
- Skipping the drift check because "it was just generated, of course it matches". Generation can match the spec-as-read while diverging from the model or the sequences — different files, different authors. A clean 4/4 ✓ is cheap; a silent ✗ in prod is not.
- Error responses from the spec only. §5 lists happy + a couple of errors; the §6 sequences hold the authorization and concurrent-state branches. Skipping them leaves blind spots.
- Inventing a field with no origin in any input, or silently dropping one that left
data-model.md(keep it with a# stalenote and surface it — the human decides). - Stack-specific schema or error names. Schemas use the domain language from
data-model.md; error codes are the neutralmodule.error_nameconvention — not a Go/TS/Python idiom and not tied to any driver's error type. - Free-text errors (
{"error": "failed"}),?v=2query versioning,nullable: true(3.0 style — usetype: [string, null]), offset pagination, or real PII in examples. - Re-deriving the interface kind when
designalready declared it.target_surfacesinsad.mdis the primary signal — read it; the architecture-map derivation is the fallback only when the SAD/field is absent (greenfield). Silently re-inferring HTTP-vs-events on every run is the double-derivation this skill's surface-awareness removes.
References & template
- [
../_shared/ask-style.md](../_shared/ask-style.md) — canonical question/option phrasing for the drift-resolution dialog (step 7). - [
../_shared/size-matrix.md](../_shared/size-matrix.md) — MVP (one resource, events only if async) vs Full surface depth. - [
../_shared/surfaces.md](../_shared/surfaces.md) — the declaredtarget_surfaces(read fromsad.md) pick the contract form; this skill reads, never re-derives. - [
./references/drift-check.md](./references/drift-check.md) — the field-origins table + 4-point drift checklist, reconcile semantics, conflict table. - [
./templates/openapi.yaml](./templates/openapi.yaml) — OpenAPI 3.1 scaffold:BearerAuth, cursor page wrapper,{code, message, details?}Error schema. - [
./templates/events.md](./templates/events.md) — async event-contract scaffold (producer / consumers / payload / retry / DLQ).
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: genkovich
- Source: genkovich/sdd
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.