Collaboration and Communication

A Software Statement of Work That Prevents Arguments

jrhaws October 24, 2026 16 min read

Here’s a statement of work that gets signed more often than anyone admits:

Contractor will develop firmware for the new sensor board, including drivers,
BLE, OTA updates, and testing. Estimated 12 weeks.Code language: plaintext (plaintext)

Both sides read it, both agree, and both have a different project in mind. Does "testing" mean unit tests, or someone in a lab with a spectrum analyzer? Does "OTA" include the phone app that sends the update? What happens when the boards arrive in week 5 instead of week 1? When is the work done, and who says so?

A good SOW answers those questions before they cost money. It protects both sides: the client knows exactly what they’re paying for, and the contractor knows exactly when they’ve finished. This post walks through each part, using a firmware project as the example, and ends with a way to check a SOW for the gaps that usually cause trouble.

This is about the engineering content of a SOW, not legal advice. Payment terms, IP ownership, liability, and warranty language usually live in a master services agreement, and someone qualified should review those.

TL;DR

  • Describe results, not activities. "Develop firmware" is an activity; "firmware release v1.0 that passes AT-01 to AT-08 on three Rev B boards" is a result.
  • Give every deliverable acceptance criteria with a method: test, demonstration, inspection, or analysis.
  • Tie each payment to a milestone, and each milestone to deliverables that can be accepted.
  • Give every assumption an owner and a consequence if it turns out false.
  • List exclusions. Anything not listed is open to argument.
  • Set an acceptance window and a change process.

Results, not activities

US federal contracting rules have a good test for this. FAR 37.602 asks agencies to describe the work "in terms of the required results rather than either ‘how’ the work is to be accomplished" or how many hours it takes, and to judge it "against measurable performance standards" (FAR 37.602). That advice works just as well for a twelve-week firmware job.

So a deliverable is something you can hand over and check:

Activity:     Develop BLE support.
Deliverable:  D2, BLE sensor service: firmware exposing the four characteristics
              in ICD rev C, with the source in the client's repository.Code language: plaintext (plaintext)

Activities ("support", "assist", "develop", "investigate") have no end. If you really are selling time, say so and sell it as time. Don’t dress hours up as a deliverable.

Acceptance criteria with a method

Each deliverable needs criteria that someone other than the author can check, and a stated way to check them. Systems engineers use four methods: test (measure it), demonstration (show it working), inspection (look at it), and analysis (calculate it). Naming the method settles how acceptance will happen before anyone has to argue about it.

For firmware, the criteria must say which hardware and how much of it:

D3, firmware update over BLE with rollback
  AC-3.1  test           An update from v1.0.0 to v1.1.0 finishes in under 90 s
                         on all three boards.
  AC-3.2  test           With power cut at 10 random points during an update,
                         the board boots the previous image every time.Code language: plaintext (plaintext)

"OTA works reliably" was the first draft of that. It sounds like a requirement, but nobody can tell when it’s met. The rewrite names the version pair, the time limit, the number of boards, and the failure case the client actually cares about. If your requirements are written well (see the EARS post), acceptance criteria can simply point at them.

Milestones and payments

Payments should follow acceptance, not the calendar. Each milestone groups deliverables, and its share of the price is paid once those deliverables are accepted:

Every payment traces back through a milestone and a deliverable to acceptance criteria with a verification method; assumptions that fail go through the change process; exclusions fence the scopeLeft to right: acceptance criteria, each with a method (test, demonstration, inspection, or analysis), belong to a deliverable; each deliverable belongs to one milestone; each milestone carries a share of the payment, and the shares add up to 100 percent. Below: each assumption has an owner and a consequence if it isn't met, which leads to the change process. Around everything, a dashed boundary labelled exclusions marks what is out of scope.Every dollar traces back to something you can checkIf any link is missing, that's where the argument will happen.IN SCOPE (exclusions list everything outside this line)acceptance criteriaAC-3.2: power cut at 10random points, bootsprevious image (test)deliverableD3: firmware updateover BLE with rollbackmilestoneM3, week 11payment30 %(all milestones: 100 %)method: test · demonstration · inspection · analysisEach deliverable has criteria; each milestone has a deliverable; payments add to 100 %.assumptionowner: client · boards by end of week 1if not metchange processwritten request, cost and dates, both sign
Each payment traces back through a milestone and a deliverable to criteria someone can check. Assumptions that break go through the change process.

The shares must add up to 100 percent, and every milestone needs at least one deliverable. A milestone with nothing to accept is a payment on a date.

Assumptions, with owners

Every estimate rests on assumptions. Writing them down is only half the job. Each one also needs an owner and a stated consequence if it isn’t met:

Assumption: The client supplies five Rev B boards and two debug probes by the
            end of week 1.
Owner:      client
If not met: Every milestone date moves by the length of the delay.Code language: plaintext (plaintext)

