Chapter 6: Spec-as-Source Development
Learning Objectives
By the end of this chapter, you will be able to:
- Define spec-as-source development and explain how it differs from spec-first and spec-anchored
- Identify domains where spec-as-source is already standard practice
- Evaluate when spec-as-source is viable for software projects
- Practice a constrained version of spec-as-source by generating a complete feature from specifications alone
- Articulate the prerequisites, benefits, and risks of full spec-as-source adoption
The Final Step: Humans Edit Specs, Machines Generate Everything
In spec-first development (Chapter 4), humans write specifications and then write code guided by those specifications. In spec-anchored development (Chapter 5), humans write specifications and code, with automated tools ensuring they stay aligned.
Spec-as-source development takes the final step:
Humans edit only specifications. Machines generate all code.
Developers never manually edit generated code. They never open a source file and change a function. If the system needs to change, the specification changes, and the code is regenerated.
Spec-as-Source Workflow:
Human Intent
↓
Specification (human edits this)
↓
Code Generation (AI generates this)
↓
Validation (automated, against spec)
↓
Deployment (if validation passes)
Need a change?
↓
Edit Specification (never edit code)
↓
Regenerate (code is replaced, not patched)
This sounds radical for application development. But it's already standard practice in several domains.
Where Spec-as-Source Already Exists
Automotive Systems (MATLAB/Simulink)
Automotive engineers define vehicle control systems as mathematical models in Simulink. These models are the specification. C code for embedded controllers is generated directly from the models.
Engineers never write C code. They modify the Simulink model, regenerate the C code, and validate it against safety requirements. This workflow is mandated by safety standards (ISO 26262) because hand-written code in safety-critical systems introduces unacceptable risk.
Hardware Design (VHDL/Verilog)
Hardware engineers write specifications in Hardware Description Languages. These specifications define the behavior of circuits — inputs, outputs, timing, state machines. The actual physical layout (gates, wires, routing) is generated by synthesis tools.
No hardware engineer manually places transistors. The specification defines behavior; tools generate the implementation.
API Client Generation
If you've ever used an OpenAPI code generator to produce client libraries, you've already practiced spec-as-source:
# Specification → Generated code
openapi-generator generate \
-i api-spec.yaml \
-g typescript-axios \
-o src/api-client/
# The generated code is NEVER manually edited
# If the API changes, you update the spec and regenerate
The generated API client is disposable. When the API changes, you don't patch the client — you update the specification and regenerate.
Database Migrations
Modern ORM tools generate database migrations from schema definitions:
# Specification (the model)
class User(Base):
__tablename__ = "users"
id = Column(UUID, primary_key=True)
email = Column(String, unique=True, nullable=False)
name = Column(String(100), nullable=False)
created_at = Column(DateTime, server_default=func.now())
# Generated (the migration) — never manually edited
# alembic revision --autogenerate -m "create users table"
The model is the specification. The migration is the generated artifact.
Extending Spec-as-Source to Application Code
The examples above show that spec-as-source is already proven for specific domains. The question for SDD practitioners is: can this model extend to entire application codebases?
What Makes Spec-as-Source Viable
Three conditions must be met:
1. Specification completeness: The specification must capture 100% of the system's behavior. There can be no implicit requirements that exist only in the minds of developers. For API clients, this is achievable because the OpenAPI spec fully defines the interface. For complex business logic, this requires significantly more specification effort.
2. Generation reliability: The code generator must produce correct, production-quality code from the specification. For SQL migrations from models, this is well-established. For complex application logic, AI reliability is rapidly improving but not yet at 100%.
3. Validation coverage: Automated validation must verify that the generated code matches the specification. If validation misses a case, a generation error becomes a production bug.
The Viability Spectrum
| Domain | Spec Completeness | Generation Reliability | Validation Coverage | Spec-as-Source Viable? |
|---|---|---|---|---|
| API clients | High (OpenAPI is complete) | High (well-established generators) | High (contract tests) | Yes, standard practice |
| Database schemas | High (models fully define structure) | High (ORM generators are mature) | High (migration validation) | Yes, standard practice |
| CRUD endpoints | High (data model + API contract fully define behavior) | High (templatable patterns) | High (contract + integration tests) | Yes, practical today |
| Business logic | Medium (complex rules need careful specification) | Medium (AI improving rapidly) | Medium (requires comprehensive BDD) | Partially, with human review |
| UI components | Low (visual design hard to fully specify) | Low (aesthetic quality is subjective) | Low (visual regression is imprecise) | Not yet for complex UIs |
The Hybrid Approach
Most teams will practice a hybrid approach:
Full Spec-as-Source:
- API contracts → generated client/server stubs
- Database models → generated migrations
- Configuration → generated infrastructure code
- CRUD operations → generated from data model + API spec
Spec-Anchored (human edits code, validated against spec):
- Complex business logic
- Performance-critical paths
- Custom UI components
- Third-party integrations with edge cases
The boundary between "generated" and "human-edited" shifts over time as AI capabilities improve and specification languages become more expressive.
Tutorial: Full Spec-as-Source for a CRUD Feature
Let's practice the full spec-as-source workflow for a feature that's well-suited to it: a CRUD API for managing projects.
Step 1: Write Complete Specifications
Create three specification files:
Data Model Specification (spec-project-model.md):
# Data Model: Project
## Fields
| Field | Type | Constraints | Default |
|-------|------|-------------|---------|
| id | UUID | Primary key | Auto-generated |
| name | String(200) | Required, unique per owner | None |
| description | Text | Optional, max 5000 chars | NULL |
| owner_id | UUID | Foreign key → users.id, required | None |
| status | Enum | "active", "archived", "deleted" | "active" |
| created_at | Timestamp | Auto-set on creation | NOW() |
| updated_at | Timestamp | Auto-set on modification | NOW() |
## Indexes
- Unique: (owner_id, name) — prevents duplicate project names per user
- Index: (owner_id, status, created_at DESC) — supports listing query
## Cascade Rules
- When owner is deleted → soft-delete all owner's projects
API Contract Specification (spec-project-api.md):
# API: Projects
## Endpoints
### POST /api/projects
- Auth: Required (JWT)
- Body: { name: string, description?: string }
- Success: 201 { project object }
- Errors: 400 (validation), 401 (no auth), 409 (name conflict)
### GET /api/projects
- Auth: Required (JWT)
- Query: status=active|archived (default: active), page, per_page
- Success: 200 { items: [], total: number, page: number }
- Scoping: Returns only the authenticated user's projects
### GET /api/projects/:id
- Auth: Required (JWT)
- Success: 200 { project object }
- Errors: 401, 403 (not owner), 404
### PATCH /api/projects/:id
- Auth: Required (JWT, must be owner)
- Body: { name?: string, description?: string }
- Success: 200 { updated project object }
- Errors: 400, 401, 403, 404, 409 (name conflict)
### DELETE /api/projects/:id
- Auth: Required (JWT, must be owner)
- Behavior: Soft delete (sets status to "deleted")
- Success: 204 No Content
- Errors: 401, 403, 404
Behavioral Specification (spec-project-behavior.feature):
Feature: Project Management
Users can create, view, update, and delete projects
Scenario: Create a project
Given I am authenticated as "alice"
When I create a project with name "My Project"
Then the response status is 201
And the project has status "active"
Scenario: Cannot create duplicate project name
Given I am authenticated as "alice"
And I have a project named "My Project"
When I create a project with name "My Project"
Then the response status is 409
Scenario: List only my projects
Given I am authenticated as "alice"
And "alice" has 3 active projects
And "bob" has 2 active projects
When I list projects
Then I see exactly 3 projects
Scenario: Soft delete a project
Given I am authenticated as "alice"
And I have a project "To Delete"
When I delete the project "To Delete"
Then the response status is 204
And the project "To Delete" has status "deleted"
And the project "To Delete" does not appear in my project list
Step 2: Generate Everything from Specs
Give all three specification files to your AI assistant with this directive:
"Generate the complete implementation from these three specifications. I will not edit the generated code. Generate: database migration, model, API routes, validation, tests (unit + integration + contract), and any supporting utilities. Every behavior must be traceable to a specification. Do not add any behavior not specified."
Step 3: Validate Without Editing
Run the generated code against all three specifications:
- Schema validation: Does the database match the data model spec?
- Contract tests: Does the API match the API contract spec?
- BDD tests: Does the behavior match the behavioral spec?
Step 4: Evolve by Editing Specs Only
Now add a requirement: projects should have a color field (optional, hex code, default #3B82F6).
Update only the specifications:
- Add the field to the data model spec
- Add it to the API contract (accepted in POST and PATCH, returned in GET)
- Add a BDD scenario for color validation
Then regenerate everything and validate.
The key discipline: at no point did you open a source file and edit code. You edited specifications and regenerated.
The Risks and Mitigations
Risk: Specification Gaps Become Production Bugs
If a behavior isn't specified, it won't be generated. In human-edited code, developers often add safety checks, input sanitization, and error handling that wasn't explicitly requested. In spec-as-source, if it's not in the spec, it's not in the code.
Mitigation: Specification templates and checklists that prompt for common concerns (security, error handling, edge cases, observability).
Risk: Generation Quality Varies
AI code generation is not deterministic. The same specification may produce slightly different code on different runs. Subtle quality differences can affect performance, security, or maintainability.
Mitigation: Comprehensive automated validation (contract tests, BDD, performance benchmarks, security scans) that catch quality regressions regardless of how the code was generated.
Risk: Debugging Generated Code Is Harder
When a bug appears in generated code, the developer must understand both the specification and the generation process to diagnose the issue. This requires a different debugging skill set than traditional code debugging.
Mitigation: Fix bugs by fixing specifications and regenerating, not by patching code. This ensures the fix is permanent and won't be overwritten by the next generation cycle.
Try With AI
Prompt 1: Full Generation Challenge
"I'm going to give you three specification files for a feature (data model, API contract, BDD scenarios). Generate the complete implementation without me editing any generated code. After generation, I'll run validation against all three specs. Let's see how close to 100% spec-compliant you can get on the first generation."
Prompt 2: The Regeneration Test
"Here is a specification-generated codebase. I've updated the specification to add a new field and one new endpoint. Regenerate the entire implementation from the updated specification. The regenerated code must pass all existing tests plus new tests for the added functionality. Do not carry forward any code from the previous generation — start fresh."
Prompt 3: Viability Assessment
"I'm going to describe a feature I want to build. Help me assess whether it's suitable for spec-as-source development. Score it on: specification completeness (can I fully specify the behavior?), generation reliability (can AI reliably implement it?), and validation coverage (can I automatically verify correctness?). Based on the scores, recommend which parts should be spec-as-source and which should be spec-anchored."
Practice Exercises
Exercise 1: Identify Spec-as-Source Candidates
Review your current project and list every component. Categorize each as:
- Already spec-as-source (generated from specs, never hand-edited)
- Candidate for spec-as-source (fully specifiable, generation is reliable)
- Requires human editing (complex logic, subjective quality, or poor generation reliability)
Expected outcome: You'll find that 30-50% of most codebases consists of components that could be generated from specifications.
Exercise 2: The No-Edit Challenge
Build a small feature using the spec-as-source discipline: write specifications, generate code, validate, but never open a source file to edit it. If something is wrong, fix the specification and regenerate.
Expected outcome: The experience reveals which specification gaps lead to code problems, training you to write more complete specifications.
Exercise 3: Specification Language Comparison
Take a single feature and write its specification in three formats: (1) natural language Markdown, (2) OpenAPI YAML, (3) BDD Gherkin. Compare which format captures which aspects best.
Expected outcome: Each format has strengths — Markdown for intent and context, OpenAPI for API surface, Gherkin for behavior — reinforcing the multi-pillar approach.
Key Takeaways
-
Spec-as-source development is the most advanced form of SDD: humans edit only specifications, machines generate all code.
-
Spec-as-source is already standard practice in automotive systems, hardware design, API client generation, and database migrations.
-
Three conditions determine viability: specification completeness, generation reliability, and validation coverage.
-
Most teams will use a hybrid approach — spec-as-source for well-defined components (CRUD, schemas, clients) and spec-anchored for complex logic.
-
The boundary between generated and human-edited code shifts over time as AI capabilities improve.
-
In spec-as-source, bugs are fixed by fixing specifications and regenerating, not by patching code.
Chapter Quiz
-
What is the defining characteristic of spec-as-source development?
-
Name three domains where spec-as-source is already standard practice.
-
What are the three conditions that make spec-as-source viable?
-
Why is a hybrid approach (spec-as-source for some components, spec-anchored for others) the most practical strategy?
-
How does debugging differ in spec-as-source vs. traditional development?
-
What risk emerges when specifications have gaps in a spec-as-source workflow?
-
A team generates 100% of their API code from OpenAPI specs. Is this spec-as-source? Why or why not?
-
How does the spec-as-source boundary change over time?