Egor Urvanov

Specs by aggregateRFC requirements and bounded contexts instead of a spec per feature

Spec-driven development without spec sprawl: how to keep specs in one aggregate dictionary so they do not multiply with every feature: requirement levels, the aggregate card and context boundaries.

· 4 min · · Specs and context

Why specs pile up

Spec per featurea concept is described in every feature
12 files“Booking” in 7
grows with features
Aggregate dictionarya concept is described once
Reservation
Room
TimeSlot
Member
4 entries“Booking” in 1
grows with the domain
Feature 13→[booking::Reservation]+1 MUST→test+1

Numbers are illustrative. How OpenSpec lays out specs and changes: specs/ and changes/ folders, finished changes go to an archive.

Takeaway
In this exampleTwelve features produced twelve specs, and booking is described in seven of them, each a little differently. In the dictionary the same features added lines to four aggregate entries.
In generalWhen a spec is tied to a change or a feature, every task adds files, and one concept drifts across several places. When the unit is an aggregate, the number of entries is bounded by the domain itself, and a feature only adds requirements to an entry.
Next stepList the concepts that appear in three or more specs and give each one a single dictionary entry. Let feature specs link to the entry instead of retelling it.

RFC 2119 requirement levels

WordMeaningStrengthCheck
MUSTrequiredautomated testblocks
MUST NOTforbiddenautomated testblocks
SHOULDdefault, deviate with a reasonreviewwarns
MAYallowednonenot checked
MUST = SHALL

Words and meanings follow RFC 2119. Each word maps to its own kind of check.

Takeaway
In this exampleIn the booking entry, “intervals do not overlap” is a MUST checked by a test, and “confirmation goes to the same channel” is a SHOULD looked at in review.
In generalThe requirement word tells you right away how to check it and what to do on a violation. The agent reads MUST as a boundary and SHOULD as a default and does not mix them up.
Next stepRewrite the requirements in your spec with these four words and put the test that checks it next to every MUST.

The aggregate card

The example is made up. Terms follow Martin Fowler: DDD Aggregate.

Takeaway
In this exampleThe whole booking is one card: an address, four requirements, three commands, permissions, links and the feature it came from.
In generalAn aggregate is a boundary inside which the requirements always hold, and it can change only through its own commands. That is why the card keeps the rules next to the only ways to break them.
Next stepGive every aggregate a six-field card. If a requirement cannot be assigned to any aggregate, the aggregate most likely has not been named yet.

Bounded contexts

booking
ReservationRoomTimeSlot
billing
InvoiceTariff
access
PassLevel
links between contexts go by id onlybilling::Invoice→ id →booking::Reservationaccess::Pass→ id →booking::Reservation
“Level”
booking::Room.floorFloor
access::LevelClearance
one word, two terms, different labels
one term = one entrylink as [[ctx::Name]]same word → separate entriesdictionary changes with the spec

The approach is Bounded Context and Ubiquitous Language.

Takeaway
In this exampleThe invoice and the pass refer to a booking by id and know nothing about its insides. “Level” means a floor in booking and a clearance in access, and these are two separate entries.
In generalA context is an area where every word has one meaning. When a word appears in two contexts, you create two terms with different labels; otherwise the agent carries a rule from one into the other.
Next stepSort your aggregates into contexts and find the words that appear in two of them. Give each such word separate entries and different labels in the interface.

Read next

ContinuedA reconcile loop for specslike Kubernetes: desired state, checks and the agent’s environment
The booking example is made up. Terms and approaches follow the sources linked under the diagrams.
  • AI
  • spec-driven development
  • specs
  • DDD
  • agents
© 2026 Egor Urvanov