029 — tp maturity code changes level only when the repo is at rest (#181)

Status: in progress

Why

tp maturity code N writes the level and makes the forge agree on protection and merge style, and nothing else. The level alone picks the release process (ADR 0006), so changing it while a release is in flight strands work made under the old process: dropping to 0 leaves a release branch with stories merged, open pull requests into it, a .tp-release.yml and fragments that a trunk release never reads; rising to 1 leaves open story pull requests into main where a batch expects them into release. Moving in-flight work from one process to the other is a migration tp does not have, and each case would need its own. The cheap rule is the one tp release already lives by: the facts that pick the process change only when nothing is in flight.

What changes

  • tp maturity code N, with N different from the declared level: before touching the forge or task-plus.yml, reads the in-flight state and refuses while any of these holds: release exists on the release remote; .tp-release.yml exists in the checkout; a pull request is open whose head or base is release; a pull request is open from a story/<n>-<slug> branch. The error lists every item found and how to clear it (merge or close the pull request, finish the batch with tp release on the base branch, delete the file).
  • tp maturity code N at the declared level: as now — the protection and merge style are still checked and repaired; a repair is not a change of process.
  • -dry-run: the same refusal, under the (dry-run) prefix.
  • Without a forge API or token (allowed today for code 0): the remote release branch and .tp-release.yml are still checked through git; the pull requests are not, and the output says so.
  • tp maturity (show): one more line, at rest or in flight: … with the same items, so the refusal can be seen coming.
  • docs/maturity.md: the rule and the table rows below.

Acceptance

code N vs declared release on remote .tp-release.yml Open pull requests Outcome
same any any any as now: (unchanged); protection and merge style checked and made
differs no no none from story/*, none touching release as now: forge made to agree, level written
differs yes — — error release exists on origin: finish the batch (tp release on main) or delete it; nothing written, forge untouched
differs no yes — error naming .tp-release.yml; nothing written
differs no no #12 story/12-x → release error listing #12: merge or close it; nothing written
differs no no #13 release → main error listing #13: merge it and tag, or close it; nothing written
differs no no #14 story/14-y → main error listing #14: merge or close it; nothing written
differs, several items yes yes #12 one error listing all three
differs, -dry-run yes — — (dry-run) and the same error; nothing written
differs, no API or token yes — not checked error on release; the output says pull requests were not checked
differs, no API or token no no not checked level written, with a warning that pull requests were not checked
tp maturity Prints
nothing in flight at rest
release on origin and #12 open in flight: release on origin; pull request #12 (story/12-x → release)

Out of scope

  • Migrating in-flight work between processes: closing or retargeting pull requests, consuming fragments into the CHANGELOG, deleting release. The refusal names the human steps instead.
  • Removing the base branch's protection when lowering to 0 (unchanged: the protection is left and reported).
  • tp release query reporting the at-rest state: it already reports the position of each in-flight item.

Decisions

None expected. The rule — facts that pick the release process change only at rest — is shared with story 030, which applies it to opening a release branch.

Done when

  • tests express the acceptance table and pass
  • spec updated (maturity.md)
  • ADR written if a fork was resolved (none needed)
  • roadmap ticked and stamped; changelog entry
  • issue closed with a link to the release