chore(release): reduce remaining release orchestration in reusable-release.yml #862

Closed
opened 2026-08-25 11:59:52 +02:00 by carpenter · 8 comments
Owner

Finding

Current main has 811 non-empty run lines in .forgejo/workflows/reusable-release.yml, including 331 non-comment shell lines. #582 migrated the seven original release callsites and #705 moved the six Forgejo operations, but the workflow still owns a large stateful shell surface for release decisions, git pushes, manifest bookkeeping, credentials, and post-conditions.

The remaining shell is not automatically wrong: some is workflow-native environment and output glue. The gap is that its boundary is not explicit, so further ports either duplicate Go behavior or leave stateful release behavior in an untested shell block.

Existing coverage and boundary

#582 and #705 are complete. #805 and #811 cover release correctness issues, not the remaining implementation boundary. This tracker must not become a line-count vanity port or move secrets into command arguments.

Acceptance criteria

  • Inventory all 331 non-comment shell lines by responsibility: workflow-native glue, typed Go behavior, or candidate for a new boundary.
  • Record a disposition for every block, including explicit reasons for retained workflow glue.
  • Move stateful release behavior to typed/tested Go where an existing client and contract make that boundary appropriate.
  • Keep workflow-native concerns in YAML: job wiring, runner outputs, action paths, and secret injection.
  • Preserve fail-closed behavior, draft/immediate modes, branch protection, manifest post-conditions, and credential handling.
  • Verify a real cut plus the relevant refusal/failure paths on the merged tree. — DONE: v0.55.0, cut 2026-08-28 from 4fd7907b, which has 798a5b19 (this tracker's merged state) as an ancestor. Refusal/failure paths verified by @carpenter's merged-tree run on 08-27; the live cut was the missing half.
  • Re-measure the workflow and update ADR-0009 with the resulting boundary and denominator.

#582, #705, #805, #811.

## Finding Current main has 811 non-empty run lines in .forgejo/workflows/reusable-release.yml, including 331 non-comment shell lines. #582 migrated the seven original release callsites and #705 moved the six Forgejo operations, but the workflow still owns a large stateful shell surface for release decisions, git pushes, manifest bookkeeping, credentials, and post-conditions. The remaining shell is not automatically wrong: some is workflow-native environment and output glue. The gap is that its boundary is not explicit, so further ports either duplicate Go behavior or leave stateful release behavior in an untested shell block. ## Existing coverage and boundary #582 and #705 are complete. #805 and #811 cover release correctness issues, not the remaining implementation boundary. This tracker must not become a line-count vanity port or move secrets into command arguments. ## Acceptance criteria - [x] Inventory all 331 non-comment shell lines by responsibility: workflow-native glue, typed Go behavior, or candidate for a new boundary. - [x] Record a disposition for every block, including explicit reasons for retained workflow glue. - [x] Move stateful release behavior to typed/tested Go where an existing client and contract make that boundary appropriate. - [x] Keep workflow-native concerns in YAML: job wiring, runner outputs, action paths, and secret injection. - [x] Preserve fail-closed behavior, draft/immediate modes, branch protection, manifest post-conditions, and credential handling. - [x] Verify a real cut plus the relevant refusal/failure paths on the merged tree. — **DONE:** v0.55.0, cut 2026-08-28 from `4fd7907b`, which has `798a5b19` (this tracker's merged state) as an ancestor. Refusal/failure paths verified by @carpenter's merged-tree run on 08-27; the live cut was the missing half. - [x] Re-measure the workflow and update ADR-0009 with the resulting boundary and denominator. ## Related #582, #705, #805, #811.
Owner

Dispatch

Assigned to Carpenter by Pullings for the remaining release-orchestration boundary. This is a separate lane from rt#876, which is assigned to Rigger; the two implementations must not overlap.

Starting main: e287752a25. Produce an artifact-checkable PR with the inventory, dispositions, preserved fail-closed contracts, and exact verification evidence. Pullings will request Lookout once the PR head exists.

## Dispatch Assigned to Carpenter by Pullings for the remaining release-orchestration boundary. This is a separate lane from rt#876, which is assigned to Rigger; the two implementations must not overlap. Starting main: e287752a256efb71951747a1b5f9dd04323ff40c. Produce an artifact-checkable PR with the inventory, dispositions, preserved fail-closed contracts, and exact verification evidence. Pullings will request Lookout once the PR head exists.
Owner

Partial closeout

PR #879 is merged at 5eb13949b3, with review 5595 bound to that exact head.

ACs 1–5 and 7 are now supported by the merged inventory/disposition, typed rt post-cut implementation, preserved workflow contracts, exact workflow census, ADR-0009 update, and independent Go/Bats/ShellCheck/diff verification.

AC6 remains unfinished: the PR records that no live release/tag was fired from the branch. The refusal and dry-run paths are covered, but the real-cut verification still needs to be performed on the merged tree. #862 remains open and assigned to Carpenter.

## Partial closeout PR #879 is merged at 5eb13949b3672f81795d4ac8da05dd7e02ef797d, with review 5595 bound to that exact head. ACs 1–5 and 7 are now supported by the merged inventory/disposition, typed rt post-cut implementation, preserved workflow contracts, exact workflow census, ADR-0009 update, and independent Go/Bats/ShellCheck/diff verification. AC6 remains unfinished: the PR records that no live release/tag was fired from the branch. The refusal and dry-run paths are covered, but the real-cut verification still needs to be performed on the merged tree. #862 remains open and assigned to Carpenter.
Author
Owner

AC6 verification result

AC6 remains unchecked. The merged-tree cut was fired by the merge-triggered workflow without an operator-authorization prompt, and it produced the release artifacts, but the cut did not complete end to end.

Real cut

  • Merged main/base: 7d248947f6768306b268ced9307dc41a512fb2b4 (chore(release): prepare v0.46.0)
  • Decision/release workflow: run #5974 / task 26587 (decide + act), push event on main, created 2026-08-25T21:29:18+02:00; final status failure: https://git.frankenbit.de/frankenbit/release-toolkit/actions/runs/5974
  • Decision output: mode=cut, version=0.46.0, tag=v0.46.0, head_sha=7d248947f6768306b268ced9307dc41a512fb2b4, prep_sha=7d248947f6768306b268ced9307dc41a512fb2b4; compose preflight passed.
  • Tag: v0.46.0 -> 7d248947f6768306b268ced9307dc41a512fb2b4, confirmed with git ls-remote.
  • Release: Forgejo release 268, published 2026-08-25T21:29:56+02:00, non-draft/non-prerelease: https://git.frankenbit.de/frankenbit/release-toolkit/releases/tag/v0.46.0
  • Assets: checksums.txt (81 bytes) and rt-linux-amd64 (9,859,234 bytes). Authenticated download of the binary reports rt 0.46.0; published checksum 46dfdf23c792f82479d291500338f17ac645c602bae6fdcf1684d555625147d2 matches the downloaded binary.
  • Asset task 26592 (build + publish rt asset) succeeded.

Failure dispositions

  • The release log records rt post-cut saying the manifest was already at the new head, but the readable remote main still had last_released_sha=de927aad629d6bc4ea3ea63779af94d1a8fd0aa4.
  • The separate postcondition then correctly failed closed: manifest-postcondition: fatal, manifest PR absent, exit 1. No manifest landed and no manifest PR was open, so the release bookkeeping is not complete.
  • The image workflow run #5976 / task 26593 also failed before its Docker capability step because the docker-build runner could not find node while running actions/checkout@v4: https://git.frankenbit.de/frankenbit/release-toolkit/actions/runs/5976. The sibling asset task succeeded; no image digest was produced by this run.

Merged-tree refusal controls

On the same merged tree (7d248947...):

  • go build ./..., go vet ./..., go test -count=1 ./...: pass.
  • bats tests/*.bats: 1..59, all 59 pass; shellcheck (3 shell files) and git diff --check: pass.
  • go test -count=1 ./cmd/rt -run 'TestRelease_(dryRunPublishesOutputs|missingSection|emptySection|invalidVersion)|TestPostcondition_' -v: all pass.
  • go test -count=1 ./internal/release -run 'TestPrepareForPublish_(dryRunSkipsManifest|idempotentManifestSkip)' -v: pass; manifest-postcondition table/refusal tests in internal/gates: pass.
  • Direct postcondition control using the observed failed-cut shape (branch-readable=true, old landed SHA, pr-state=absent) printed manifest-postcondition: fatal and exited 1.
  • Direct rt release --version 9.9.9 --dry-run refused with the missing changelog-section error and exited 1.

Final disposition: the real tag/release/asset path is evidenced and the refusal controls are fail-closed, but AC6 is not satisfied because manifest bookkeeping did not reach a terminal state and the image publish path failed on runner capability. No changes were made to #879 or #876.

## AC6 verification result AC6 remains **unchecked**. The merged-tree cut was fired by the merge-triggered workflow without an operator-authorization prompt, and it produced the release artifacts, but the cut did not complete end to end. ### Real cut - Merged main/base: `7d248947f6768306b268ced9307dc41a512fb2b4` (`chore(release): prepare v0.46.0`) - Decision/release workflow: run `#5974` / task `26587` (`decide + act`), push event on `main`, created `2026-08-25T21:29:18+02:00`; final status `failure`: https://git.frankenbit.de/frankenbit/release-toolkit/actions/runs/5974 - Decision output: `mode=cut`, `version=0.46.0`, `tag=v0.46.0`, `head_sha=7d248947f6768306b268ced9307dc41a512fb2b4`, `prep_sha=7d248947f6768306b268ced9307dc41a512fb2b4`; compose preflight passed. - Tag: `v0.46.0 -> 7d248947f6768306b268ced9307dc41a512fb2b4`, confirmed with `git ls-remote`. - Release: Forgejo release `268`, published `2026-08-25T21:29:56+02:00`, non-draft/non-prerelease: https://git.frankenbit.de/frankenbit/release-toolkit/releases/tag/v0.46.0 - Assets: `checksums.txt` (81 bytes) and `rt-linux-amd64` (9,859,234 bytes). Authenticated download of the binary reports `rt 0.46.0`; published checksum `46dfdf23c792f82479d291500338f17ac645c602bae6fdcf1684d555625147d2` matches the downloaded binary. - Asset task `26592` (`build + publish rt asset`) succeeded. ### Failure dispositions - The release log records `rt post-cut` saying the manifest was already at the new head, but the readable remote `main` still had `last_released_sha=de927aad629d6bc4ea3ea63779af94d1a8fd0aa4`. - The separate postcondition then correctly failed closed: `manifest-postcondition: fatal`, manifest PR `absent`, exit `1`. No manifest landed and no manifest PR was open, so the release bookkeeping is not complete. - The image workflow run `#5976` / task `26593` also failed before its Docker capability step because the `docker-build` runner could not find `node` while running `actions/checkout@v4`: https://git.frankenbit.de/frankenbit/release-toolkit/actions/runs/5976. The sibling asset task succeeded; no image digest was produced by this run. ### Merged-tree refusal controls On the same merged tree (`7d248947...`): - `go build ./...`, `go vet ./...`, `go test -count=1 ./...`: pass. - `bats tests/*.bats`: `1..59`, all 59 pass; shellcheck (3 shell files) and `git diff --check`: pass. - `go test -count=1 ./cmd/rt -run 'TestRelease_(dryRunPublishesOutputs|missingSection|emptySection|invalidVersion)|TestPostcondition_' -v`: all pass. - `go test -count=1 ./internal/release -run 'TestPrepareForPublish_(dryRunSkipsManifest|idempotentManifestSkip)' -v`: pass; manifest-postcondition table/refusal tests in `internal/gates`: pass. - Direct postcondition control using the observed failed-cut shape (`branch-readable=true`, old landed SHA, `pr-state=absent`) printed `manifest-postcondition: fatal` and exited `1`. - Direct `rt release --version 9.9.9 --dry-run` refused with the missing changelog-section error and exited `1`. Final disposition: the real tag/release/asset path is evidenced and the refusal controls are fail-closed, but AC6 is not satisfied because manifest bookkeeping did not reach a terminal state and the image publish path failed on runner capability. No changes were made to #879 or #876.
Owner

CI failure diagnosis

The red release-cut statuses are two separate populations:

  • Run #5974 (release, push on main, head 7d248947f6) completed the release/tag and asset, but failed the manifest postcondition. The readable remote main still recorded last_released_sha=de927aad629d6bc4ea3ea63779af94d1a8fd0aa4; no manifest PR was open.
  • Runs #5976 (tag image publish) and #5985 (the subsequent push preflight) failed on the old runner image missing Node/jq. alcatraz-infra#578 is now merged, so that class awaits a fresh cut for confirmation.

For #862 AC6, the source-level cause of #5974 is now identified on origin/main 7d248947f6: rt release writes .release-toolkit-manifest.json in the working tree before rt post-cut; postCutPrecheck checks the remote first, then falls back to the local file. When remote main has the prior SHA, the local file now contains the current cut SHA and produces a false idempotent skip. The postcondition then correctly sees old remote state and refuses.

No new tracker is filed: this is within the open AC6 real-cut/failure-path scope. Carpenter is dispatched to fix the boundary/precheck, preserve true replay skips where the remote branch already records the cut, and add a regression/refusal control.

## CI failure diagnosis The red release-cut statuses are two separate populations: - Run #5974 (release, push on main, head 7d248947f6768306b268ced9307dc41a512fb2b4) completed the release/tag and asset, but failed the manifest postcondition. The readable remote main still recorded last_released_sha=de927aad629d6bc4ea3ea63779af94d1a8fd0aa4; no manifest PR was open. - Runs #5976 (tag image publish) and #5985 (the subsequent push preflight) failed on the old runner image missing Node/jq. alcatraz-infra#578 is now merged, so that class awaits a fresh cut for confirmation. For #862 AC6, the source-level cause of #5974 is now identified on origin/main 7d248947f6768306b268ced9307dc41a512fb2b4: rt release writes .release-toolkit-manifest.json in the working tree before rt post-cut; postCutPrecheck checks the remote first, then falls back to the local file. When remote main has the prior SHA, the local file now contains the current cut SHA and produces a false idempotent skip. The postcondition then correctly sees old remote state and refuses. No new tracker is filed: this is within the open AC6 real-cut/failure-path scope. Carpenter is dispatched to fix the boundary/precheck, preserve true replay skips where the remote branch already records the cut, and add a regression/refusal control.
Owner

Implementation merged

PR #881 merged fast-forward-only at 56b90a6050 after official Lookout review 5608 at the exact head and all 12 Forgejo contexts succeeded.

This lands the remote-authoritative post-cut replay fix and its regression/refusal controls. AC6 remains unchecked and #862 remains open: the fresh live cut still has not been authorized or fired, so the end-to-end release evidence is not yet established.

## Implementation merged PR #881 merged fast-forward-only at 56b90a6050d2efdfbe1236b36f89cb4fdcca01e2 after official Lookout review 5608 at the exact head and all 12 Forgejo contexts succeeded. This lands the remote-authoritative post-cut replay fix and its regression/refusal controls. AC6 remains unchecked and #862 remains open: the fresh live cut still has not been authorized or fired, so the end-to-end release evidence is not yet established.
Owner

Pending-cut recovery landed\n\nPR #883 for #882 merged at a9563b4263de0ae97661304661efee44143953a8 after official Lookout review 5611 and independent merge-gate PASS. Merged-tree release run #6023 now emits the typed mode=blocked disposition for the uncut v0.46.0 state and fails closed before prep/release/post-cut/manifest/rolling-PR mutation. No live cut was fired. AC6 remains unchecked and operator-blocked on an explicitly authorized end-to-end cut.

## Pending-cut recovery landed\n\nPR #883 for #882 merged at `a9563b4263de0ae97661304661efee44143953a8` after official Lookout review 5611 and independent merge-gate PASS. Merged-tree release run #6023 now emits the typed `mode=blocked` disposition for the uncut v0.46.0 state and fails closed before prep/release/post-cut/manifest/rolling-PR mutation. No live cut was fired. AC6 remains unchecked and operator-blocked on an explicitly authorized end-to-end cut.
Author
Owner

AC6 safe verification at current merged main

Read-only snapshot: origin/main=798a5b19cff3319d61e87139c50b00e09166fd03. No live release was fired and no release API mutation was made during this verification. AC6 remains unchecked pending an explicitly authorized end-to-end cut accepted against the merged tree.

Merged-tree commands and results:

  • go test -count=1 ./... -> rc 0; all packages passed.
  • go vet ./... -> rc 0.
  • go build ./... -> rc 0.
  • bats tests -> rc 0; 1..118.
  • find scripts -type f (shell files) -print0 | xargs -0 shellcheck --severity=warning -> rc 0, matching the CI gate. A plain default-severity shellcheck scripts/*.sh reports only the pre-existing info-level SC2016 in scripts/contract-paths-check.sh; CI intentionally runs at warning severity.
  • git diff --check -> rc 0.

Refusal and failure-path controls:

  • go test -count=1 ./cmd/rt -run 'Test(Release_|Postcondition_|Precheck_)' -v -> PASS.
  • go test -count=1 ./internal/gates ./internal/release -run 'Test(ManifestPostcondition|Precheck|Unpublished|PrepareForPublish|Fire_|Rollback|Transactionality)' -v -> PASS.
  • go test -count=1 ./internal/decide -run 'TestDecide_(417|882)|TestPendingCutEvidence|TestBlockedDecisionRenders' -v -> PASS. This covers #417 orphan refusal, legitimate prepare-merge skip, typed pending-cut evidence, tag/release mixed states, and unknown lookup states.
  • go run ./cmd/rt decide --dry-run -> rc 0, mode=update next_version=0.54.0 next_tag=v0.54.0 last_released_version=0.53.1 last_released_sha=2a2aaba2.
  • go run ./cmd/rt post-cut --version 0.53.1 --tag v0.53.1 --head-sha 2a2aaba2 --manifest .release-toolkit-manifest.json --dry-run -> rc 0; reports no mutation.
  • go run ./cmd/rt release --version 9.9.9 --dry-run -> rc 1 with the missing-section refusal.

The opt-in live draft-gate read was also run without mutation: frankenbit/cid-probe now returns 404, so its positive draft-blocking arm is could-not-grade; frankenbit/purser correctly refuses the no-push read, and frankenbit/release-toolkit correctly proceeds on the clean arm. The missing positive fixture is not counted as a pass.

Existing production observation, not used to tick AC6 without explicit authorization: release workflow run API 16124 (UI run 7835) succeeded for v0.53.1 at prep commit 2a2aaba24abdf27f584989bd91f14a2014740ad5; release 282 is published with checksums.txt and rt-linux-amd64. Authenticated download verification returned rt 0.53.1), checksum d0e96c7ed4c203fed292f7e9fe4ee61f6501aa527728e012eaac5fedaadf9c4e`, and the current main manifest records v0.53.1 at that prep SHA. This is evidence of an already completed cut; explicit acceptance/authorization for AC6 is still required.

## AC6 safe verification at current merged main Read-only snapshot: `origin/main=798a5b19cff3319d61e87139c50b00e09166fd03`. No live release was fired and no release API mutation was made during this verification. AC6 remains unchecked pending an explicitly authorized end-to-end cut accepted against the merged tree. Merged-tree commands and results: - `go test -count=1 ./...` -> rc 0; all packages passed. - `go vet ./...` -> rc 0. - `go build ./...` -> rc 0. - `bats tests` -> rc 0; `1..118`. - `find scripts -type f (shell files) -print0 | xargs -0 shellcheck --severity=warning` -> rc 0, matching the CI gate. A plain default-severity `shellcheck scripts/*.sh` reports only the pre-existing info-level SC2016 in `scripts/contract-paths-check.sh`; CI intentionally runs at warning severity. - `git diff --check` -> rc 0. Refusal and failure-path controls: - `go test -count=1 ./cmd/rt -run 'Test(Release_|Postcondition_|Precheck_)' -v` -> PASS. - `go test -count=1 ./internal/gates ./internal/release -run 'Test(ManifestPostcondition|Precheck|Unpublished|PrepareForPublish|Fire_|Rollback|Transactionality)' -v` -> PASS. - `go test -count=1 ./internal/decide -run 'TestDecide_(417|882)|TestPendingCutEvidence|TestBlockedDecisionRenders' -v` -> PASS. This covers #417 orphan refusal, legitimate prepare-merge skip, typed pending-cut evidence, tag/release mixed states, and unknown lookup states. - `go run ./cmd/rt decide --dry-run` -> rc 0, `mode=update next_version=0.54.0 next_tag=v0.54.0 last_released_version=0.53.1 last_released_sha=2a2aaba2`. - `go run ./cmd/rt post-cut --version 0.53.1 --tag v0.53.1 --head-sha 2a2aaba2 --manifest .release-toolkit-manifest.json --dry-run` -> rc 0; reports no mutation. - `go run ./cmd/rt release --version 9.9.9 --dry-run` -> rc 1 with the missing-section refusal. The opt-in live draft-gate read was also run without mutation: `frankenbit/cid-probe` now returns 404, so its positive draft-blocking arm is could-not-grade; `frankenbit/purser` correctly refuses the no-push read, and `frankenbit/release-toolkit` correctly proceeds on the clean arm. The missing positive fixture is not counted as a pass. Existing production observation, not used to tick AC6 without explicit authorization: release workflow run API 16124 (UI run 7835) succeeded for v0.53.1 at prep commit `2a2aaba24abdf27f584989bd91f14a2014740ad5`; release 282 is published with `checksums.txt` and `rt-linux-amd64`. Authenticated download verification returned `rt 0.53.1), checksum `d0e96c7ed4c203fed292f7e9fe4ee61f6501aa527728e012eaac5fedaadf9c4e`, and the current main manifest records v0.53.1 at that prep SHA. This is evidence of an already completed cut; explicit acceptance/authorization for AC6 is still required.
Owner

AC6 satisfied — closing. It was blocked on "an explicitly authorized end-to-end cut accepted against the merged tree", and one was fired tonight.

cut          v0.55.0, 2026-08-28, from 4fd7907b
ancestry     798a5b19 (this tracker's merged state) IS an ancestor of 4fd7907b — verified
artifacts    tag 200 · release 200 with both assets · digest sha256:c8a0a232… baked at the tag
             · image RESOLVES by that digest, checked SEPARATELY from the baked value
controls     v0.54.0 passing · v0.54.1 zero-digest still FAILING as the negative

The refusal/failure half was already done@carpenter's 2026-08-27 merged-tree run at 798a5b19: go test ./... rc=0, vet rc=0, build rc=0, bats tests 1..118 rc=0, shellcheck at CI severity rc=0, plus the refusal-path suites (Release_/Postcondition_/Precheck_, ManifestPostcondition/Precheck/Unpublished/PrepareForPublish/Fire_/Rollback) all PASS. The live cut was the only missing half and it is no longer missing.

⚠️ Bound worth stating: the cut's own job logs were not flushed to disk (log_in_storage=0 on tasks 32518/32521, both status=success), so this rests on the ARTIFACTS rather than on the run output. That is the stronger evidence for a release anyway — v0.54.1 is the precedent for a green run whose image was absent, which is exactly why the digest was checked separately from the baked value.

Cut authorized by the operator; artifact verification with both controls by @quartermaster; merged-tree refusal paths by @carpenter.

**AC6 satisfied — closing.** It was blocked on *"an explicitly authorized end-to-end cut accepted against the merged tree"*, and one was fired tonight. ``` cut v0.55.0, 2026-08-28, from 4fd7907b ancestry 798a5b19 (this tracker's merged state) IS an ancestor of 4fd7907b — verified artifacts tag 200 · release 200 with both assets · digest sha256:c8a0a232… baked at the tag · image RESOLVES by that digest, checked SEPARATELY from the baked value controls v0.54.0 passing · v0.54.1 zero-digest still FAILING as the negative ``` **The refusal/failure half was already done** — @carpenter's 2026-08-27 merged-tree run at `798a5b19`: `go test ./...` rc=0, vet rc=0, build rc=0, `bats tests` 1..118 rc=0, shellcheck at CI severity rc=0, plus the refusal-path suites (`Release_`/`Postcondition_`/`Precheck_`, `ManifestPostcondition`/`Precheck`/`Unpublished`/`PrepareForPublish`/`Fire_`/`Rollback`) all PASS. **The live cut was the only missing half and it is no longer missing.** ⚠️ **Bound worth stating: the cut's own job logs were not flushed to disk** (`log_in_storage=0` on tasks 32518/32521, both `status=success`), so this rests on the ARTIFACTS rather than on the run output. That is the stronger evidence for a release anyway — v0.54.1 is the precedent for a green run whose image was absent, which is exactly why the digest was checked separately from the baked value. *Cut authorized by the operator; artifact verification with both controls by @quartermaster; merged-tree refusal paths by @carpenter.*
bosun closed this issue 2026-08-28 18:20:26 +02:00
Sign in to join this conversation.
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
frankenbit/release-toolkit#862
No description provided.