Software maturity
A project's maturity is declared, not inferred: task-plus.yml states the
level the project has reached on each ladder, and
task-plus enforces the practices
that level requires and reports the gaps. The ladders are the contract;
the enforcement details live in task-plus and are linked from here, so
this page stays true when a command changes shape.
There are two ladders rather than one, because how code reaches main and
how a release reaches a running system move independently: a project can
deploy every release and sense-check it while still committing straight to
main, and a trunk-based project can run blue-green. Each ladder is strict
— a level includes everything below it.
Code flow
How a change reaches main.
| Level | Name | Practice | task-plus enforces |
|---|---|---|---|
| 0 | Trunk | Commit on main; releases are cut from it |
nothing new |
| 1 | Branches | One branch per story, merged to main by a pull request on the repo's own forge, fast-forward or rebase; main is protected against direct pushes and is always releasable |
tp release refuses to run off main; tp pr opens the pull request for the current branch; the release commit itself goes through a pull request |
| 2 | Gated | CI runs the project's check task on every pull request; merge is blocked until it is green |
tp check confirms the forge has branch protection and a CI config for main |
The release is a write to main like any other, so it goes through a pull
request like any other. On a protected main, tp release puts the rolled
changelog on a release/vX.Y.Z branch and opens the pull request; a person
merges it, and a second tp release tags the merged commit and publishes.
No account is whitelisted for pushes, so the gate holds for everyone and
every commit on main has been through the same door. Learned on gobank's
first protected release, which Forgejo refused and tp release rolled
back; until task-plus ships this, a release on a protected main needs the
rule relaxed for that one push.
Level 1 is not about review. A solo project with an agent gains little from
a second pair of eyes on every merge; it gains a main that is deployable
at any moment, which the deployment ladder from level 2 upward depends on.
Review, when a project wants it, is a forge setting on top of level 2, not
a level of its own.
Deployment
How a release reaches a running system, and what that system promises while it happens. Each level is a deployment strategy paired with the reliability metrics it commits to; the metrics are what make the level testable rather than aspirational.
| Level | Name | Strategy | Downtime per upgrade | Data loss (RPO) | Recovery (RTO) | task-plus enforces |
|---|---|---|---|---|---|---|
| 0 | Manual | Deployed by hand when someone remembers | unmeasured | unmeasured | unmeasured | nothing |
| 1 | Released | Every story ships as a release that is deployed and sense-checked before the next story starts | the whole run restarts; state is not kept | everything since the run began | a fresh run | post_release deploy hook present; release not closed until the deploy reports |
| 2 | In place | Stop, replace, start on the same host; schema migrations are versioned and expand/contract; rollback to the previous release is rehearsed | the restart, measured and recorded per deploy (minutes) | none: every committed fact survives | restart time, or rollback time if the release is bad | a migration version table exists; the deploy log records downtime per upgrade |
| 3 | Zero downtime | Blue-green or rolling behind a load balancer; old and new versions serve together, so migrations must be compatible with both | none: every request during the deploy is served | none | seconds (switch back) | deploy log shows requests served across the switch |
| 4 | Multi-region | Level 3 in more than one region; the loss of a region is survived | none, including for a region outage | none | automatic failover | — |
Downtime is therefore not a separate concern bolted on to a deployment strategy: it is the metric that distinguishes the levels. A project at level 2 is allowed downtime, and says how much; moving to level 3 is the decision to stop allowing it, and that decision costs the ability to run a single process against the database.
Declaring a level
# task-plus.yml
maturity:
code: 1
deploy: 2
tp check reports each ladder's level and the practices still missing for
the declared level, and warns when a project exceeds a level it has not
declared (a project at deploy: 1 with branch protection on main is
probably at code: 2 and should say so). Raising a level is a story in the
project that adopts it, because each one changes the project's day-to-day
flow; lowering one is an ADR.
Where this is implemented, and the exact checks, is task-plus's maturity page. Until that ships, the table above is the specification it is built to.
Example: gobank
| Today | Next | Later | |
|---|---|---|---|
| Code | 0 | 1 — from the first ADR-0002 stage 2 story | 2 — CI on the Bytestone Forgejo |
| Deploy | 1 — every story deployed to Hetzner and sense-checked | 2 — stage 2, the demo resumes from its database and each later story is an upgrade drill | 3 — stage 8, several processes, blue-green; 4 — phase 4, CockroachDB across regions |
Downtime is accepted at deployment level 2 and recorded per upgrade, so there is a number to drive to zero when level 3 arrives.