Solution Architecture14 September 202611 min read

Spec-Driven Development: From Prompting to Controlled Delivery — Part 7 of 9

Four worked examples: greenfield, brownfield, a small defect and an AI feature

Spec-driven development should change shape with the work. Four examples from one fictional insurer show what a specification looks like for a new service, a change to an old monolith, a two-hour defect and an AI feature that touches health information.

#Spec-Driven Development#Solution Architecture#Software Delivery#AI Governance#Migration Playbook

Most writing about spec-driven development uses one example, usually a neat new feature in an empty repository. Real teams do not choose their work that way. In the same fortnight they build something new, change something old that nobody fully understands, fix a defect reported that morning, and add an AI feature the business is excited about.

If the method only works for the first of those, it is not a method. It is a demo.

This part works through four examples, each a different shape of work, and shows how much specification each one actually needs. We are still following Kestrel Mutual, a fictional South African insurer. All figures are illustrative. Kestrel sells life, funeral and household cover to about 420,000 policyholders, and earlier parts of the series followed its assisted account recovery feature. Here the same team takes on four other pieces of work.

ExampleShape of workHow much specificationThe artefact that mattered most
A. Claim-status notificationsGreenfield servicePrinciples, feature spec, design, tasks, evidenceThe principles, written before any design
B. Policy documents by emailBrownfield change to a monolithChange proposal, delta, bridge controlsThe delta: what changes, what must not
C. Double submission on the payment formSmall defectA mini-spec of five linesThe one line about idempotency
D. Claim document classificationAI featureBehaviour, thresholds, evaluation and evidenceThe rule for what happens when the model is unsure

Example A: a greenfield claim-status notification service

The intent

Kestrel’s claims team takes a steady stream of calls that amount to one question: “Where is my claim?” The intent is short:

Customers should receive timely, consent-aware updates on their claim status through their preferred channel. Operations staff must be able to see whether each message was delivered and retry the ones that failed.

Two words in that sentence do a lot of work. Consent-aware means Kestrel only messages a customer on a channel they agreed to, for a purpose they were told about. Preferred channel means WhatsApp for some customers, SMS for others and email for the rest, which is a very South African mix.

Principles first

Before anyone designed anything, the team wrote five principles. In Spec Kit’s vocabulary this would be part of the constitution; in any vocabulary it is the set of rules every later decision is checked against.

  • No notification is sent without a recorded purpose and a preference check.
  • Business services request a notification by publishing an event. They never call a channel provider directly.
  • Personal data in logs is minimised.
  • Delivery is asynchronous and idempotent.
  • A channel failure never rolls back the business transaction.

Two terms need explaining. Asynchronous means the claims service does not wait for the message to be sent before carrying on. Idempotent means that processing the same request twice has the same effect as processing it once: if the same event arrives again, the customer does not get a second message.

Each principle closes off a tempting shortcut. Without the second, the quickest way to ship is for the claims service to call the WhatsApp provider directly, and six months later three services have their own copy of that integration. Without the fifth, a WhatsApp outage could block a claim approval, which is absurd said out loud and easy to build by accident.

The feature specification

The behaviour is written as a scenario in Gherkin, the Given/When/Then format used in behaviour-driven development (BDD), so that product owners and testers can read it as easily as engineers:

Scenario: Notify a customer that a claim is approved for payment
  Given a claim is approved for payment
  And the customer has opted in to WhatsApp notifications
  When the claim-approved event is accepted
  Then one WhatsApp notification is requested
  And the delivery state is recorded
  And a repeated event does not create a duplicate message

The last line is the one a coding agent would most likely miss if nobody wrote it down. Most event systems guarantee delivery at least once rather than exactly once, so duplicates are normal, not exceptional. Customers who receive “Your claim has been approved” three times start to wonder what else is broken.

The design

Claim-status notifications: the greenfield designClaims servicepublishes claim-approved eventEvent brokerIDEMPOTENT: CLAIM ID + STATUSNotification servicePreference servicecheck consent and channelONE CHANNEL INTERFACEWhatsAppSMSEmailDelivery-state storeops view and retriessigned delivery callbackA failed WhatsApp send never rolls back the claim approval.
The claims service only publishes an event; it never calls a channel provider. The notification service checks consent before anything is sent, routes through one channel interface and records delivery state. Provider callbacks are checked for a valid signature before they are trusted.

The claims service publishes a claim-approved event to an event broker, a piece of infrastructure that holds events and passes them to whichever services have subscribed. The notification service picks it up, checks the customer’s consent and channel with the preference service, sends the message through a channel adapter, and records the delivery state. When the provider later confirms delivery, it calls back, and the notification service checks that the callback is genuinely signed by the provider before trusting it.

Tasks and evidence

