Skip to content

Project bond maturity as terminal; preview the closing payout in the UI - #276

Merged
Greaker merged 4 commits into
developmentfrom
feature/redeem-with-final-coupon-api
Sep 8, 2026
Merged

Greaker merged 4 commits into
developmentfrom
feature/redeem-with-final-coupon-api

Conversation

@Greaker

@Greaker Greaker commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Follow-up to Close a bond with its final coupon; emit BondMatured #275 (Phases 2–5 of docs/plans/redeem-with-final-coupon/). The API ingests BondMatured as a MATURED history entry carrying paymentCount, principalPaid, couponPaid, and unsoldBurned, and as the terminal bond transition; matured now means closed with zero supply and redeemed leaves BondStatus. POST /v1/bonds/{isin}/redemptions and the unused UI client are removed; the REDEMPTION audit type stays as a legacy value. openapi.json is regenerated.
  • The pay-coupon modal detects the final period and previews coupon, principal, and total per holder, lists unsold manager-held units as burned without payment, and warns when the reserve's WNOK balance cannot cover the payout; the Coupon payout page explains its work-queue filter and that the final coupon closes the bond; Bonds filter, Bond detail tooltips, and the redeemed badge style follow.
  • Docs: the coupon/maturity sequence diagram, the bond lifecycle classifier and state diagram, architecture, the diagrams index, the contracts README, the API development guide, and the closed-loop plan's Phase 6 note. The "partially allocated bonds deadlock coupon payment" known issue is removed (unsold units are skipped and burned by construction) together with the coupon route's allowlisting hint. progress.md records the fresh-sandbox evidence: a fully allocated bond and a bond with 40 unsold units both closed in one final-coupon transaction, WNOK supply unchanged, MATURED projected with totals.

Test plan

  • cd services/nb-bond-api && npm run lint && npm run format:check && npm test && npm run build — 245 tests (incl. reducer order-independence)
  • cd services/nb-ui && npm run format:check && npm run lint && npm test && npm run build — 123 tests (final-payout preview, unsold units, reserve warning)
  • python3 scripts/verification/check-public-repo-hygiene.py && python3 scripts/verification/check-markdown-links.py
  • Fresh ./sandbox.sh delete && ./sandbox.sh start on the branch; two bonds run to maturity through http://bond-api.cbdc-sandbox.local/ with balance trails in docs/plans/redeem-with-final-coupon/progress.md

…on route

Ingestion maps the new BondMatured event to a MATURED history entry that
carries paymentCount, principalPaid, couponPaid, and unsoldBurned, and to
a reducer transition that sets both the matured and redemption-complete
flags, since the final coupon now pays principal and burns every unit.
The composer's terminal status is matured; redeemed leaves the BondStatus
enum. The POST /v1/bonds/{isin}/redemptions route, its handler, and its
OpenAPI path are removed; the REDEMPTION operation type stays as a legacy
value so stored audit rows still validate. openapi.json is regenerated.

A reducer test proves the projected state is identical whether the
maturity flag or the token burns are applied first, matching ingestion's
manager-first ordering within a block. nb-bond-api: 245 jest tests green.
The pay-coupon modal detects the final period from the bond's remaining
payment count and previews, per holder, the coupon, the principal, and
the total, lists unsold units held by the bond manager as burned without
payment (and as earning no coupon on interim periods), and warns when the
government reserve's WNOK balance from the Central Bank resource cannot
cover the total, since the whole transaction would revert. The action
reads "Pay final coupon and close bond". The old treasury-held failure
warning goes away: those units no longer block a payout.

The Coupon payout page explains that it is a work queue and that the
final coupon closes the bond; the Bonds filter loses the redeemed status;
the Bond detail tooltips describe closure at maturity; the unused
BondsApi.redeem client and the redeemed badge style are removed. nb-ui:
123 vitest tests green.
…dlock entry

The coupon sequence diagram becomes a coupon-and-maturity sequence with
the final-period branch (setMatured, Redeem settlement for coupon plus
principal, unsold units burned without payment, BondMatured). The bond
lifecycle classifier and state diagram drop the redeemed state; matured
is terminal. Architecture, the diagrams index, the contracts README, and
the API development guide (route list and scenario 6.4) follow. The
"partially allocated bonds deadlock coupon payment" known issue is
removed: unsold manager-held units are now skipped on coupons and burned
at maturity, so the coupon route's allowlisting hint goes too. The
closed-loop plan's Phase 6 note records that maturity closure exists.
… final coupon

Phase 4 of docs/plans/redeem-with-final-coupon/: on a fresh local sandbox
a fully allocated bond and a bond with 40 unsold units on the manager
both closed in their final coupon transaction. Holders received coupon
plus principal, unsold units were burned without payment, WNOK supply
never changed, the manager never needed allowlisting, and each closure
projected one MATURED history entry with its totals and a terminal
matured status with zero supply.
@Greaker
Greaker merged commit 49dd2ed into development Sep 8, 2026
11 checks passed
@Greaker
Greaker deleted the feature/redeem-with-final-coupon-api branch September 8, 2026 15:19
Greaker added a commit that referenced this pull request Sep 8, 2026
Move docs/plans/redeem-with-final-coupon/ to docs/plans/archive/ with its
status set to Implemented (shipped via #275 and #276), tick its done
criteria, close out progress.md, and move the documentation-index entry
to the archive list. ADR 0005 moves to Accepted in the ADR index and the
documentation index. The contracts versioning note no longer names the
removed BondManager.redeem entry point.
@Greaker Greaker mentioned this pull request Sep 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant