# Spec-Driven Development for ERP Requirements With AI Agents

> ERP requirements arrive as edge cases, not specs. How I turn them into testable acceptance criteria, a worked-example table and an ambiguity log.

URL: https://www.matthewswong.com/en/blog/spec-driven-development-erp-requirements/

- Author: Matthews Wong (https://www.matthewswong.com/en/about/)
- Published: 2026-09-14
- Updated: 2026-09-14
- Category: ERP

> **Key takeaway:** ERP requirements arrive as edge cases, not specifications. Before prompting an agent, write one testable acceptance criterion per rule, a worked-example table carrying the boundary rows finance argues about, and an ambiguity log naming who owns each unanswered question. The spec outlives the prompt, so the spec is what gets reviewed.

The requirement arrived as three things in one afternoon: a spreadsheet of purchase requests that had routed to the wrong approver, a WhatsApp thread where the branch manager explained which of them were fine anyway, and a meeting where finance said the limit was five million rupiah — except at month end. None of it was a specification, and no amount of prompting turns it into one.

This post is about the part of spec-driven development that happens before any agent is involved: turning that material into acceptance criteria a test can fail, a worked-example table finance will sign, and a log of the questions the spec could not answer. The examples are the ERP rules that cause the most argument in review — an approval hierarchy with a delegation path, a posting-period lock, tax rounding and landed-cost apportionment.

## What an ERP requirement looks like when it arrives

ERP requirements are rarely written down, because the business already knows them. They live in the exceptions, and the exceptions surface only when something routes to the wrong person. What lands on your desk is almost always one of four shapes, and none of them is a rule:

- A spreadsheet of edge cases — rows someone collected because the current system handled them wrong, with no statement anywhere of what right would have looked like.
- A chat thread, where the decision is real but the reasoning is spread across the scrollback and two people in it disagree without noticing.
- A sentence with an except in it. Approve anything up to five million, except at month end, is two independent rules and a boundary nobody has dated.
- A screenshot of the old system, offered as the specification, including the bugs the team has quietly learned to work around.

All four share the same defect: they describe behaviour by example without saying which examples are the rule and which are the exception. Hand any of them to an agent and you get something plausible, because plausible is all the input supports. The work is not prompting better. It is deciding, in writing and before the session starts, which of those rows are law.

## A rule nobody can write a failing test for is not a rule yet

This is the only filter I apply to every line of an ERP spec: can I write a test that fails today and passes when the rule is implemented? If not, the line is a preference, an aspiration or a summary. Keep it in the background section and stop calling it a requirement. ISO/IEC/IEEE 29148, the standard that superseded IEEE 830 in 2011, asks for the same thing in its own vocabulary — individual requirements are to be necessary, appropriate and unambiguous.

Gherkin is not the point here; the shape is. One rule per block, one assertion per line, and named people rather than roles. Writing that Rina has delegated approval to Budi from 14 to 21 September forces the delegation window into the spec, and the window is exactly what implementations forget: a delegation with no end date is a permanent second approver that nobody audits until an auditor asks who signed.

> Write the assertion before you write the rule text. If you cannot state the assertion in one clause, the rule is two rules — split it. An approval limit and a month-end freeze sound like one sentence in the meeting, and they are two independent rules with two different owners in the spec.

## The worked-example table settles more arguments than the prose

The table is the artefact I bring to the review, not the prose. Prose about approval limits gets nodded at; a row saying a request for exactly 5,000,000 is approved by the manager alone gets argued about, and the argument is the deliverable. Cucumber's Example Mapping calls these the green cards, concrete examples sitting under each blue rule, and the claim matches what happens in an ERP review: examples expose disagreement that rule statements hide.

| Input | Expected result | Why this row is in the table |
| --- | --- | --- |
| PR for 4,500,000 IDR, branch approval limit 5,000,000 | Department manager approves, no finance step | The happy path, so a routing regression is visible immediately |
| PR for exactly 5,000,000 IDR | Manager approves alone — the limit is inclusive | Finance said up to five million; the first implementation read it as under five million |
| PR for 40,000,000 IDR, finance head delegated to the controller for one week | Controller approves; the audit trail names both people and the window | A delegation with no end date becomes a permanent second approver |
| PR for 40,000,000 IDR raised by the named delegate | Routes one level above, never back to the delegate | Self-approval is a control, not a preference, so it needs its own row |
| Two invoice lines of 1,000,005 IDR at the 11 percent rate on the item master | Tax of 220,002 IDR, rounded half up on each line | Rounding the invoice total once instead gives 220,001, and the difference never reconciles |
| Supplier invoice dated 30 September, period already locked | Posting refused; it posts to October with the document date preserved | The boundary finance asks about first, in every single review |

Every row in that table exists because someone got it wrong once. The boundary rows do the work: the amount sitting exactly on the limit, the approver who is also the requester, the invoice dated on the day the period closes. A table of comfortable mid-range rows proves nothing and reviews in thirty seconds, which is precisely why writing one feels productive.

*Ownership changes at every step, and that is the part worth noticing: the sentence belongs to finance, the criterion to whoever writes the spec, the worked example goes back to finance for sign-off, and only the failing test belongs to the agent. Skipping the third band is how you get a correct implementation of the wrong rule.*

## The ambiguity log is the deliverable stakeholders actually engage with

Every ERP module spec I have written produced more questions than rules on the first pass, and the questions turned out to be the valuable half. Example Mapping makes the same move with its red cards — as the Cucumber write-up puts it, you have just turned an unknown unknown into a known unknown. The ERP-specific hardening is that each question gets an owner, a date and a default.

Two fields make this a working document rather than a list of complaints. The owner field names a person, because a question owned by finance is owned by nobody. The default field states what the code will do if no answer ever arrives, which converts an open question into shippable behaviour plus a recorded risk — and the test asserts the default, so on the day the real answer comes back the failing test tells you exactly what to change.

> The failure mode is a spec written after the code. It reads like a specification, passes review, and is in fact documentation of whatever got built, including the parts nobody decided. You can spot one by its missing boundary rows: a spec written afterwards has none, because the implementation already answered every boundary question silently.

## Boundaries an agent cannot guess: period locks and rounding

Two classes of rule cause most of the post-go-live arguments, and neither is guessable from the surrounding code. The first is the posting-period boundary. Odoo, as a documented example, ships two of them: a Lock Everything date that prevents modifying posted entries and posting new ones on or before that date, and an irreversible Hard Lock date that, in Odoo's words, cannot be changed or overridden regardless of access rights. Which of those your rule means is a business decision, and the period is closed does not say.

The second is rounding, where a specification earns its keep, because the arithmetic is decided by where you round rather than how. Two invoice lines of Rp 1,000,005 at the 11 percent rate configured on the item master come to 220,002 rupiah when each line is rounded half up, and 220,001 when the invoice total is rounded once. One rupiah, on every invoice, and a reconciliation task that never closes. Landed-cost apportionment has the same shape with the sign reversed.

The comment in that test is the specification. Naive rounding invents one rupiah of freight, so the residual has to land somewhere on purpose. Which line receives it is genuinely arbitrary — highest value, first line, last line are all defensible — but it has to be written down, because otherwise three developers pick three different arbitrary answers and the inventory valuation stops tying out to the supplier invoice.

## Why the spec, not the prompt, is the artefact worth reviewing

A prompt is discarded at the end of the session. The spec is read by the next person, and on an ERP module the next person is usually auditing a number that came out wrong in a month you have long forgotten. GitHub's Spec Kit takes this to its logical end, describing a process in which specifications become executable and directly generate working implementations rather than just guiding them, with specify, plan, tasks and implement as separate commands. What I take from it is narrower: the reviewable unit is a spec file in the repository, next to the code it governs.

In practice the spec lives in a specs folder alongside the module, the ambiguity log sits beside it, and a change to a rule becomes a diff you can put in front of a finance person. I review the diff of the spec, not the transcript of the session that implemented it. The transcript tells me what the agent was told; the spec tells me what the business decided, and only one of those is still true next quarter.

*The middle panel is the state to aim for, not the left one — a written default plus a named owner is already enough to build against. The right panel is the only one that ships an unreviewed decision, and it surfaces at month-end close rather than in code review.*

## The order that keeps an ERP spec honest

The order matters more than the format. Written in this sequence a spec stays a specification; written in any other order it slowly becomes a description of the code.

1. Collect the edge cases as they arrive, unfiltered, and label each one rule or exception. Do not tidy them up yet — the mess is evidence.
2. Write one acceptance criterion per rule, each with an assertion that can fail. Split anything containing the word except.
3. Fill the worked-example table, boundary rows first: on the limit, on the lock date, on the rounding edge, on the self-approval case.
4. Log every question the table could not answer, with an owner, a date and the default the code will ship if no answer arrives.
5. Only then start the agent, and point it at the spec file rather than pasting the requirement into a prompt.

The step that gets skipped under deadline is the fourth, and skipping it is the whole difference between a module with three documented open decisions and a module with three undocumented ones. Both ship on the same day. Only one of them can be audited afterwards.

Spec-driven development in ERP is not a prompting technique. It is the old requirements discipline — one testable criterion per rule, examples over prose, questions recorded against a named owner — done before an agent is allowed to write anything, because an agent will cheerfully implement an ambiguity instead of reporting it. Write the spec so the failing test comes first, and the implementation stops being the hard part.

## Sources and further reading

- GitHub Spec Kit — the spec-driven development toolkit and its specify, plan, tasks and implement phases
- Cucumber — Introducing Example Mapping: rules, examples and questions as separate cards
- Gherkin Reference — the Rule keyword, Scenario Outline and the Examples table
- Odoo 18 Accounting documentation — year-end closing, the Lock Everything date and the Hard Lock date
- Wikipedia — Software requirements specification, including the ISO/IEC/IEEE 29148 characteristics

## Source links

- https://github.com/github/spec-kit
- https://cucumber.io/blog/bdd/example-mapping-introduction/
- https://cucumber.io/docs/gherkin/reference/
- https://www.odoo.com/documentation/18.0/applications/finance/accounting/reporting/year_end.html
- https://en.wikipedia.org/wiki/Software_requirements_specification

## FAQ

### What is spec-driven development for ERP requirements?

It means writing the requirement down as testable rules before an AI agent writes any code, rather than describing the requirement in a prompt. For ERP that spec has three parts: one acceptance criterion per business rule, a worked-example table with real inputs and expected outputs, and a log of the questions the spec could not answer. The agent implements against the spec, and the spec is what gets reviewed and kept.

### How do I write an acceptance criterion for an ERP approval rule?

Write it so that a test can fail today and pass once the rule is implemented. Name real people rather than roles, state one assertion per line, and include the delegation window with its start and end dates. If you cannot express the assertion in a single clause, you are looking at two rules and should split them.

### Why does an ERP spec need a worked-example table?

Because prose about approval limits gets nodded through in review while a concrete row gets argued about, and the argument is the point. Fill the boundary rows first: the amount sitting exactly on the limit, the approver who is also the requester, the invoice dated on the day the period locks. A table of comfortable mid-range examples reviews quickly and proves nothing.

### What goes in an ambiguity log and who owns it?

Each entry holds the question, a named person who can answer it, the date it was asked, and the default behaviour the code will ship if no answer arrives. Naming a department instead of a person means nobody owns it. The default matters most, because it turns an open question into shippable behaviour plus a recorded risk, and the test asserts that default so the change is obvious when the real answer comes back.

### Should I review the prompt or the spec when an agent builds an ERP module?

Review the spec. A prompt is discarded when the session ends, while the spec is read months later by whoever is auditing a number that came out wrong. Keep the spec file in the repository next to the module it governs, so a change to a business rule shows up as a diff you can put in front of a finance person.

---

- HTML version: https://www.matthewswong.com/en/blog/spec-driven-development-erp-requirements/
- Bahasa Indonesia: https://www.matthewswong.com/id/blog/spec-driven-development-erp-requirements/
- Site index for agents: https://www.matthewswong.com/llms.txt
- Sitemap: https://www.matthewswong.com/sitemap.xml
- API description (OpenAPI): https://www.matthewswong.com/openapi.json
