Writing an SDD

An SDD is a one-page design you write before coding an architecture-led task (see Ways of Working).

It exists to make decisions visible and approved — not to describe code.

Seed it from deploy/templates/SDD.md.

Small bug / one rule? Skip it — that’s TDD.

sdd-lifecycle

Anatomy — what each section must answer

Section Answers Keep it to

Goal

the one outcome, why now

1–2 sentences

Scope

in / out (the "out" prevents creep)

bullets

Affected components

which modules/packages (from COMPONENT-MAP.md); new role sub-packages

a short list

Design + alternatives

the chosen approach and the options you rejected (+ why)

a paragraph + 2 bullets

Data & DB

DTOs/records, tables/columns (snake_case, no FK, indexes, audit cols), migration

only what changes

FE↔BE contract

envelope / error codes / verb+path / identity — or "none"

the delta only

Protocol

TR-069/181/369 / LwM2M paths validated vs spec — or "N/A"

the paths touched

Logging & metrics

deviceId/sessionId key=value; levels; any metric

1–3 lines

Test plan

unit *Test + integration *IT (real infra), ≥80% diff coverage, the behaviours to assert

bullets

Risks / rollout

migration/back-compat risk, what’s deferred

bullets

Do / don’t

✅ Do ❌ Don’t
  • Decide — name the approach and the rejected alternatives.

  • Stay on one page; link the component map / contract instead of restating them.

  • Make the test plan concrete (the asserts, the real infra).

  • Get approval before code — that’s the whole point.

  • Paste pseudo-code or a class-by-class design (that’s implementation).

  • Leave "TBD" in Scope or Design — resolve it or it’s not ready.

  • Re-document the standard (the agent already enforces it).

  • Write an SDD for a bug fix or a one-line change.

Live example — a complete, approved SDD

# SDD: Bulk-disable CPEs by serial — ft-device-network-service
Status: APPROVED (2026-06-26, CTO)   ·   Task grade: architecture-led   ·   Ticket: OPS-412

## 1. Problem & goal
During an incident, ops must disable many CPEs fast. Today it's one-by-one in the UI. Goal: one call
that disables a list of serials and reports which were unknown.

## 2. Scope
- In: REST endpoint + service + the disable path through CpeWriteService.
- Out: UI, scheduling/automation, re-enable (separate ticket).

## 3. Affected components
- network.cpe.rest.CpeController (new action) · network.cpe.service.CpeWriteService (reuse)
- new request record in network.cpe.rest (role sub-package)

## 4. Design / approach
Add a northbound REST action `PUT api/Cpe/disable` taking a serial list; fan out through the existing
CpeWriteService batch path. Chosen over: (a) per-serial loop in the controller — rejected, N round-trips;
(b) a new async job — rejected, ops needs a synchronous result now.

## 5. Data & DB
No schema change. Audit cols already present. Batch the write at the J-DB relational tier (~1000).

## 6. FE↔BE contract
External REST (StatusResponse). No internal UI↔BE change.

## 7. Protocol
N/A (no parameter-path changes).

## 8. Logging & metrics
log per device: deviceId=<serial> action=disable result=...; counter cpe.disable.count.

## 9. Test plan
- should_disable_all_when_serials_valid (*IT, emulator)
- should_report_unknown_serials_when_some_missing (*IT)
- should_cap_when_list_exceeds_batch_limit (*Test)

## 10. Risks / rollout
Large lists → cap at 1000 with a clear 4xx. No migration; safe to ship behind the existing ops role.
Template → deploy/templates/SDD.md. Back to Ways of Working · Overview.