• By
  • 15 min read

Specification-Driven Design: From Intent to Verifiable Software

Specification-driven design is a way to design software by turning intent into an explicit, reviewable contract before implementation becomes expensive. The specification describes the outcome, behavior, boundaries, and proof of correctness; the code is one implementation of that contract.

The goal is not to predict every detail up front. It is to make important decisions visible early, connect them to evidence, and keep the specification current as the team learns.

A useful specification tells a designer what must be understandable, a developer what must be true, and a reviewer how to prove it.

This article uses a fictional expense-approval system as a worked example. Its five Mermaid diagrams were parsed and rendered locally with Mermaid 11.16.0. The example demonstrates a design method rather than a complete production specification.

The idea in one diagram

flowchart LR
    accTitle: Specification-driven design feedback loop
    accDescr: Product intent becomes measurable outcomes, requirements, design, implementation, and evidence. Failed verification revises the requirements, while production learning feeds back into the outcomes.

    intent["Product intent"] --> outcomes["Measurable outcomes"]
    outcomes --> requirements["Requirements and constraints"]
    requirements --> design["Behavior and system design"]
    design --> implementation["Implementation"]
    implementation --> evidence["Tests, observations, and metrics"]
    evidence --> decision{"Meets the specification?"}
    decision -->|Yes| release["Release and monitor"]
    decision -->|No| revise["Revise design, code, or assumptions"]
    revise --> requirements
    release --> learning["Production learning"]
    learning --> outcomes

This is a feedback loop, not a one-way handoff. A failed test can reveal an implementation defect, a weak design, or an incorrect requirement. The team updates the right layer instead of quietly changing the code until a test passes.

What makes a design specification useful

A useful specification has five qualities:

  1. Outcome-oriented: it explains the user or business result, not only the requested interface.
  2. Unambiguous: important terms, states, thresholds, and responsibilities have one intended meaning.
  3. Verifiable: every mandatory statement has a practical way to demonstrate that it is true.
  4. Traceable: designs, tasks, tests, and decisions point back to named requirements.
  5. Changeable: the document is structured so one decision can change without rewriting everything.

The word specification does not mean a hundred-page document. A small change may need one page containing a problem statement, three requirements, a state diagram, and acceptance tests. A regulated or distributed system may need separate product, interface, data, security, and operational specifications.

Specification-driven is not waterfall

Both approaches may create documents before code, but the resemblance ends there. A specification-driven team can work in short iterations:

  • Specify the smallest coherent behavior.
  • Review risky assumptions before building.
  • Implement a thin vertical slice.
  • Collect test and user evidence.
  • Amend the specification when learning changes the design.

The specification remains the current agreement. It is not an abandoned requirements document whose inaccuracies everyone works around.

Build a chain from intent to evidence

Do not jump directly from a broad request to components or tickets. Decompose the request into layers, giving each layer a different job.

flowchart TD
    accTitle: Traceability from intent to evidence
    accDescr: The goal of reducing expense approval time becomes one measurable outcome, three requirements, corresponding design elements, and verification activities.

    vision["Intent: reduce the time employees spend waiting for expense decisions"]
    outcome["Outcome: 90% of complete requests receive a decision within one business day"]

    vision --> outcome
    outcome --> r1["REQ-01: an employee can submit a complete expense request"]
    outcome --> r2["REQ-02: requests below the policy threshold can be approved automatically"]
    outcome --> r3["REQ-03: every decision has an auditable reason"]

    r1 --> ux["UX states and validation"]
    r2 --> policy["Policy rule and approval flow"]
    r3 --> audit["Decision record and retention"]

    ux --> t1["Acceptance and accessibility tests"]
    policy --> t2["Rule boundary tests"]
    audit --> t3["Audit-record tests"]

The layers answer different questions:

LayerQuestionExample
IntentWhy are we changing the system?Reduce approval delays
OutcomeWhat observable result would count as success?90% decided within one business day
RequirementWhat must be true?A complete request can be submitted
ConstraintWhat limit cannot the design violate?Only finance administrators can change policy thresholds
DesignHow will users and systems satisfy it?Validate the form, evaluate policy, then route or approve
EvidenceHow will we know it works?Boundary tests, audit inspection, and lead-time metrics

This chain prevents two common errors: implementing a feature with no measurable value, and measuring an outcome that the design cannot influence.

A practical specification-driven workflow

1. Frame the problem and its boundary

Begin with a short problem brief. Name the affected users, the current failure, the desired outcome, and what is explicitly out of scope.

For example:

Employees currently ask for expense approval in chat, so requests are incomplete and decisions cannot be audited. This change introduces a structured submission and approval flow. Reimbursement and payroll remain out of scope.

Record facts separately from assumptions. “Policy requires an audit record” may be a confirmed constraint; “most requests are below $1,000” is an assumption until data supports it.

2. Name the actors and vocabulary

Create a small glossary before writing detailed rules. If requester, approver, finance reviewer, and administrator are different roles, use those names consistently. Define domain terms such as complete request, policy threshold, and decision.

Vocabulary is part of the design. Two teams using the same word for different states will build incompatible behavior even if their diagrams look aligned.

3. Write testable requirements

Give each requirement a stable identifier and use normative language deliberately:

  • Must or shall: mandatory and tested before release.
  • Should: expected unless a documented reason prevents it.
  • May: permitted but optional.

Prefer one obligation per statement:

REQ-07: The system shall record the policy version used for every automated decision.

Avoid combining behavior, implementation, and motivation in one sentence. Put the reason in a rationale field and the implementation in the design section. This keeps the requirement stable when the technical approach changes.

4. Describe behavior with examples

Examples remove ambiguity faster than adjectives such as “fast,” “simple,” or “secure.” Use scenarios for the main path, boundary values, failures, retries, and permissions.

Scenario: A request exactly at the automatic-approval threshold
  Given the active policy threshold is $1,000
  And an employee has completed all required fields
  When the employee submits a request totaling $1,000
  Then the request is routed to finance review
  And no automatic approval is recorded

That example resolves whether the rule is < 1000 or <= 1000. A prose statement like “large expenses need review” does not.

5. Model states and transitions

State models expose missing behavior: who can cancel, what happens after rejection, whether a failed notification changes the business state, and which transitions are irreversible.

stateDiagram-v2
    accTitle: Expense request states and transitions
    accDescr: An expense request moves from draft to submitted, then to automatic approval or finance review. Finance review can approve, reject, or cancel the request.

    [*] --> Draft
    Draft --> Submitted : submit [complete]
    Draft --> Cancelled : cancel
    Submitted --> AutoApproved : below threshold
    Submitted --> InReview : at or above threshold
    InReview --> Approved : approve
    InReview --> Rejected : reject with reason
    InReview --> Cancelled : requester withdraws
    AutoApproved --> [*]
    Approved --> [*]
    Rejected --> [*]
    Cancelled --> [*]

For every transition, specify:

  • the actor or event that triggers it;
  • preconditions and authorization;
  • state and data changes;
  • side effects, such as notifications;
  • behavior on retries or partial failure;
  • the evidence recorded.

6. Design the interaction across boundaries

A sequence diagram makes ownership and failure points visible. It complements the state model: the state model says which transitions are legal, while the sequence says how collaborators produce them.

sequenceDiagram
    accTitle: Expense submission interaction
    accDescr: An employee submits an expense request. The API evaluates policy and records either an automatic approval or a route to finance review in the audit store.

    autonumber
    actor Employee
    participant UI as Expense UI
    participant API as Expense API
    participant Policy as Policy service
    participant Audit as Audit store

    Employee->>UI: Submit completed request
    UI->>API: POST request with idempotency key
    API->>Policy: Evaluate amount and employee policy
    Policy-->>API: Decision and policy version

    alt Below automatic-approval threshold
        API->>Audit: Record approval and rationale
        API-->>UI: Approved
    else Review required
        API->>Audit: Record routing and rationale
        API-->>UI: Pending finance review
    end

This design implies questions the prose must answer. What happens if the audit store is unavailable? Can the API return success before the audit record exists? How long is the idempotency key retained? A diagram surfaces the questions; the specification records the decisions.

7. Specify interfaces and data contracts

For each system boundary, define the contract at the level needed for independent implementation:

  • request and response fields, types, and units;
  • required, optional, and conditionally required values;
  • authentication and authorization;
  • validation and error semantics;
  • idempotency, ordering, and retry behavior;
  • versioning and compatibility;
  • privacy classification and retention;
  • observable signals such as logs, metrics, and traces.

Include examples, but do not let an example become the only definition. A JSON example cannot by itself show maximum length, permitted null values, or whether an omitted field differs from an empty field.

