Release v0.4.1 shipped with zero artifacts, so brew installs 0.3.0 on GA day #232

Closed
opened 2026-07-30 00:10:04 +00:00 by stephen · 4 comments
Owner

brew install fj on GA day installs 0.3.0, two releases behind, and the tap cannot be bumped because the newest release has no binaries.

Evidence

  • Release workflow run 11508, triggered by push on refs/tags/v0.4.1, failed at 2026-07-22T20:19:49Z. It has never been retried.
  • Consequently v0.4.1 is a published, non-draft release with 0 assets and an empty body. Compare v0.3.0, which has 5: SHA256SUMS, the three platform tarballs, and a generated fj.rb.
  • rasterstate/homebrew-tap Formula/fj.rb still pins version "0.3.0" with v0.3.0 download URLs, which is correct behaviour given there is nothing newer to point at.
  • There is no v0.4.0 tag at all; the tag sequence is v0.3.0 then v0.4.1.

Why this matters now

GA is announced and live. FJ_INSTALL_COMMAND in fjord-platform (src/lib/clients.ts:41) is the public install path, it is rendered on the CLI page, and the GA blog post reuses the same one-liner. Every person who follows the announcement to install the CLI today gets 0.3.0.

The compounding part: the GA blog post's fj capability list was rewritten to match the 0.4.x verb set. So the documented commands and the installable binary are from different releases. Whichever verbs landed after 0.3.0 are documented but absent for every new user.

Scope

  1. Fix whatever broke run 11508 and produce the v0.4.1 artifacts. Note the release job also generates fj.rb, so the tap bump is meant to fall out of a successful release rather than being hand-edited.
  2. Bump rasterstate/homebrew-tap to the shipped version with correct sha256 values, and verify a clean brew tap ... && brew install fj produces a binary whose fj --version matches.
  3. Reconcile the docs with reality: confirm which of the verbs documented in the GA blog post exist in the release users can actually install, and either ship a release that has them or correct the post.
  4. Guard it. A tag push whose release job fails currently leaves a published release with zero assets and nothing alerts. Either the release should not be published until artifacts upload, or a failed release run should be loud.

Also noticed, minor

FJ_INSTALL_COMMAND taps as rasterandstate/tap while the repo is rasterstate/homebrew-tap. It works because the explicit URL wins, but the alias is a typo and it is public-facing.

`brew install fj` on GA day installs **0.3.0**, two releases behind, and the tap cannot be bumped because the newest release has no binaries. ## Evidence - Release workflow run **11508**, triggered by `push` on `refs/tags/v0.4.1`, **failed** at `2026-07-22T20:19:49Z`. It has never been retried. - Consequently `v0.4.1` is a published, non-draft release with **0 assets** and an empty body. Compare `v0.3.0`, which has 5: `SHA256SUMS`, the three platform tarballs, and a generated `fj.rb`. - `rasterstate/homebrew-tap` `Formula/fj.rb` still pins `version "0.3.0"` with `v0.3.0` download URLs, which is correct behaviour given there is nothing newer to point at. - There is no `v0.4.0` tag at all; the tag sequence is `v0.3.0` then `v0.4.1`. ## Why this matters now GA is announced and live. `FJ_INSTALL_COMMAND` in fjord-platform (`src/lib/clients.ts:41`) is the public install path, it is rendered on the CLI page, and the GA blog post reuses the same one-liner. Every person who follows the announcement to install the CLI today gets 0.3.0. The compounding part: the GA blog post's `fj` capability list was rewritten to match the **0.4.x** verb set. So the documented commands and the installable binary are from different releases. Whichever verbs landed after 0.3.0 are documented but absent for every new user. ## Scope 1. Fix whatever broke run 11508 and produce the `v0.4.1` artifacts. Note the release job also generates `fj.rb`, so the tap bump is meant to fall out of a successful release rather than being hand-edited. 2. Bump `rasterstate/homebrew-tap` to the shipped version with correct `sha256` values, and verify a clean `brew tap ... && brew install fj` produces a binary whose `fj --version` matches. 3. Reconcile the docs with reality: confirm which of the verbs documented in the GA blog post exist in the release users can actually install, and either ship a release that has them or correct the post. 4. Guard it. A tag push whose release job fails currently leaves a published release with zero assets and nothing alerts. Either the release should not be published until artifacts upload, or a failed release run should be loud. ## Also noticed, minor `FJ_INSTALL_COMMAND` taps as `rasterandstate/tap` while the repo is `rasterstate/homebrew-tap`. It works because the explicit URL wins, but the alias is a typo and it is public-facing.
Author
Owner