The plan broke into nine tasks, each small enough for one person or one agent to complete and one reviewer to check:

  1. Define the claim-approved event and its idempotency key.
  2. Define the preference query contract.
  3. Implement the notification request handler.
  4. Add the WhatsApp adapter behind a channel interface.
  5. Store request and delivery states.
  6. Process signed provider callbacks.
  7. Add a retry and dead-letter policy (a dead-letter queue holds messages that failed repeatedly, so a person can inspect them instead of retrying forever).
  8. Add metrics, traces and a support view.
  9. Run the acceptance, security and failure tests.

The evidence that the service works is decided up front, not assembled at the end:

  • contract tests for the event and preference interfaces;
  • a duplicate-event test;
  • a provider-timeout and retry test;
  • a callback-signature test, including a forged callback that must be rejected;
  • an inspection of logs for personal data;
  • an operational alert and a runbook.

Example B: a brownfield change to the policy administration monolith

Current behaviour

Kestrel’s policy administration system is an older monolith: one large application that does many jobs. When a customer buys a policy online, it emails the policy schedule during the purchase, and waits for the email provider to respond before confirming.

Two problems follow. When the email provider is slow, checkout is slow. When the provider fails at the wrong moment, it is sometimes unclear whether the purchase completed, which creates support calls and occasionally a duplicate policy.

Describe the change, not the whole system

For brownfield work, the useful question is not “what should exist?” but “what changes from what we already promise?” This is where an OpenSpec-style delta earns its place. In OpenSpec, a change carries a proposal, delta specs, a design and tasks; the delta lists requirements as added, modified or removed, and once the change is complete the deltas are merged into the durable specifications.

Kestrel is tool-neutral, but the team borrowed the shape:

  • Added: a policy-issued event with a stable identifier and a schema version; an asynchronous delivery state.
  • Changed: purchase success no longer depends on the email provider’s response.
  • Preserved: the content of the policy schedule, the customer’s contact preference, and the audit record.
  • Removed later: the direct call to the email provider, once two reconciliation periods run clean.

The Preserved line is what makes this a good delta. In a brownfield system the things that must not change are usually the things nobody wrote down, and they are exactly what a coding agent “tidies up”. If your team is still discovering what an old system actually does, reverse-engineering legacy systems with coding agents covers that earlier step.

The design, including the bridge

Policy documents: the outbox bridge and its deltaTHE NEW PATH, WITH THE OLD ONE KEPT BEHIND A FLAGPolicy purchase (monolith)Transaction commitwrites outbox row tooRelay to event brokerNotification componentEmail providerFeatureflagtemporary legacy pathTHE DELTA FOR THIS CHANGEADDEDPolicy-issued event, versionedAsynchronous delivery stateCHANGEDPurchase no longer waits for emailPRESERVEDSchedule content, preferenceand the audit recordREMOVED LATERDirect provider call, aftertwo clean reconciliationsThe preserved list is what an agent is most likely to tidy away.
The purchase and the outbox record are saved in one transaction, so the customer no longer waits for an email server. The old direct call stays behind a feature flag until reconciliation proves the new path. The delta states what is added, changed, preserved and removed later.

The design uses a transactional outbox. When the purchase commits, the monolith writes a policy-issued record into an outbox table in the same database transaction. Either both the purchase and the outbox record are saved, or neither is. A separate relay then publishes outbox records to the broker, and the new notification component sends the email. The customer’s purchase no longer waits for anyone’s email server.

The old path is not deleted on day one. It stays behind a feature flag, a switch that turns behaviour on or off without redeploying, so the team can run both paths while they compare results and switch back quickly if something is wrong.

Bridge controls

A bridge state is only safe if someone has decided how to run it:

  • dual-path comparison without sending the customer two emails;
  • reconciliation between outbox records and published events;
  • an alert when the delivery backlog grows;
  • a rollback flag that has been tested, not just configured;
  • retirement criteria for the old path, and a named owner who will remove it.

Temporary paths without an owner become permanent architecture.

Example C: a small defect, with a quick spec

A customer reported that during a slow response they clicked Pay twice on the once-off premium payment form, and were debited twice.

This does not need a product requirements document, a design review or a workshop. It does need five lines written down before anyone, human or agent, starts changing code:

  • Disable the submit button after the first accepted click.
  • Show progress without clearing what the customer entered.
  • Use an idempotency key at the payment API, because a disabled button alone is not enough.
  • Re-enable the button only after a recoverable client-side failure.
  • Test a rapid double-click, a page refresh and a network retry.

The third line is the point of writing anything at all. The fastest fix is to disable the button, and it passes a quick manual test. It does not survive a weak mobile connection whose browser retries the request, or a page refresh. The idempotency key lets the payment service recognise a repeated request and not debit again, whatever the browser does.

