Key takeaways
- A first release needs one observable user result, not a catalogue of features.
- Scope is strongest when exclusions are written beside inclusions.
- Records and states reveal hidden decisions before interface work disguises them.
- Failure handling and verification belong in the plan, not in the final afternoon.
A small software project becomes releasable when its decisions agree with one another. The outcome describes what changes for a user. The boundary says which path produces that change. Data gives the path memory. State constrains what can happen next. Failure rules answer interruptions. Verification supplies evidence that the path works. Miss one, and “nearly finished” can last for weeks.
The six passes below are not six phases with locked gates. They are lenses. A new state may expose missing data; a failure case may force a narrower boundary. Loop back. Keep the notes short enough to revise, yet precise enough that another person could challenge them. Record each revision while the reason is still fresh. That balance matters.
Pass 1: phrase an observable outcome
Begin with a sentence that can become either true or false after a short demonstration. “Organise requests” is an ambition. “A visitor records a request and later sees whether it is awaiting a decision” is observable. The second sentence contains an actor, an action, a persisted fact and a visible result. Good. It does not yet prescribe screens, tables or a typed language.
Use this pattern: [actor] can [complete action] and [observe result] under [named condition]. One invented project might read: “A club organiser can record a room request and see its current decision state after reopening the browser application.” The reopening condition tests whether the project has real memory rather than a temporary interface illusion.
Beware compound outcomes. If your sentence needs three “and” clauses, it probably conceals several releases. Split them and rank them. The first release should prove the riskiest useful loop, not accumulate every adjacent convenience.
Pass 2: draw the boundary around one path
Now list the steps a user must cross from blank start to visible result. For the room-request example: create requester label, choose date, submit request, reopen list, inspect status. Five moments. Each must either exist or be explicitly removed. This is where the one-slice scope method earns its keep.
Create a parallel exclusion ledger. Recurring requests, multiple venues, reminders, calendar views, delegated approval and account recovery can all be sensible later. Writing them down prevents two opposite mistakes: forgetting a useful idea and quietly pulling it into the current build. The ledger is a parking place, not a promise.
Scope is not the number of screens. It is the number of behaviours, states, records and failure paths the release agrees to support.
Count those hidden dimensions. A single request form with draft saving, attachment handling, conflict detection and reminders is not “one screen”; it is several systems wearing one coat.
Pass 3: sketch the smallest durable record
Write a sample record in plain text before choosing storage details. Give every field a reason. A request may need an identifier, owner label, requested date, note, status and creation time. Does it need an edited time? Only if later behaviour uses it. Does the status permit arbitrary text? No: later transitions rely on a closed set.
request = {
id: stable_identifier,
owner_label: "North room group",
requested_date: "2026-10-14",
note: null,
status: "awaiting_decision",
created_at: recorded_time
}
Then annotate invariants. The requested date must exist. Status must be one allowed value. The identifier never changes. A nullable note means “no note supplied”, not “field forgotten”. Ownership is deliberately a label in this tiny example; a later multi-person release would need a stable owner identifier. The record-planning note shows how identifiers, nullability and migration boundaries fit together.
One awkward question belongs here: what happens to records created by the first release after the shape changes? If the honest answer is “erase the illustrative data”, say so. If records must survive, reserve a schema revision and a transformation rule. Tiny projects acquire real data sooner than expected.
Pass 4: name states and legal transitions
Interfaces tempt us to think in controls: a green button, a grey badge, an edit link. State modelling asks the harder question first: what facts may be true, and which move is legal from each fact? For the request, perhaps awaiting_decision can become accepted, declined or withdrawn. An accepted request may later be cancelled, but it cannot return silently to awaiting a decision.
Draw arrows. Mark the actor permitted to trigger each arrow. Add a rejection for every impossible move. This catches contradictions before components spread them through the interface. See the booking-request transition table for a fuller worked example.
Derived display labels should follow state, never replace it. “Waiting” may be friendly copy, but the stored state needs an unambiguous internal meaning. Likewise, disabled controls are not a security or integrity rule; the behaviour layer must reject an illegal transition even when a malformed request bypasses the visible interface.
Pass 5: give failures different answers
“Show an error” is not a failure design. Separate at least four categories. Expected rejection means the system is healthy but the input or transition is not allowed. Dependency failure means a required datastore or message channel is unavailable. Partial completion means one write happened and a later action did not. Retryable failure means repeating the same safe action might succeed.
Each category needs a user message, a log detail and a retry policy. Those outputs should not be identical. The visitor needs a safe next step, not an internal record key or stack trace. The maintainer needs enough context to diagnose the fault without receiving secrets. The failure decision matrix pairs those concerns.
Idempotency becomes relevant even in a small project. If a visitor presses again after an uncertain response, can the system create the same request twice? A client-generated attempt key or a uniqueness rule may prevent duplicates. That adds complexity, so use it only where repeated work would cause real confusion.
Pass 6: map checks to release risks
Verification starts with acceptance examples in ordinary language. Given no records, when a valid request is submitted, then one awaiting request appears after reopening. Given an accepted request, when withdrawal is attempted, then the move is rejected and the stored state is unchanged. These examples join user intent to observable evidence.
Unit checks belong around dense decisions: date rules, legal transitions, message selection. Integration checks cross boundaries you do not fully control: serialisation, datastore writes, clock handling. A short release smoke check proves the assembled path in its deployed setting. Our risk-based test-budget note calculates how a small allowance can cover those layers without pretending every line deserves equal effort.
A test count is not the goal. Coverage of meaningful risk is. Five carefully chosen examples can reveal more than fifty mirrored implementation checks, especially when the release has one narrow path.
The release card: the original asset
Compress the six passes onto one review card. If a row cannot fit into two short lines, the decision probably remains fuzzy. Here is the completed card for the invented room-request project.
| Pass | Decision | Proof at release |
|---|---|---|
| Outcome | Organiser records one room request and later sees its decision state. | Demonstrate create, reopen and inspect. |
| Boundary | One venue, single requests, one decision role; no reminders or recurring dates. | Exclusion ledger reviewed. |
| Data | Stable ID, owner label, date, nullable note, closed status, creation time. | Stored record survives restart. |
| State | Awaiting to accepted, declined or withdrawn; accepted to cancelled. | Illegal arrows are rejected. |
| Failure | Validation, unavailable storage and uncertain repeat each have separate handling. | Three induced examples show safe messages. |
| Verification | Four acceptance examples, six rule checks, two integration checks, one smoke route. | Results recorded against release revision. |
Notice what is absent: architecture theatre, product selection and promises about future scale. The card serves one release. Should the project gain multiple venues or delegated approval, rewrite the relevant rows rather than stretching old assumptions until they snap.
Run one final handover check with a reader who did not design the project. They should be able to identify the useful path, excluded work, allowed state changes, stored evidence and release checks from the card alone. Any answer that depends on private memory belongs on the card before release.
Honest limitation
This framework favours a small, reversible information system. It does not supply threat modelling, formal assurance, capacity engineering, accessibility testing depth or statutory controls. Safety-critical, regulated, high-scale, real-time, medical, financial and security-critical systems need specialist methods and independent review.