Item 1: which documented verbs are missing from 0.3.0

None. No verb named in any GA post is absent from 0.3.0, so the blog needed neither a hold nor a copy change on verb grounds.

Method: built 0.3.0 from its tag and 0.4.x from main, walked the full --help tree of each (every subcommand and every nested subcommand), and diffed the command surfaces.

Top-level surface is byte-identical between the two. The only two differences anywhere in the tree:

verb 0.3.0 0.4.x named in any post?
fj run log absent present no
fj auth session absent present no

That is the whole diff. Every verb the posts actually instruct a reader to type (fj auth login, fj repo view, fj pr create, fj issue list, fj run list, fj api, and the rest) exists in 0.3.0 with the same flags.

The caveat that does matter, which is not a missing verb

meet-fj has a bullet promising you can watch Actions runs and "read their logs". On 0.3.0 that half-works, and the reason is #103 rather than a missing command:

Forgejo's Actions log and rerun routes are cookie-gated and reject a PAT. fj auth session exists in 0.4.x precisely to work around it, by importing an i_like_gitea cookie. Without it:

  • Public repositories: fine. Logs are readable.
  • Private repositories: not readable for a PAT user. Which is every paying Team and Pro customer, since private repos are the thing the tier is for.

So the exposure on GA day is narrower than "the post documents verbs that do not exist", but it is not nothing: the one bullet that does not hold on the installable version is a bullet aimed at exactly the customers who pay.