In firmware work, hardware arriving late is the assumption that breaks most often, and "boards will be available" with no owner and no consequence just postpones the argument. Other common ones: the interface definition is frozen by a given week, the vendor SDK version stays fixed, and the contractor gets access to the client’s repositories and CI in week 1.

Exclusions

A short list of what’s not included prevents most scope creep, because it gets discussed at signing instead of in week 9:

- Mobile app and cloud backend
- Regulatory and Bluetooth qualification testing
- Hardware changes or board rework
- Production test fixtures and manufacturing scriptsCode language: plaintext (plaintext)

The things that sit next to the work, such as the phone app on the other end of BLE, certification, and manufacturing, are the most important to exclude.

Acceptance windows and changes

After each delivery, the client gets a fixed number of business days to run the acceptance criteria. Defects go back in writing, each one naming the criterion it fails. If nobody responds, many contracts treat the delivery as accepted, which keeps an unreviewed delivery from holding the invoice forever. Check how your contract handles that.

After each delivery the client has a fixed window to run the acceptance criteria; it ends in acceptance, a written defect list that restarts the cycle, or deemed acceptance if there is no responseDelivery on day zero starts a ten business day acceptance window. The client runs the acceptance criteria. Three outcomes: all pass, so the deliverable is accepted and the milestone can be invoiced; defects are listed in writing against specific criteria, the contractor fixes and redelivers, and a new window starts; or there is no response within the window, and the deliverable is deemed accepted, if the contract says so.Acceptance has a clock, and three ways to endWithout a window, a delivery can sit unreviewed while the invoice waits.deliveryday 0client runs theacceptance criteriawithin 10 business daysall passaccepted: invoice the milestonedefects, in writingfix, redeliver, new windowno responsedeemed accepted (if the contract says so)each defect names the criterion it fails
Each delivery starts a clock. Defects must name the criterion they fail, so "I don’t like it" isn’t a defect.

Changes need a process too, even a light one: a written request, a reply with the cost and schedule impact within a few days, and no work until both sides sign. That turns "can you also…" into a decision rather than unpaid work.

Keep the SOW as data, and check it

If you write SOWs often, keep the parts that matter as structured data and check them like code. Here’s a typical first draft as YAML, run through a small checker:

deliverables:
  - id: D1
    title: Firmware for the sensor board
    acceptance:
      - id: AC-1
        criterion: Firmware works properly on the board
  - id: D2
    title: BLE and OTA support
    milestone: M2
    acceptance:
      - id: AC-2
        criterion: BLE is stable and OTA is robust
        method: test
milestones:
  - {id: M1, week: 4, payment_pct: 40}
  - {id: M2, week: 10, payment_pct: 50}
assumptions:
  - text: Boards will be availableCode language: YAML (yaml)
$ ./sow_check.py sow-draft.yaml; echo "exit $?"
D1/AC-1: method must be one of ['analysis', 'demonstration', 'inspection', 'test']
D1/AC-1: vague: 'works'
D1/AC-1: vague: 'properly'
D1: not tied to a milestone
D2/AC-2: vague: 'robust'
D2/AC-2: vague: 'stable'
milestone payments add up to 90%, not 100%
M1: no deliverable, so nothing to accept before paying
assumption 'Boards will be available': no owner
assumption 'Boards will be available': no 'if_not_met'
no exclusions: everything not listed is arguable
no acceptance window: delivery can sit unreviewed forever
2 deliverables, 12 problem(s)
exit 1Code language: Shell Session (shell)

Every line is a conversation you’d otherwise have later, at a worse time. The full SOW for this project, with four deliverables, eight acceptance criteria, four milestones, four assumptions, the exclusions, a ten-day acceptance window, and a change process, passes:

$ ./sow_check.py sow.yaml; echo "exit $?"
4 deliverables, 0 problem(s)
exit 0Code language: Shell Session (shell)

The checker can’t tell you whether 90 seconds is the right limit or whether twelve weeks is realistic. It makes sure every part of the chain exists, so the real discussion is about the numbers, not about what was meant.

Wrap-up

  • Write deliverables as results someone can hand over, not activities.
  • Give each deliverable acceptance criteria with a method, and name the hardware they run on.
  • Pay on accepted milestones, and make the shares add up to 100 percent.
  • Give every assumption an owner and a consequence, and list what’s excluded.
  • Set an acceptance window and a change process before work starts.

What’s the one line you wish had been in the last SOW you signed?


Keep going

If you want to go further with requirements, and trace them from the SOW through the design to the tests, that’s where The Model Is Not the Code picks up, the book I’m writing on SysML v2 for software engineers.

If you have a project to scope now, the Requirements & SOW Package delivers a SOW with deliverables, acceptance criteria, assumptions, and exclusions, plus up to 60 testable requirements with rationale and a verification method for each. See the Services page or get in touch.

Tags consulting project management requirements statement of work

Leave a Reply

Your email address will not be published. Required fields are marked *