Specification as Source of Truth in Golang: A New SDD Way of Thinking
Understand specification as a single source of truth in SDD. How to build an effective spec governance system for Golang projects with Claude Code.
Specification as Source of Truth: A New Way of Thinking
In a software project that’s been running long enough, there’s one question that often can’t be answered with certainty: “How should the system behave in condition X?”
Ask developer A β the answer differs from developer B. Developer B checks the code, developer C looks at old Jira tickets, developer D checks a Confluence document that might be outdated. There’s no single place that serves as the definitive source of truth.
This is the problem that specification as source of truth solves in SDD. In this article we look at what it means operationally, why it’s harder than it appears, and how to build it correctly for a Golang project.
02.1 The Problem with “Code as Documentation”
There’s a popular belief among developers: “code is the best documentation.” The idea is that code can’t lie, it’s always up-to-date, it’s the ground truth. This belief is partially correct β but dangerous as a sole philosophy.
Code is the ground truth for what the system currently does. It is not the ground truth for what the system should do. That distinction is crucial, and the function below makes it visible.
1func (uc *CancelOrderUseCase) Execute(ctx context.Context, input CancelOrderInput) error {
2 order, err := uc.repo.GetByID(ctx, input.OrderID)
3 if err != nil {
4 return err
5 }
6 if order.Status != StatusPending {
7 return ErrCannotCancel // β why only PENDING? Who decided this?
8 }
9}The question in that comment β why only PENDING? β can’t be answered from code alone. It could be a business requirement, a temporary constraint that was forgotten, or a bug, and the code can’t tell you which. Specifications answer the “why,” code answers the “how.” Both are needed, but the spec must come first.
02.2 Three Source of Truth Candidates That Often Compete
In most teams, three candidates compete to be the “source of truth”:
Code: Always reflects current behavior and never lies β but doesn’t explain why, and can’t be reviewed before implementation.
Tickets (Jira, Linear): Available to all stakeholders and carry business context β but are often not updated after implementation and can be contradictory across tickets.
Documentation (Confluence, Notion): Can have good narrative and diagrams β but dies quickly, because nobody updates it after the feature ships, and it has no single owner.
SDD Solution: Spec files that are stored in the same repository as code (versioned with git), updated alongside code (enforced via the PR process), structured but readable, and with clear ownership.
02.3 Anatomy of an Effective Source of Truth
Not all specs can be effective sources of truth. The first characteristic is being specific but not too detailed β the comparison below shows the sweet spot between vague and over-engineered.
1β Too vague: "System must handle orders well"
2β Too detailed (implementation): "Use BEGIN TRANSACTION with READ COMMITTED..."
3β
Just right: "Only PENDING orders can be cancelled.
4 Other statuses β 409 Conflict with error code ORDER_NOT_CANCELLABLE"The “just right” line names the behavior and the failure mode without prescribing SQL β that’s the target for every AC you write. Three more characteristics complete the picture:
- Automatically verifiable β every AC must be writable as a test.
- Single ownership β one named person is responsible for its accuracy.
- Versioned with git β every change is tracked, with a reason.
Together these four properties are what separate a spec that teams actually trust from a document nobody reads.
02.4 Source of Truth in Practice: Common Conflicts
Consider a real scenario β a customer reports: “I can’t cancel my order even though I placed it 5 minutes ago.”
Without a spec, you get two hours of debate: developer A says one thing, developer B says another, QA says the docs are outdated, and there’s no clear resolution. With a spec as source of truth, the relevant AC settles the question immediately. The excerpt below is the single line the whole team can point to.
1# specs/order/cancel-order.md (v1.3, updated 2025-06-15)
2AC7: Customer can only cancel orders within 15 minutes of creation,
3 OR while status is still PENDING β whichever comes first.
4 After that β 409 with error CANCEL_WINDOW_EXPIRED.Now the question is precise: does the implementation match AC7 v1.3? If yes and the customer still complains, there’s data to investigate; if no, it’s a clear bug. The spec converts an open-ended argument into a verifiable check.
02.5 Decision Hierarchy in SDD
In SDD, there’s a clear hierarchy to fall back on when sources of truth conflict. The ranking below is the tiebreaker.
11. Approved specification β highest authority
22. Merged code review
33. Existing unit tests
44. Code running in production
55. Code comments β lowest authorityRead that ranking as a rule: if code contradicts an approved spec, the code is wrong β not the spec. The only exception is an explicit decision to revise the spec, which must go through the review process rather than sneak in through a code change.
02.6 Writing Spec Worthy of Being Source of Truth
The complete spec template we use throughout the series is shown below. Treat it as a fill-in-the-blanks skeleton for any new feature.
1# [Feature Name]
2# Version: [X.Y]
3# Owner: @[name]
4# Status: Draft | Review | Active | Deprecated
5# Created: [date]
6# Last Updated: [date]
7
8---
9
10## User Story
11As [who], I want [what] so that [why/benefit].
12
13## Scope
14Includes: [what's covered]
15Excludes: [what's intentionally NOT covered]
16
17## Acceptance Criteria
18
19### Happy Path
20- AC1: [first condition]
21- AC2: [second condition]
22
23### Error Cases
24- AC[n]: [error condition] β [HTTP status] with error_code [CODE]
25 message: "[user-facing error message]"
26
27## Edge Cases
28- EC1: [boundary/concurrent/unusual condition] β [expected behavior]
29
30## Non-Functional Requirements (remove if not applicable)
31- NFR1: Response time < [X]ms at p[Y]th percentile
32
33## Changelog
34| v | Date | Change | Reason | Author |
35|---|------|--------|--------|--------|
36| 1.0 | [date] | Initial | - | @[name] |The header block (Owner, Status, Last Updated) is what makes the spec auditable, and the Changelog is what makes it a living document rather than a snapshot. Keep both sections even for small features.
02.7 Source of Truth at Team Level: Governance
A spec can only be a source of truth if governance keeps it accurate. Without it, the spec will be outdated within a few sprints. The minimal effective governance is four practices:
- Spec review before implementation β no code is written for a new feature until the spec is reviewed by at least one other person.
- Spec update alongside code β if the implementation diverges from the spec for technical reasons, the spec is updated in the same PR.
- Quarterly spec audit β review all active specs every quarter.
- Spec in CLAUDE.md β instruct Claude Code to always reference specs before generating code (covered in Article 04).
02.8 Spec as Contract: Testing Implications
When the spec is the source of truth, the ideal test suite maps one-to-one onto it. The test file below is organized by spec section, so coverage is measured against business behavior rather than lines of code.
1// cancel_order_test.go β structure follows spec
2
3// Happy Path Tests (from AC Happy Path)
4func TestCancelOrder_PendingOrder_Success(t *testing.T) {} // AC1-AC6
5func TestCancelOrder_StockRestored_AfterCancel(t *testing.T) {} // AC5
6
7// Error Case Tests (from AC Error Cases)
8func TestCancelOrder_ConfirmedOrder_Returns409(t *testing.T) {} // AC9
9func TestCancelOrder_NotFound_Returns404(t *testing.T) {} // AC8
10func TestCancelOrder_AfterWindow_Returns409(t *testing.T) {} // AC10
11
12// Edge Case Tests (from EC)
13func TestCancelOrder_ConcurrentCancel_OnlyOneSucceeds(t *testing.T) {} // EC1
14func TestCancelOrder_KafkaDown_StillCancels(t *testing.T) {} // EC2Every test carries the AC or EC number it verifies, so a reviewer can confirm coverage at a glance. This is spec-based coverage, not code-based coverage β and the difference is significant, because code coverage can hit 100% while still missing important business behavior.
02.9 Integration with Claude Code: Spec as Context
One of the greatest strengths of SDD is how the spec can directly serve as context for Claude Code. Instead of describing behavior in a long prompt, you point at the spec file and supply just enough surrounding context, as below.
1Implement CancelOrderUseCase based on specs/order/cancel-order.md.
2
3Additional context:
4- Stack: Go 1.22, Echo v4, pgx/v5
5- Architecture: domain β usecase β repository β handler
6- Reference pattern: internal/usecase/order/create_order.go
7- Skip tests for now, focus on usecase implementation firstWith this prompt, Claude Code reads the spec and understands every AC and EC, identifies the error cases that need handling, builds the right interface for dependency injection, and generates code whose coverage traces back to the spec. The spec does the heavy lifting the prompt would otherwise have to.
02.10 Psychological Challenges: Why This Is Hard
The concept sounds simple, but several psychological challenges make it hard to practice:
Challenge 1: “Code already exists, why write spec?” β For existing features, the spec doesn’t need to be complete. Just document current behavior and highlight areas needing clarification.
Challenge 2: Fear of over-engineering β A minimal spec (user story + 3 ACs + 1 EC) takes 10 minutes. That’s not over-engineering; it’s preventing it.
Challenge 3: “Spec will be outdated anyway” β A self-fulfilling prophecy. With proper governance (spec in git, PR enforcement), it’s preventable.
Challenge 4: Not knowing how to write good spec β The most valid challenge, which is why Part 2 of this series (Articles 05β09) is dedicated to this skill.
02.11 Spec as Source of Truth for AI-Generated Code
In the AI coding assistant era, “spec as source of truth” gains an extra dimension: the AI has no memory across sessions. Each new session, it doesn’t know the architectural decisions, business rules, or patterns already agreed on. The spec becomes the AI’s “persistent memory.” The two prompts below show the practical difference.
1Weak prompt: "Add validation for the cancel order endpoint"
2
3Strong prompt (spec-based):
4"Based on specs/order/cancel-order.md, add validation for all
5ACs and ECs not yet implemented. Verify by comparing current
6implementation in internal/usecase/order/cancel_order.go with the spec."The strong prompt gives the AI complete context about what is wanted, not just what is asked β which is exactly the gap that causes vibe-coded output to drift. The spec file is what closes it.
02.12 Living Spec: Keeping Spec Relevant
A good spec is a “living document” β continuously evolving as the system evolves, but always reflecting the current intended behavior. Three principles keep it alive:
- Spec changes first, code follows β for a new requirement, update the spec first (usually in a separate PR), review it, then implement.
- Accurate changelog β every spec change is documented with clear reasoning.
- Deprecated specs are marked, not deleted β this preserves history and prevents confusion.
02.13 Complete Example: Santekno Shop Order Service Spec
To see all of this in one place, the fully fleshed-out spec below is what a production-ready cancel-order feature looks like at Santekno Shop.
1# Cancel Order
2# Version: 1.3
3# Owner: @ihsan-arif
4# Status: Active
5
6## User Story
7As a Santekno Shop customer, I want to cancel an unprocessed order
8so I don't have to wait if I change my mind, with automatic stock restoration.
9
10## Acceptance Criteria
11
12### Happy Path
13- AC1: DELETE /orders/{id} with valid Authorization header
14- AC2: Order must belong to the requesting user
15- AC3: Order status must be PENDING at time of request
16- AC4: Cancel possible within first 15 minutes since order creation
17- AC5: After cancel: status changes to CANCELLED, all item stock restored,
18 ORDER_CANCELLED event published to Kafka
19- AC6: Response: 204 No Content
20
21### Error Cases
22- AC7: Order not belonging to requesting user β 404 Not Found
23- AC8: Order not found β 404 Not Found
24- AC9: Order status not PENDING β 409 Conflict, error_code: ORDER_NOT_CANCELLABLE
25- AC10: > 15 minutes since order creation β 409 Conflict, error_code: CANCEL_WINDOW_EXPIRED
26
27## Edge Cases
28- EC1: Two concurrent cancel requests β only one succeeds (SELECT FOR UPDATE)
29- EC2: Kafka publish fails after DB update β log WARNING, return 204 (no rollback)
30- EC3: Partial stock restore failure β rollback entire transaction, return 500Notice how every branch a developer could hit β wrong owner, wrong status, expired window, concurrent race, Kafka failure β has a defined outcome. That completeness is precisely what stops the AI, and the next engineer, from guessing.
02.14 Spec Review Checklist
Use the checklist below when reviewing someone else’s spec β it turns a subjective “looks fine” into a concrete pass/fail on completeness, AC quality, and consistency.
1### Completeness
2- [ ] User story present and clear (who, what, why)
3- [ ] At least 3 AC happy path
4- [ ] At least 1 AC error case with explicit HTTP status code
5- [ ] At least 1 EC (edge case)
6- [ ] Scope defined (what's included AND excluded)
7
8### AC Quality
9- [ ] Each AC measurable (can be written as a test)
10- [ ] HTTP status codes explicitly mentioned for all error cases
11- [ ] Response format defined (which fields are returned)
12- [ ] Error message/code mentioned where relevant
13
14### Consistency
15- [ ] No conflicts with existing specs
16- [ ] Naming consistent with domain glossary
17- [ ] Enum value format consistent (UPPER_SNAKE_CASE, etc.)If a spec fails even one box in the “AC Quality” section, send it back β an unverifiable AC is the single most common source of downstream ambiguity.
02.15 Reusable Spec Template
Save the template below at specs/_templates/feature-spec.md so the whole team starts from the same structure every time.
1# [Feature Name]
2# Version: 1.0
3# Owner: @[owner-name]
4# Status: Draft
5
6## User Story
7As [role], I want [action] so that [benefit].
8
9## Scope
10**Includes:** [list of what's covered]
11**Excludes:** [list intentionally not covered]
12
13## Acceptance Criteria
14
15### Happy Path
16- AC1: [first required condition]
17- AC2: [second condition]
18- AC3: [at least 3 ACs for happy path]
19
20### Error Cases
21- AC[n]: [error condition] β [HTTP status] with error_code [CODE]
22 message: "[user-facing error message]"
23
24## Edge Cases
25- EC1: [boundary/concurrent/unusual condition] β [expected behavior]
26
27## Non-Functional Requirements (remove if not applicable)
28- NFR1: Response time < [X]ms at p[Y]th percentile
29
30## Changelog
31| v | Date | Change | Reason | Author |
32|---|------|--------|--------|--------|
33| 1.0 | [date] | Initial spec | - | @[name] |A shared template removes one more source of inconsistency: every spec in the repo has the same sections in the same order, so reviewers always know where to look.
02.16 Anti-Patterns: What Must Not Be in Spec
A good spec is defined as much by what it excludes. The pair below contrasts an implementation-detail anti-pattern with the behavior-level statement that belongs in a spec.
1β "Use pgx.Begin() to start a transaction, then execute..."
2β
"Cancel operation must be atomic β all succeed or all rollback"The “β ” line commits to a guarantee without dictating the mechanism, leaving the implementer free to choose the best approach. The other anti-patterns follow the same principle:
- UI/UX details belong in a design spec, not a feature spec.
- Timelines or estimates belong in project management tools.
- SQL or code snippets don’t belong in a spec at all β the spec defines what, not how.
02.17 Tooling for Managing Spec
Git hooks that enforce spec updates and PR templates with spec-compliance checks are useful, but they don’t require special tools β everything works with standard git and markdown. Dedicated tooling can be added later as the team scales, but it’s never a prerequisite for starting.
02.18 Tips & Gotchas
π‘ Tip 1: Create the spec in a separate branch before coding β spec/feature-name β PR for spec review β merge β then create feature/feature-name for code.
π‘ Tip 2: A minimal spec beats no spec β 1 user story + 3 ACs + 1 EC can be written in 10 minutes.
π‘ Tip 3: Use domain language, not implementation language β the spec should be readable by product managers.
π‘ Tip 4: Link the spec from code via comments β // Cancel order per specs/order/cancel-order.md v1.3.
β οΈ Gotcha 1: An ambiguous spec is worse than no spec β if you can’t write a test for an AC, it’s too ambiguous.
β οΈ Gotcha 2: Don’t let spec review become a bottleneck β if review takes more than 2 days, fix the review process, not SDD.
β οΈ Gotcha 3: AI can generate a well-formatted but substantively empty spec β you make the business decisions, the AI helps with format.
β οΈ Gotcha 4: A spec doesn’t replace stakeholder discussion β it’s the output of discussion, not a substitute.
02.19 Spec as Source of Truth in High-Traffic Systems
In high-traffic systems like Santekno Shop, “source of truth” has to include performance and capacity β otherwise developers guess the targets. The NFR block below documents exactly what the cancel endpoint must guarantee under load.
1## Non-Functional Requirements for Cancel Order
2
3### Performance
4- NFR1: Response time < 300ms at p95 with 100 concurrent users
5- NFR2: Response time < 1000ms at p99 with 500 concurrent users
6
7### Load
8- NFR3: Designed for 50 cancellations/second at peak load
9- NFR4: DB lock (SELECT FOR UPDATE) timeout: 2 seconds maximum
10
11### Monitoring
12- NFR5: Required metrics: cancel_order_total, cancel_order_errors_total,
13 cancel_order_duration_seconds (histogram)
14- NFR6: Alert if error rate > 5% over 5 minutesWith these numbers written down, nobody has to guess the performance target, and when a regression appears there’s a concrete baseline to compare against. Non-functional requirements are as much a part of the source of truth as the happy-path ACs.
02.20 Summary
“Specification as source of truth” is a concrete engineering practice, not just a philosophical concept:
An effective source of truth must be specific but not too detailed, automatically verifiable, single-owned, and git-versioned.
Without governance, a spec will become outdated. The minimum needed is spec review before implementation, spec updates alongside code, and a quarterly audit.
In the AI context, the spec becomes “persistent memory” across sessions. The AI can’t remember yesterday’s decisions β but it can read the spec files in the repository.
A spec doesn’t replace stakeholder discussion β it makes discussion more productive, because everyone speaks from the same document.
In the next article, we’ll look at the SDD tools ecosystem 2025 β Claude Code, Kiro, Cursor, GitHub Spec Kit, and other supporting tools, plus how to choose the right ones for your needs.