A .spec file is Specy’s format for formalizing business specifications and validating them against existing .domain.specy models. It captures a business requirement in prose, confronts it with the current domain model to detect contradictions, gaps, and impacts, and produces a structured projection of what should change. The .spec file is a specification artifact: it describes what should change, not what has changed.
Header
Explanation:
Every .spec file begins with a header that identifies the specification, pins it to a specific model version, and declares which .domain.specy file it was validated against. This enables staleness detection: if the models evolve after the spec is written, the version mismatch signals that the confrontation results may be outdated.
Syntax:
spec "Name of the specification"
against module Name version "gitSha" at "ISO 8601 timestamp"
uses module Name
uses module OtherModule
spec: a short, descriptive name for the specification.against module: the primary module, model version (gitShafromspecy/.meta.json), and extraction timestamp (lastRun). This pins the spec to a specific model version for staleness detection.uses module: all modules this spec was validated against. Multipleuses moduledirectives are allowed for cross-module specs.
Example:
spec "Deliver an order"
against module Order version "a1b2c3d" at "2025-01-15T10:30:00Z"
uses module Order
When a spec is fully realized (all projected changes are present in the model), a realized line is added:
spec "Deliver an order"
against module Order version "a1b2c3d" at "2025-01-15T10:30:00Z"
realized version "f4e5d6c" at "2025-02-01T14:00:00Z"
uses module Order
Narrative
Explanation:
The narrative block captures the original business requirement in the user’s own words: a user story, business rule, feature request, or behavior change. It preserves the intent exactly as stated, before any decomposition or formalization.
Syntax:
narrative {
"The original requirement in the user's own words."
"Can span multiple lines."
}
Example:
narrative {
"As a warehouse operator, I want to mark an order as delivered"
"so that the customer is notified and the order lifecycle is complete."
}
The narrative is never rewritten or interpreted. It is a verbatim record of what was requested.
Concepts
Explanation:
The concepts block is the result of decomposition and anchoring. It inventories every business concept involved in the spec and maps each to the existing model using one of three labels:
[existing]: the concept exists in the.domain.specymodel.[new]: the concept does not exist and must be created.[ambiguous]: the term does not match existing vocabulary and needs clarification.
Syntax:
concepts {
entity Order [existing] // entity Order in orders.domain.specy
command DeliverOrder [new]
event OrderDelivered [new]
field deliveryNote [ambiguous] // unclear mapping
}
Each line follows the pattern: {type} {Name} [{label}] with an optional // comment for context. Valid types are: entity, value, enum, command, event, operation, policy, invariant, service, transition, and field.
Example:
concepts {
entity Order [existing] // entity Order in orders.domain.specy
enum OrderStatus.delivered [existing] // value exists, no transition modeled
command DeliverOrder [new]
event OrderDelivered [new]
operation DeliverOrder [new]
}
A [new] concept does not necessarily produce a formal block in changes. When a concept’s logic is better captured inside another construct, listing it in concepts documents the intent without forcing a formal block.
Confrontation
Explanation:
The confrontation block captures the results of validating the spec against the existing model. Every finding receives one of five labels:
| Label | Meaning |
|---|---|
compatible |
The spec is fully compatible with the existing model. No contradiction, no impact. |
contradiction |
An existing fails, policy, or invariant blocks the proposed behavior. |
impact |
A modification to the model affects existing operations. |
gap |
The spec does not cover an error case or edge case detectable from the model. |
coverage |
An emitted event has no consumer, or a concept is orphaned. |
Syntax:
confrontation {
compatible "Short description" {
"Optional detailed justification."
}
contradiction "Short description" {
"Detailed explanation."
}
impact "Short description" {
"Detailed explanation."
}
gap "Short description" {
"Detailed explanation."
}
coverage "Short description" {
"Detailed explanation."
}
}
Example:
confrontation {
compatible "Order entity supports new status transition" {
"The OrderStatus enum already includes 'delivered'."
"No existing policy or invariant blocks transitioning to delivered."
}
gap "No handling for already-delivered orders" {
"The spec does not specify what happens if DeliverOrder is called"
"on an order that is already in 'delivered' status."
}
}
Each entry has a short description string followed by an optional block with detailed explanation lines.
Changes
Explanation:
The changes block contains the projected modifications to a .domain.specy file. Each block targets a specific file and uses three operators to describe what should happen:
| Operator | Meaning |
|---|---|
add |
New definition. The full block follows in native Specy syntax. |
modify |
Existing definition changed. Two forms: top-level (full construct replacement for non-entity constructs like enums, values, services) and dotPath (targeted sub-block of an entity, value, or service). Changed lines are annotated with // was: ... or // added by this spec. |
remove |
Existing definition deleted. Only the type and name, no block body. |
Note: uses module references modules by name, while changes "file.domain.specy" references physical files. This asymmetry is intentional: uses targets semantic modules, changes targets files. To map between them, read the module declaration at the top of each .domain.specy file.
The content inside add and modify is native Specy syntax, the same grammar as .domain.specy files. No new syntax is invented for projected modifications.
Syntax:
changes "orders.domain.specy" {
add command DeliverOrder {
fields {
orderId : uuid required
}
}
modify Order.fields {
deliveredAt : datetime optional pastOrPresent // added by this spec
}
modify Order.operations {
add "Deliver an order" on DeliverOrder {
resolves Order from deliverOrder.orderId
sets Order { status = delivered }
emits OrderDelivered
}
}
modify Order.transitions {
add shipped --> delivered on "Deliver an order"
}
remove command DeprecatedCommand
}
DotPath modify targets a specific sub-block of an entity (or value/service):
modify Entity.fields { ... }— only added/modified fields, with// was:or// added by this specmodify Entity.operations { add/remove ... }— add or remove operationsmodify Entity.operations."Label" { ... }— full operation as it should be after modificationmodify Entity.transitions { add/remove ... }— add or remove transitionsmodify Entity.policies { ... }— adds/modifies entity policiesmodify Entity.references { ... }— adds/modifies referencesmodify Entity.invariants { ... }— adds/modifies invariants
Top-level modify replaces the entire construct (for non-entity types):
modify enum OrderStatus { ... }— full enum with all values (annotate new ones// added by this spec)modify value Money { ... }— full value objectmodify policy name(...) { ... }— file-level policymodify service X { ... }— full service
Example:
changes "orders.domain.specy" {
add command DeliverOrder {
fields {
orderId : uuid required
deliveryNote : string optional maxLength(500)
}
}
add event OrderDelivered {
fields {
orderId : uuid required
deliveredAt : datetime required
}
}
modify Order.fields {
deliveredAt : datetime optional pastOrPresent // added by this spec
}
modify Order.operations {
add "Deliver an order" on DeliverOrder {
resolves Order from deliverOrder.orderId
policy orderMustBeShipped(Order.status) :: "Order is not shipped" {
Order.status != shipped
}
sets Order { status = delivered, deliveredAt = now() }
emits OrderDelivered { orderId = Order.id, deliveredAt = Order.deliveredAt }
}
}
modify Order.operations."Cancel an order" {
resolves Order from cancelOrder.orderId
policy orderCancellable(Order.status) :: "Order cannot be cancelled" {
Order.status not in {draft, confirmed, shipped} // was: {draft, confirmed}
}
sets Order { status = cancelled, cancelledAt = now() }
emits OrderCancelled
}
modify Order.transitions {
add shipped --> delivered on "Deliver an order"
}
}
Key points:
- Every model change must have a corresponding
impactblock (see below). - DotPath
modifyshows only the targeted sub-block, not the complete construct. Annotate changes with// was: ...or// added by this speccomments. - Top-level
modify(enums, values, services) shows the complete construct after modification.
Impact
Explanation:
The impact block traces how model changes affect existing operations, policies, and invariants. Every operation, policy, and invariant that references a modified construct must be listed with either none (no impact) or affected (impacted by the changes), followed by a quoted explanation on the next line.
Each entry follows the pattern: {kind} {dotPath} -> {none|affected} where kind is operation, policy, invariant, service, event, entity, value, or enum. Operations use the Entity."Label" form for command/event-triggered operations and Entity.name for internal operations.
The impact block only covers existing constructs. New constructs proposed by the spec do not appear here (they are in the changes block).
Syntax:
impact {
operation Order."Cancel an order" -> none
"delivered already excluded from not in {draft, confirmed}"
operation Order."Ship a confirmed order" -> none
"unrelated, transitions confirmed to shipped"
invariant OrderMustHaveLines -> none
"references Order.lines, not Order.status"
}
Example:
impact {
operation Order."Place a new order" -> none
"creates orders in draft status, unrelated to delivery"
operation Order."Cancel an order" -> affected
"must now handle the delivered status, policy updated in changes"
operation Order."Confirm an order after payment" -> none
"transitions confirmed status only, no delivery dependency"
policy maxOrderAmount -> none
"references Order.totalAmount, not Order.status or deliveredAt"
invariant OrderMustHaveLines -> none
"references Order.lines, not affected by delivery changes"
}
The impact block is mandatory whenever the spec includes model modifications. A model change without impact analysis is considered incomplete.
Lifecycle
Explanation:
A .spec file progresses through three stages:
- Created: the
/specskill produces the.specfile inspecy/specs/. - Active: the spec is pending implementation. Developers use it as a guide for what should change in the code.
- Realized: the code is implemented,
/distillre-extracts the.domain.specymodels, and the models now reflect the spec. Arealizedline is added to the header with the new model version and timestamp, then the file is moved tospecy/specs/done/.
Verification checks whether a spec’s projected changes have been realized in the current model. Each change entry receives one of four labels:
| Label | Meaning |
|---|---|
[REALIZED] |
The change is fully present in the current model. |
[PARTIAL] |
The construct exists but differs from the projection. |
[MISSING] |
The projected change is not reflected in the model. |
[DIVERGENT] |
The construct exists but has changed in ways the spec did not predict. |
When all changes are [REALIZED], the spec is marked as realized and archived.