Two ways to close it

  1. Ship v0.4.1 artifacts (#233, and see below). Copy stays as written, fj auth session exists, logs work on private repos. This is the one I would take.
  2. Soften the bullet to "read their logs on public repositories". Accurate against 0.3.0, but it undersells 0.4.x and would want reverting once artifacts land.

Nothing else in the launch copy is affected either way.

## Item 1: which documented verbs are missing from 0.3.0 **None.** No verb named in any GA post is absent from 0.3.0, so the blog needed neither a hold nor a copy change on verb grounds. Method: built 0.3.0 from its tag and 0.4.x from `main`, walked the full `--help` tree of each (every subcommand and every nested subcommand), and diffed the command surfaces. **Top-level surface is byte-identical** between the two. The only two differences anywhere in the tree: | verb | 0.3.0 | 0.4.x | named in any post? | | --- | --- | --- | --- | | `fj run log` | absent | present | no | | `fj auth session` | absent | present | no | That is the whole diff. Every verb the posts actually instruct a reader to type (`fj auth login`, `fj repo view`, `fj pr create`, `fj issue list`, `fj run list`, `fj api`, and the rest) exists in 0.3.0 with the same flags. ### The caveat that does matter, which is not a missing verb `meet-fj` has a bullet promising you can watch Actions runs and "read their logs". On 0.3.0 that half-works, and the reason is #103 rather than a missing command: Forgejo's Actions **log and rerun routes are cookie-gated and reject a PAT.** `fj auth session` exists in 0.4.x precisely to work around it, by importing an `i_like_gitea` cookie. Without it: - Public repositories: fine. Logs are readable. - **Private repositories: not readable for a PAT user.** Which is every paying Team and Pro customer, since private repos are the thing the tier is for. So the exposure on GA day is narrower than "the post documents verbs that do not exist", but it is not nothing: the one bullet that does not hold on the installable version is a bullet aimed at exactly the customers who pay. ### Two ways to close it 1. **Ship v0.4.1 artifacts** (#233, and see below). Copy stays as written, `fj auth session` exists, logs work on private repos. This is the one I would take. 2. **Soften the bullet** to "read their logs on public repositories". Accurate against 0.3.0, but it undersells 0.4.x and would want reverting once artifacts land. Nothing else in the launch copy is affected either way.
Author
Owner

Item 2: does #233 republish the actual v0.4.1 artifacts, or only protect future tags?

It republishes the real ones. v0.4.1 gets its five assets, the tag does not move, and it keeps its 2026-07-22 date. Not a future-tags-only fix. The chain, with the part I verified at each link:

1. A dispatch is a new run, and that alone unwedges it. The collision that killed run 391 is keyed on the run id. From upload-artifact-action src/lib/config.js:162:

objectBase: join(prefix, ctx.repo, 'artifacts', ctx.runId)

and the throw at src/lib/upload-impl.js:94:

if (existing.exists && !overwrite) throw new Error(
  `An artifact named "${name}" already exists for run ${cfg.runId}. ...`)

Run 391's stale objects live under .../artifacts/391/. A dispatch gets a fresh GITHUB_RUN_ID, so exists() is false and every leg uploads clean. #230's overwrite: true is a second layer under it, not the thing doing the work. This is also why re-running 391 could never go green no matter how many times it was retried.

2. It builds the tag's code, not main's. Both build and publish check out ref: v0.4.1. The dispatching branch supplies only the workflow file, which is the whole point: a tag push reads its workflow from the tag, so the fix is unreachable from 391 but reachable from a dispatch.

3. The action attaches to the release that already exists. This was the part worth checking rather than assuming, since v0.4.1 is already a published release (id 214, draft: false, assets: 0, empty body). forgejo-release-action/src/release.js:87:

let release = await client.getReleaseByTag(tag);
if (release) {
  core.info(`updating existing release id=${release.id}`);
  release = await client.updateRelease(release.id, { name, body, draft, prerelease });
} else { ... createRelease ... }

So it takes the update branch: PATCHes release 214 with the CHANGELOG body, then uploads. No new tag, no moved tag, no second release. A useful side effect: if guard has demoted a failed release to draft, a later successful dispatch promotes it back, because draft defaults to false.

4. Everything the job needs is present at the tag. Checked against v0.4.1 (62d4d7f) rather than the working tree:

  • CHANGELOG.md has ## [0.4.1] - 2026-07-22; the awk extracts 12 lines and stops at ## [0.4.0]. The release body will not be empty.
  • README.md, LICENSE, CHANGELOG.md, Cargo.lock all present, which the package and --locked steps need.
  • The package step produces fj-v0.4.1-<target>.tar.gz with a single top-level fj-v0.4.1-<target>/, matching what the corrected formula expects. Re-verified after this round's changes.

5. Then the tap bump is a copy, not an edit, because the formula generator no longer emits the cd that made every generated fj.rb uninstallable.

What to run

Dispatch release from main with tag: v0.4.1. Expected: 5 assets on v0.4.1 (three tarballs, SHA256SUMS, fj.rb), a non-empty body, unchanged tag and date.

What I cannot confirm without CI

The macOS signing and notarization steps and the artifact round-trip. If notarization is flaky the build fails again, but recoverably now: each dispatch is a fresh run id, so a retry is a retry rather than a permanent wedge, and guard files an issue instead of leaving a zero-asset release looking installable.

target_commitish on release 214 currently reads main, and updateRelease does not send that field, so the stale value stays. Cosmetic: the tag itself points at 62d4d7f and that is what the assets are built from. Not worth moving the tag to tidy.

After the dispatch, the tap bump is the last piece of this issue. Per the operator's gate I will open that PR and stop rather than push to the tap.

## Item 2: does #233 republish the actual v0.4.1 artifacts, or only protect future tags? **It republishes the real ones.** v0.4.1 gets its five assets, the tag does not move, and it keeps its 2026-07-22 date. Not a future-tags-only fix. The chain, with the part I verified at each link: **1. A dispatch is a new run, and that alone unwedges it.** The collision that killed run 391 is keyed on the run id. From `upload-artifact-action` `src/lib/config.js:162`: ```js objectBase: join(prefix, ctx.repo, 'artifacts', ctx.runId) ``` and the throw at `src/lib/upload-impl.js:94`: ```js if (existing.exists && !overwrite) throw new Error( `An artifact named "${name}" already exists for run ${cfg.runId}. ...`) ``` Run 391's stale objects live under `.../artifacts/391/`. A dispatch gets a fresh `GITHUB_RUN_ID`, so `exists()` is false and every leg uploads clean. #230's `overwrite: true` is a second layer under it, not the thing doing the work. This is also why re-running 391 could never go green no matter how many times it was retried. **2. It builds the tag's code, not `main`'s.** Both `build` and `publish` check out `ref: v0.4.1`. The dispatching branch supplies only the workflow file, which is the whole point: a tag push reads its workflow from the tag, so the fix is unreachable from 391 but reachable from a dispatch. **3. The action attaches to the release that already exists.** This was the part worth checking rather than assuming, since v0.4.1 is already a published release (id `214`, `draft: false`, `assets: 0`, empty body). `forgejo-release-action/src/release.js:87`: ```js let release = await client.getReleaseByTag(tag); if (release) { core.info(`updating existing release id=${release.id}`); release = await client.updateRelease(release.id, { name, body, draft, prerelease }); } else { ... createRelease ... } ``` So it takes the update branch: PATCHes release 214 with the CHANGELOG body, then uploads. No new tag, no moved tag, no second release. A useful side effect: if `guard` has demoted a failed release to draft, a later successful dispatch promotes it back, because `draft` defaults to false. **4. Everything the job needs is present at the tag.** Checked against `v0.4.1` (`62d4d7f`) rather than the working tree: - `CHANGELOG.md` has `## [0.4.1] - 2026-07-22`; the awk extracts 12 lines and stops at `## [0.4.0]`. The release body will not be empty. - `README.md`, `LICENSE`, `CHANGELOG.md`, `Cargo.lock` all present, which the `package` and `--locked` steps need. - The `package` step produces `fj-v0.4.1-<target>.tar.gz` with a single top-level `fj-v0.4.1-<target>/`, matching what the corrected formula expects. Re-verified after this round's changes. **5. Then the tap bump is a copy, not an edit,** because the formula generator no longer emits the `cd` that made every generated `fj.rb` uninstallable. ### What to run Dispatch `release` from `main` with `tag: v0.4.1`. Expected: 5 assets on v0.4.1 (three tarballs, `SHA256SUMS`, `fj.rb`), a non-empty body, unchanged tag and date. ### What I cannot confirm without CI The macOS signing and notarization steps and the artifact round-trip. If notarization is flaky the build fails again, but recoverably now: each dispatch is a fresh run id, so a retry is a retry rather than a permanent wedge, and `guard` files an issue instead of leaving a zero-asset release looking installable. `target_commitish` on release 214 currently reads `main`, and `updateRelease` does not send that field, so the stale value stays. Cosmetic: the tag itself points at `62d4d7f` and that is what the assets are built from. Not worth moving the tag to tidy. After the dispatch, the tap bump is the last piece of this issue. Per the operator's gate I will open that PR and stop rather than push to the tap.
stephen referenced this issue from a commit 2026-07-30 03:06:06 +00:00
Author
Owner

Tap bump: verified and pushed, but the PR cannot be opened

v0.4.1 now has its five assets (recovery dispatch, run #410, after #233 and #234). The tap bump is ready on rasterstate/homebrew-tap branch fj-0.4.1 at f705bc3. It is not merged and I have not pushed to main.

rasterstate/homebrew-tap has has_pull_requests: false, so there is nowhere to open a pull request. fj pr create reports not found: rasterstate/homebrew-tap, and the repository also has has_actions: false, so no Forseti review can run there either. The repo has no branch protection and I have push access, which is exactly why I stopped at the branch rather than pushing main. Two ways forward, operator's call: enable the pull requests unit and I open it normally, or review main...fj-0.4.1 and merge it directly.

The formula is byte-identical to the published asset

Formula/fj.rb was replaced wholesale with the fj.rb published on the v0.4.1 release rather than hand-edited from 0.3.0. cmp reports no difference; both hash to 7d62eeb32052730187626c2bb53e7de6cc0c3c41673cba80bc06e32087aa5602. The diff against 0.3.0 is the version, three URLs, three hashes, and a longer comment on the install block, which is the generator's wording.

Verified rather than trusted

  • Each tarball downloaded and hashed with shasum -a 256. All three match the published SHA256SUMS and the hashes in fj.rb. shasum -c passes on all three.
  • brew fetch passes, so Homebrew's own checksum verification agrees.
  • Each tarball has a single top-level directory containing exactly the four files the install block references, which is what makes the dropped cd correct.
  • The darwin-aarch64 binary is Mach-O arm64, hardened runtime, and spctl -a -t install reports accepted, source=Notarized Developer ID, origin=Developer ID Application: Raster & State LLC (62Y3FRM8PD). Notarization was the last thing I had listed as unverifiable without CI; it is now verified from the published artifact.
  • End to end on the real tap: pointed the rasterstate/tap checkout at fj-0.4.1, ran brew upgrade fj, and got 0.3.0 -> 0.4.1 with fj --version reporting fj 0.4.1 and README.md, LICENSE, CHANGELOG.md landing in share/fj. Then restored the checkout to 2b263a2 and reinstalled 0.3.0, so the machine is as it was.
  • Confirmed the reported symptom first: before the change, brew install fj on a machine with the tap already tapped served 0.3.0.

Flagging, not fixing: brew test fj fails, and did before this

The formula's test block asserts a string that fj --help does not contain:

assert_match "Command-line tool for Forgejo", shell_output("#{bin}/fj --help")

The help text reads fj is a command-line tool for Forgejo, lowercase c, so the match fails:

Minitest::Assertion: Expected /Command\-line\ tool\ for\ Forgejo/ to match
  "fj is a command-line tool for Forgejo, in the spirit of GitHub's gh. ..."

Three things worth separating:

  1. It is not a regression. The restored 0.3.0 formula fails identically, on 0.3.0's own --help. brew test has been broken here since at least 0.3.0.
  2. It does not affect brew install. Test blocks only run under brew test and strict audits, which is why nobody noticed. The --version assertion in the same block passes.
  3. The real fix belongs in the generator, in .forgejo/workflows/release.yml, not in the tap. Fixing it in the tap would break the byte-identity with the published asset that makes this bump a copy rather than an edit, and it would be undone by the next release. I have not changed it in either place. Happy to open a one-line fj PR against the generator (assert the lowercase string, or the desc text that actually appears) on your word.

Remaining on this issue

Only the merge. Once fj-0.4.1 lands on the tap's main, brew tap rasterstate/tap && brew install fj serves 0.4.1, and with #665 having fixed the rasterandstate/tap typo in FJ_INSTALL_COMMAND, the documented one-liner and the tap agree.

## Tap bump: verified and pushed, but the PR cannot be opened v0.4.1 now has its five assets (recovery dispatch, run #410, after #233 and #234). The tap bump is ready on `rasterstate/homebrew-tap` branch **`fj-0.4.1`** at `f705bc3`. It is not merged and I have not pushed to `main`. **`rasterstate/homebrew-tap` has `has_pull_requests: false`,** so there is nowhere to open a pull request. `fj pr create` reports `not found: rasterstate/homebrew-tap`, and the repository also has `has_actions: false`, so no Forseti review can run there either. The repo has no branch protection and I have push access, which is exactly why I stopped at the branch rather than pushing `main`. Two ways forward, operator's call: enable the pull requests unit and I open it normally, or review `main...fj-0.4.1` and merge it directly. ### The formula is byte-identical to the published asset `Formula/fj.rb` was replaced wholesale with the `fj.rb` published on the v0.4.1 release rather than hand-edited from 0.3.0. `cmp` reports no difference; both hash to `7d62eeb32052730187626c2bb53e7de6cc0c3c41673cba80bc06e32087aa5602`. The diff against 0.3.0 is the version, three URLs, three hashes, and a longer comment on the `install` block, which is the generator's wording. ### Verified rather than trusted - Each tarball downloaded and hashed with `shasum -a 256`. All three match the published `SHA256SUMS` and the hashes in `fj.rb`. `shasum -c` passes on all three. - `brew fetch` passes, so Homebrew's own checksum verification agrees. - Each tarball has a single top-level directory containing exactly the four files the `install` block references, which is what makes the dropped `cd` correct. - The darwin-aarch64 binary is `Mach-O arm64`, hardened runtime, and `spctl -a -t install` reports `accepted, source=Notarized Developer ID, origin=Developer ID Application: Raster & State LLC (62Y3FRM8PD)`. Notarization was the last thing I had listed as unverifiable without CI; it is now verified from the published artifact. - End to end on the real tap: pointed the `rasterstate/tap` checkout at `fj-0.4.1`, ran `brew upgrade fj`, and got `0.3.0 -> 0.4.1` with `fj --version` reporting `fj 0.4.1` and `README.md`, `LICENSE`, `CHANGELOG.md` landing in `share/fj`. Then restored the checkout to `2b263a2` and reinstalled 0.3.0, so the machine is as it was. - Confirmed the reported symptom first: before the change, `brew install fj` on a machine with the tap already tapped served **0.3.0**. ### Flagging, not fixing: `brew test fj` fails, and did before this The formula's test block asserts a string that `fj --help` does not contain: ```ruby assert_match "Command-line tool for Forgejo", shell_output("#{bin}/fj --help") ``` The help text reads `fj is a command-line tool for Forgejo`, lowercase `c`, so the match fails: ``` Minitest::Assertion: Expected /Command\-line\ tool\ for\ Forgejo/ to match "fj is a command-line tool for Forgejo, in the spirit of GitHub's gh. ..." ``` Three things worth separating: 1. **It is not a regression.** The restored 0.3.0 formula fails identically, on 0.3.0's own `--help`. `brew test` has been broken here since at least 0.3.0. 2. **It does not affect `brew install`.** Test blocks only run under `brew test` and strict audits, which is why nobody noticed. The `--version` assertion in the same block passes. 3. **The real fix belongs in the generator,** in `.forgejo/workflows/release.yml`, not in the tap. Fixing it in the tap would break the byte-identity with the published asset that makes this bump a copy rather than an edit, and it would be undone by the next release. I have not changed it in either place. Happy to open a one-line fj PR against the generator (assert the lowercase string, or the `desc` text that actually appears) on your word. ### Remaining on this issue Only the merge. Once `fj-0.4.1` lands on the tap's `main`, `brew tap rasterstate/tap && brew install fj` serves 0.4.1, and with #665 having fixed the `rasterandstate/tap` typo in `FJ_INSTALL_COMMAND`, the documented one-liner and the tap agree.
Author
Owner

Closing the loop on the last half of this, since the issue was marked closed while brew install fj was still serving 0.3.0.

The tap was the remaining gap. rasterstate/homebrew-tap has no pull requests unit, so main fast-forwarded to the fj-0.4.1 branch directly: 2b263a2..f705bc3, one commit, Formula/fj.rb only. brew info rasterstate/tap/fj now reports stable 0.4.1.

Verified independently of the release job before pushing, rather than trusting the published SHA256SUMS (same job produces both, so it is not a check):

  • re-downloaded fj-v0.4.1-darwin-aarch64.tar.gz and hashed it with shasum -a 256. It matches the formula's 99ee1c6a... exactly, and all three formula hashes match the published sums.
  • the tarball has a single top-level fj-v0.4.1-darwin-aarch64/ containing exactly the four files the install block references, which confirms the no-cd install and the comment explaining it.
  • the branch file is byte-identical to the fj.rb asset on the v0.4.1 release, both 1423 bytes, so the tap is the generator's output and not a hand-edit.

One defect stays open and is not this issue's: the formula's test do asserts assert_match "fj #{version}". That belongs in the generator in this repo rather than in the tap, so it should be filed here if it is not already.

Closing the loop on the last half of this, since the issue was marked closed while `brew install fj` was still serving 0.3.0. The tap was the remaining gap. `rasterstate/homebrew-tap` has no pull requests unit, so `main` fast-forwarded to the `fj-0.4.1` branch directly: `2b263a2..f705bc3`, one commit, `Formula/fj.rb` only. `brew info rasterstate/tap/fj` now reports stable 0.4.1. Verified independently of the release job before pushing, rather than trusting the published `SHA256SUMS` (same job produces both, so it is not a check): - re-downloaded `fj-v0.4.1-darwin-aarch64.tar.gz` and hashed it with `shasum -a 256`. It matches the formula's `99ee1c6a...` exactly, and all three formula hashes match the published sums. - the tarball has a single top-level `fj-v0.4.1-darwin-aarch64/` containing exactly the four files the install block references, which confirms the no-`cd` install and the comment explaining it. - the branch file is byte-identical to the `fj.rb` asset on the v0.4.1 release, both 1423 bytes, so the tap is the generator's output and not a hand-edit. One defect stays open and is not this issue's: the formula's `test do` asserts `assert_match "fj #{version}"`. That belongs in the generator in this repo rather than in the tap, so it should be filed here if it is not already.
Sign in to join this conversation.
No milestone
No assignees
1 participant
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
rasterstate/fj#232
No description provided.