This is what “scale the ceremony with risk” from Part 1 looks like in practice. The spec is small because the change is small. It still exists because the change touches money.

Example D: AI-assisted claim document classification

The intent

Claims arrive with supporting documents: death certificates, copies of identity documents, bank confirmation letters, medical reports, affidavits. Today an administrator opens each one and routes it by hand. The intent:

Classify incoming claim documents to reduce manual routing, without allowing a misclassification to change a claim outcome silently.

The second half of that sentence is the whole design problem. A classifier that is right most of the time and quietly wrong the rest of the time is worse than no classifier, because nobody is looking for the errors.

What the specification must define

The spec definesWhy it matters at Kestrel
Supported document classesAnything outside the list must be recognised as unknown, not forced into the nearest class
A representative evaluation datasetReal claim documents, including poor scans and phone photos, not tidy samples
Precision and recall for each classPrecision: how often a “medical report” label is right. Recall: how many medical reports it finds. Both, per class
Confidence thresholdsSet per class from the evaluation results, not picked because they sound reassuring
Abstain or unknown behaviourWhat happens when the model is unsure
Human-review rulesWhich documents a person must confirm, and when
Data handling and retentionWhere documents are processed, who can see them and how long anything is kept
Model and prompt version evidenceWhich version classified which document, so a problem can be traced
Drift monitoringHow the team notices when accuracy falls as documents change
RollbackHow to return to manual routing in minutes
Cost and latency targetsWhat a classification may cost and how long it may take

Notice what is not in the table: the name of a model. The model name is a design choice. The behaviour, the risk and the evidence belong in the spec. Kestrel can swap models next year without rewriting the specification, and the evaluation set tells them whether the new one is actually better.

Routing by confidence

Claim documents: routing by confidenceIncoming claim documentClassifierconfidence per classABOVE CLASS THRESHOLDAuto-route to claims queuelogged and reversibleBETWEEN THRESHOLDSA person reviewsand routes itBELOW, OR UNKNOWN CLASSAbstain: no guessmanual routingMedical reports: a person confirms before any claim outcome changesHealth information is special personal information (POPIA s26)Thresholds are set per class from the evaluation set,and every decision records the model and prompt version
The classifier never forces a guess. High confidence routes automatically and stays reversible, a middle band goes to a person, and low confidence or an unknown document type makes the system abstain. Medical reports always need a person, because health information is special personal information.

The routing rule has three outcomes. High confidence routes the document automatically, and the decision is logged and reversible. A middle band goes to a person. Low confidence, or a document that fits no known class, makes the system abstain: it declines to guess and hands the document to manual routing.

Medical reports get a stricter rule. Health information is special personal information under section 26 of POPIA, which starts from a prohibition with limited exceptions, so Kestrel’s spec requires a person to confirm any medical report before it can affect a claim. That is a design decision for Kestrel’s privacy and legal people to confirm, not something to take from an article; which data can AI touch gives the plain-language background. For how to build the evaluation set itself, see evaluating AI agents.

What the four examples have in common

The four specifications look nothing alike, but they share what matters. Each names the behaviour that is easiest to get wrong: duplicate messages, silently changed behaviour, double debits, confident misclassification. Each decides what evidence will prove it works before the work starts. Each is small enough that nobody approved a document they did not read. And none tells an engineer or an agent which classes and methods to write. A specification that prescribes file names and method signatures has become slow code, and it will be out of date by the second pull request.

What to take from this part

Scale the specification to the shape of the work. A new service, an old monolith, a defect and an AI feature each need a different amount and kind of spec.

Write the principles before the design on greenfield work. Each principle closes off a shortcut that would otherwise become architecture.

On brownfield work, specify the change and what must be preserved. The unwritten behaviour is what an agent is most likely to tidy away.

Even a small defect needs the one line that matters. Five bullets that include idempotency beat a quick fix that passes a manual test.

For AI features, specify behaviour under uncertainty. Thresholds, abstention and human review belong in the spec; the model name does not.

Next: Part 8: Govern decisions and evidence, not document volume.

How CloudNala can help

We work alongside delivery teams on their real backlog rather than a sample project: choosing the right weight of specification for each piece of work, writing the deltas and bridge controls for changes to older systems, and defining the evaluation sets, thresholds and review rules that an AI feature needs before it touches production data. The aim is a team that can make that call for itself on the next piece of work.


Work with CloudNala

CloudNala helps organisations move from technology ambition to practical execution across cloud, AI, data, platform engineering and digital services.

Whether you are exploring AI, modernising your cloud environment, building a public-sector digital service, or turning an idea into a working MVP, we can help you shape the roadmap and deliver the next step.

Plan an SDD Pilot or write to us at consult@cloudnala.co.za