Skip to content
Engineering Process

How to Build a Decision-Gated Software Delivery Workflow

A hands-on guide to organizing software requirements, decisions, calculations, TODOs, tests, and request flows before implementation starts.

CodingBowl··9 min read
Photo by Campaign Creators on Unsplash

How to Build a Decision-Gated Software Delivery Workflow

Decision-Gated Engineering is useful when a software task has more uncertainty than its ticket suggests. A new dashboard may depend on an undefined metric. A filter may interact with permissions. A “small” UI change may require a new query, empty state, and accessibility behavior.

This article shows how to turn that uncertainty into a working repository process. The approach is simple: inspect evidence, resolve material decisions, plan the work, split it into testable leaves, then record how the finished request flows through the application.

Start with a feature directory

Create one directory for the module or feature. Keep source requirements separate from engineering records:

_docs/inventory-analysis/
├── docs/
│   ├── specs/
│   ├── calculations/
│   └── flows/
└── plans/
    ├── inventory-analysis-plan.md
    ├── inventory-analysis-decisions.md
    ├── inventory-analysis-todo.md
    ├── stock-velocity-plan.md
    ├── stock-velocity-decisions.md
    └── stock-velocity-todo.md

Use a sub-module directory or its own plan when the sub-module has independent decisions, dependencies, or delivery work. Keep filenames predictable: inventory-analysis-decisions.md, not a mixture of underscores, spaces, and abbreviations.

Inspect the existing system before making decisions

Begin the plan with evidence, not an implementation guess. Record the files, models, endpoints, queries, and tests you inspected:

## Existing system and evidence

- `inventory/views.py`: current list view and query parameters
- `inventory/services.py`: stock movement aggregation
- `inventory/models.py`: date and warehouse relationships
- `inventory/tests/test_services.py`: existing aggregation coverage
- `templates/inventory/detail.html`: current empty-state behavior

For a calculation, compare the requirement with the code. For example, if the requirement says “average daily demand over the selected period,” check whether the implementation divides by calendar days, days with activity, or the number of records. Those are different products, not merely different formulas.

Record decisions as gates

Use one row or section per question. A decision record should make it possible for another engineer to understand why the implementation uses a particular rule:

### D-004 — What is the denominator for average daily demand?

Status: Accepted

Impact: Changes the displayed metric and its zero-activity behavior.

Evidence: The requirement says calendar days. The current service divides by
days with movement, which overstates demand for intermittent products.

Recommendation: Divide by the inclusive number of calendar days in the filter.

Final decision: Use calendar days; return zero when the range is invalid or
there are no demand records.

Dependent work: TODO-07, TODO-09, and the demand calculation tests.

Do not hide an unresolved choice inside a TODO. A recommendation is not an accepted behavior until the status changes to Accepted. If a decision is rejected or superseded, preserve that history so future contributors do not reopen the same question without new evidence.

Make calculations executable and testable

Put the formula in a calculation note and make the edge cases explicit before writing the view:

from datetime import date


def average_daily_demand(total_units, start: date, end: date):
    if end < start:
        return 0

    calendar_days = (end - start).days + 1
    return total_units / calendar_days if calendar_days else 0

The calculation record should also contain worked examples:

1,000 units from 2026-01-01 through 2026-01-10
calendar days = 10
average daily demand = 100

Then test the boundaries that caused the decision: a one-day range, an invalid range, zero units, missing records, and a range that crosses a month or year boundary. A formula without these tests is still an assumption.

Split the plan into atomic delivery leaves

An implementation TODO should be independently testable. A useful task names the behavior and its evidence:

- [ ] TODO-07: Implement calendar-day demand calculation
  - Objective: return the accepted formula for a selected date range
  - Affected layers: service, unit tests, calculation documentation
  - Depends on: D-004 Accepted
  - Acceptance criteria:
    - Inclusive date ranges use calendar days
    - Invalid ranges return zero
    - Zero-unit ranges return zero
  - Focused tests: `test_average_daily_demand_*`
  - Completion evidence: test output and calculation note reference

If a TODO contains “update the backend, UI, permissions, tests, and docs,” it is probably an epic rather than a delivery leaf. Split it at a boundary where the result can be reviewed and tested without waiting for unrelated work.

Verify the request flow after implementation

Once the code works, document the path a request takes through the system:

GET /inventory/?warehouse=west&from=2026-01-01&to=2026-01-10
  -> validate query parameters
  -> check warehouse permission
  -> call inventory service
  -> calculate demand using calendar-day rule
  -> render table, empty state, or error state

The flow record should name the entry point, filters, validation, permission checks, view or controller, services, data sources, response states, accessibility behavior, return navigation, and verification tests. This is particularly valuable for bugs that occur only at the boundaries between layers.

A practical completion checklist

Before declaring the feature complete, check:

  • Every material product or technical question has an accepted decision.
  • Every TODO names its dependency decisions and acceptance criteria.
  • Calculations have formulas, examples, and edge-case tests.
  • Empty, unavailable, permission, and error states are implemented.
  • The request flow matches the code that shipped.
  • Tests and documentation provide completion evidence.

Decision-Gated Engineering is not a request to document every coding choice. It is a way to spend documentation effort where ambiguity creates rework. The practical rule is: if a decision can change scope, behavior, dependencies, or acceptance, make it visible before implementation.

Influence and methodology

The underlying approach for this framework is adapted from a method I learned from a colleague. I used AI to help analyze how the method works, organize its practices, and develop the name “Decision-Gated Engineering.” The implementation details and examples in this article are my interpretation of that approach, not a claim of co-authorship.