8. Cover quality attributes with measurable targets

“The page must be fast” and “the service must be secure” are aspirations, not specifications. Add conditions and thresholds:

Quality attributeVerifiable form
PerformanceThe submit endpoint shall complete within 500 ms at the 95th percentile under 50 requests per second, excluding the documented audit-store outage path.
AvailabilityMonthly successful request availability shall be at least 99.9%, measured at the public API boundary.
AccessibilityThe submission flow shall be usable with keyboard-only navigation and shall expose programmatic names and validation errors.
SecurityA requester shall not read another employee's request unless granted the finance-reviewer role.
RecoveryRetrying the same submission with the same idempotency key shall not create another request.

A threshold without a workload, measurement point, or time window is still ambiguous.

9. Connect each requirement to proof

Maintain a lightweight traceability table. This can live in the specification for a small feature or be generated from issue and test metadata for a larger program.

RequirementDesign elementVerificationStatus
REQ-01 Submit a complete requestSubmission form and POST /requestsAC-01 happy path; AC-02 validationDraft
REQ-02 Apply the approval thresholdPolicy evaluation and state transitionsAC-03 below; AC-04 equal; AC-05 aboveDraft
REQ-03 Preserve an auditable reasonDecision record with policy versionAC-06 record schema; AC-07 failure behaviorDraft
NFR-01 Prevent duplicate requestsIdempotency-key contractAC-08 retry and concurrency testDraft

Traceability is valuable when it helps answer impact questions. It should not become a ritual of linking every paragraph to every ticket.

10. Plan vertical implementation slices

Turn the accepted design into slices that deliver observable behavior. A useful order for the example might be:

  1. Submit and store a valid draft request.
  2. Reject incomplete or unauthorized submissions.
  3. Route all submissions to manual review.
  4. Add policy evaluation and automatic approval.
  5. Add notifications and operational dashboards.

Each slice should cite its requirements and acceptance examples. Technical tasks can sit beneath the slice, but completing all tasks is not the same as satisfying the acceptance criteria.

11. Govern changes explicitly

Specifications change. Require a small impact check when a requirement, contract, or state transition changes.

flowchart LR
    accTitle: Specification change impact review
    accDescr: A proposed change is traced to affected requirements, then assessed across user experience, interfaces, data, operations, and tests before acceptance or rejection.

    proposal["Change proposal"] --> requirements["Affected requirements"]
    requirements --> ux["User experience"]
    requirements --> api["Interfaces"]
    requirements --> data["Data and migration"]
    requirements --> operations["Operations and support"]
    requirements --> tests["Tests and evidence"]

    ux --> decision{"Accept the change?"}
    api --> decision
    data --> decision
    operations --> decision
    tests --> decision

    decision -->|Yes| version["Version the specification and contracts"]
    decision -->|No| record["Record the reason"]

Do not erase a consequential decision. Use a short decision record containing the context, chosen option, alternatives, consequences, date, and owner. The current specification should show the chosen behavior; the decision record should explain why it changed.

A reusable Markdown specification template

Copy this template and remove sections that do not improve the decision or its verification.

# Feature: [Name]

**Status:** Draft | In review | Approved | Implemented | Retired
**Owner:** [Name or team]
**Reviewers:** [Product, design, engineering, security, operations]
**Last updated:** YYYY-MM-DD

## 1. Summary

[One paragraph describing the change and its value.]

## 2. Problem and outcome

- **Current problem:**
- **Target users:**
- **Desired outcome:**
- **Success measure:**
- **Non-goals:**

## 3. Vocabulary and actors

| Term or actor | Definition or responsibility |
| ------------- | ---------------------------- |
|               |                              |

## 4. Assumptions, constraints, and dependencies

| ID     | Type       | Statement | Evidence or owner |
| ------ | ---------- | --------- | ----------------- |
| ASM-01 | Assumption |           |                   |
| CON-01 | Constraint |           |                   |

## 5. Requirements

| ID     | Requirement         | Priority | Rationale |
| ------ | ------------------- | -------- | --------- |
| REQ-01 | The system shall... | Must     |           |

## 6. User experience and behavior

### Primary flow

[Steps or a Mermaid flowchart.]

### States and transitions

[State table or Mermaid state diagram.]

### Failure and recovery behavior

[Validation, permissions, timeouts, retries, partial failures, cancellation.]

## 7. Interfaces and data

