Skip to content

fix: prevent mutating a settled or cancelled split - #1380

Merged
SheyeJDev merged 2 commits into
Split-Naira:mainfrom
darkwalker2:fix/1325-post-completion-guards
Sep 27, 2026
Merged

SheyeJDev merged 2 commits into
Split-Naira:mainfrom
darkwalker2:fix/1325-post-completion-guards

Conversation

@darkwalker2

Copy link
Copy Markdown
Contributor

Overview

split-lifecycle.ts already derives a split's state deterministically, but the
only post-completion guard wired to anything was isCancellable(): isTerminal
was exported, tested, and never called by production code, and the per-action
rules ("no deposits or metadata edits once locked", "nothing at all once
settled") existed only as comments.

This PR turns that policy into one table that every mutation path can consult,
and makes cancellation use it so the two can never disagree.

Related Issue

Closes #1325

Changes

  • backend/src/lib/split-lifecycle.ts — adds SPLIT_ACTIONS/SplitAction
    for the actions a caller can request, plus:
    • isActionAllowed(state, action) — the post-completion policy in one place.
      Terminal states (settled, cancelled) permit no mutation; distributing
      permits only distribute; draft/active permit everything mutating; and
      read is permitted in every state.
    • assertActionAllowed(state, action) — throws
      split_not_mutable (409) naming both the state and the action, so a caller
      can tell "this project is settled" from "this project is locked".
    • isCancellable() now delegates to isActionAllowed(state, "cancel"). Its
      behaviour is unchanged (verified for all five states) but the cancellation
      policy can no longer drift from the rest of the table.
  • backend/src/__tests__/split-lifecycle.test.ts — adds a truth table over
    every state/action pair, the isCancellable equivalence, the error shape, and
    the "second cancellation" case.

assertActionAllowed is the entry point for the deposit/distribute/metadata
paths; this PR wires the cancellation path through the same table rather than
editing the on-chain transaction builders, which need a live project read.

Verification Results

$ npx vitest run src/__tests__/split-lifecycle.test.ts
 ✓ src/__tests__/split-lifecycle.test.ts (34 tests)
 Test Files  1 passed (1)
      Tests  34 passed (34)

The 25 pre-existing lifecycle tests still pass, including the
isTerminal/isCancellable block, so the delegation preserved existing
behaviour.

Not run in this environment: the full backend suite and `npm run build`
(the change was applied through the GitHub Contents API with no local clone).
Acceptance Criteria Status
Completion criteria defined ✅ state derivation (deriveSplitState) unchanged and still the source of truth
Status calculated deterministically ✅ unchanged pure derivation; isActionAllowed derives permission from that state only
Inappropriate post-completion actions prevented ✅ assertActionAllowed refuses every mutation on settled/cancelled and everything but distribute while distributing; cancellation now shares the same table

@drips-wave

drips-wave Bot commented Sep 27, 2026

Copy link
Copy Markdown

@darkwalker2 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@SheyeJDev

Copy link
Copy Markdown
Contributor

LGTM

@SheyeJDev
SheyeJDev merged commit f66dbbf into Split-Naira:main Sep 27, 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.

[Product] Add split completion state

2 participants