[Schemas, validation, errors, compatibility, retention, migration.]

## 8. Quality attributes

[Measurable performance, reliability, accessibility, security, privacy,
observability, and recovery requirements.]

## 9. Acceptance examples

```gherkin
Scenario: [Boundary or behavior]
  Given ...
  When ...
  Then ...
```

## 10. Traceability

| Requirement | Design | Verification | Result  |
| ----------- | ------ | ------------ | ------- |
| REQ-01      |        |              | Not run |

## 11. Rollout and rollback

[Feature flags, migration order, monitoring, abort conditions, rollback limits.]

## 12. Open questions

| Question | Owner | Due date | Resolution |
| -------- | ----- | -------- | ---------- |
|          |       |          |            |

## 13. Decisions and change history

| Date | Decision or change | Reason | Approver |
| ---- | ------------------ | ------ | -------- |
|      |                    |        |          |

Review the specification from several viewpoints

A single general review tends to approve readable prose while missing system behavior. Use focused review passes:

  • Product: Does the design produce the intended outcome? Are non-goals clear?
  • UX and accessibility: Are all states, errors, permissions, and recovery paths understandable and operable?
  • Engineering: Are boundaries, invariants, concurrency, compatibility, and failure semantics defined?
  • Quality: Can every mandatory requirement be verified? Are boundary and negative cases covered?
  • Security and privacy: Are access, misuse, sensitive data, retention, and audit needs addressed?
  • Operations: Can the team detect failure, diagnose it, recover safely, and support users?

Ask reviewers to mark statements as accepted, ambiguous, unsupported, or not verifiable. That is more actionable than collecting undifferentiated comments.

Definition of ready and definition of done

A specification is ready for implementation when:

  • the problem, desired outcome, and non-goals are understood;
  • mandatory requirements have stable identifiers;
  • primary, boundary, and failure behavior is defined;
  • interfaces and ownership are clear enough for the first slice;
  • security, privacy, accessibility, and operational risks have owners;
  • acceptance evidence is defined;
  • unresolved questions are either answered or explicitly safe to defer.

The feature is done when:

  • required behavior is implemented and linked to the specification;
  • acceptance and quality checks pass with retained evidence;
  • observability and support procedures exist;
  • migration, rollout, and rollback conditions have been exercised where practical;
  • the specification reflects the released behavior;
  • any accepted deviation is documented, owned, and time-bounded.

Common failure modes

Writing an implementation recipe instead of a requirement

“Use Redis to make submission fast” hides the actual need. Specify the latency and consistency behavior first; select Redis only if the design evidence supports it.

Describing only the happy path

Real design lives in permissions, empty states, boundary values, retries, concurrency, partial failure, cancellation, and recovery. Review these before polishing the primary flow.

Using diagrams as decoration

A diagram without IDs, decisions, or supporting prose may look authoritative while remaining ambiguous. Give important nodes and transitions stable names, then connect them to requirements and tests.

Treating the specification as immutable

New evidence should change weak assumptions. Version consequential contracts and keep decision history, but do not protect an incorrect design merely because it was approved.

Letting code silently become the only truth

Code shows what the current implementation does, not necessarily why it does it or what all conforming implementations must do. When behavior changes deliberately, update the specification in the same change.

Requiring documentation with no review audience

Every section should support a decision, handoff, or proof. If nobody knows who uses a document or when, reduce or remove it.

Start small

For your next feature, try this minimum set:

  1. Write one paragraph for the problem and one measurable outcome.
  2. List five or fewer mandatory requirements with stable IDs.
  3. Draw the main flow or state model in Mermaid.
  4. Write one happy-path, one boundary, and one failure scenario.
  5. Link each requirement to a test or observation.
  6. Review the design with the people who build, operate, support, and use it.
  7. Update the specification alongside the implementation.

Specification-driven design works when the document becomes a compact interface between intent, design, implementation, and evidence. The measure of success is not how much was written. It is how few important decisions remain hidden—and how confidently the team can prove that the delivered system does what it agreed to do.

For the diagram-authoring side of this workflow, continue with the Mermaid chart tips and tricks guide. If you use AI to turn requirements into a first draft, the requirements-to-Mermaid guide shows how to keep each generated relationship tied to evidence. Teams documenting these specifications in Confluence can use Diagram Lens to keep Mermaid source editable beside the design rationale.

Diagram preview

Powered by Diagram Lens

Loading diagram viewer…