M4. Domain logic¶
Goal. Replace M1 to M3 stubs with real group rules, vote lifecycle, marked pot NAV, redeem, P and L, and both leaderboards. Postgres and the Go API are the source of truth. Jupiter runs when a proposal passes. Votes are not on-chain.
Depends on. M1 wallets, M2 deposit credit on positions, M3 Jupiter buy and sell plus the stub execute hook.
Owns. apps/backend/internal/app full commands, packages/domain math, remaining Postgres migrations, StartBuy for passed votes only, RedeemJob executor, deletion of M3 stub buy path.
Structure¶
packages/domain/
nav.go NavMarked, ComputePotNAV
votes.go TallyProposal, ProposalStatus
redeem.go ComputeRedeemSlice, RedeemJobStatus
pnl.go PercentReturn, board rank helpers
apps/backend/
internal/app/
governance.go ProposeBuy, Vote, join
redeem.go Redeem → RedeemJob state machine
views.go GroupView, HomeView
start_buy.go StartBuy: passed vote only (replaces dev stub gate)
internal/pyth/ Pyth price client: USDC-only pot + marked pot, after-hours flag
internal/postgres/
proposals, votes, nav_snapshots, redeem_jobs, withdrawals writes
Flow¶
Group rules
POST /v1/groupswith join policy, voter set, threshold, expiry. Creator getsgroup_membersrow.POST /v1/groups/{id}/joinwith optional password.
Buy via vote
GET /v1/groups/{id}/assets?query=for catalog search. Mobile consumes this endpoint only.POST /v1/groups/{id}/quotesreturnsroutableso propose UI can disable before submit.POST /v1/groups/{id}/proposals: backend resolves mint, quote must exist, insert open proposal.POST /v1/proposals/{id}/votes: persist vote,TallyProposal→ passed/failed/expired.- On pass only:
StartBuyallows execute → same Jupiter path as M3 →transactionswithproposal_idset → NAV snapshot.
Marked NAV and boards
- Pyth price client
MarkedPotfeedsComputePotNAV(NavMarked). Cost basis ontransactions, live price from Pyth. GroupView: pot, viewer equity, in-group board ranked by percent return.HomeView: group board + people board.
Redeem (debit-first)
POST /v1/groups/{id}/redeemswith share amount or dollar target, plus payout proof.- Verify payout proof. Postgres txn: row-lock
positions, debitshare_units, insertredeem_jobsstatusdebited. - If group treasury holds xStock, Jupiter client
SellToUSDCfor redeemed slice → statusselling. - Privy client
PayUSDCto proven address → statuspaying. - Insert
withdrawalswithtx_signature, incrementpositions.amount_withdrawn, statussettled, NAV snapshot. - Crash after step 10 resumes from
redeem_jobs.status. No second debit.
Open decisions (M4-T39–T41): ticket only. No proposer rule, execute-retry, or dissolve logic in code.
Data models¶
Keep rules in packages/domain, not scattered handler ifs. packages/domain stays Go-only. Mobile reads domain results over HTTP with hand-written Codable types. Swift never calls xStocks, Jupiter, Pyth, or Solana RPC. Catalog search, quotes, Pyth prices, and after-hours flags are backend endpoints only.
- groups gains join policy, voter set, threshold, expiry duration.
- group_members. One user may sit in many groups.
- proposals and votes. Status is open, passed, failed, or expired.
- deposits, positions, withdrawals, transactions. Already defined in M2 and M3. M4 writes withdrawal rows on redeem payout and debits position share units first.
- positions hold share units and net USDC in columns (
amount_deposited,amount_withdrawn). M4 pot NAV uses USDC plus xStock units times Pyth price. - nav_snapshots. Written on deposit, transaction confirm, and withdrawal payout so boards replay from history.
- net USDC in. Per member per group from position columns: deposits credited minus withdrawals paid. Sum for the people board.
Claim units on deposit. M2 credits share_units and amount_deposited 1:1 with swept USDC while the pot is USDC only. Once the treasury holds marked xStock, M4 mints share_units from current pot NAV so a later deposit does not absorb unrealized gain (README Alex/Blair example). That ratio is internal ledger math only. User copy says claim units or omits price. Never say share price or NAV to users.
Formulas from docs/product.md.
- USDC-only pot:
share_unitsandamount_depositedincrease by swept USDC (M2 path). - Marked pot:
share_units credited = USDC swept in × total share_units / pot NAV your equity = (your share_units / total share_units) × pot NAVpercent return = equity / net USDC in − 1- Skip a board row when net USDC in is 0.
- Rank by percent return, never by dollars.
Do not invent answers for the three README open decisions. Record tickets only.
Parallelization¶
Milestone gate: complete Wave 5 before M5.
| Wave | Tracks (run in parallel within the wave) | Gate for next wave |
|---|---|---|
| 1 | Schema M4-T1 · Domain types M4-T2 | M3 |
| 2 | Deposit credit M4-T3 · Pot NAV M4-T5 · Pyth M4-T38 · Group rules M4-T7–T10 · Catalog M4-T11, M4-T12 | Wave 1 |
| 3 | Proposals M4-T13 · Vote tally M4-T14 · Leaderboards M4-T23–T29, M4-T37 | Wave 2 |
| 4 | Execute on pass M4-T19 · Redeem M4-T30 | M4-T13, M4-T14 |
| 5 | Tests M4-T4, T6, T18, T22, T29, T36 · Open decisions M4-T39–T41 | Waves 3–4 |
Tracks inside Wave 2 are fully parallel once schema lands. Leaderboard math (T23–T29) can start once pot NAV (T5) exists; it does not wait on votes. Redeem (T30) and execute-on-pass (T19) are parallel in Wave 4 but each has internal sequential subissues.
Tickets¶
- M4-T1 Add remaining schema for group settings, members, proposals, votes, NAV snapshots, and payout proofs. Do not recreate M1 wallet tables or M2 deposit tables.
- M4-T2 Add domain types for join policy, voter set, threshold, expiry, proposal status, and withdrawal status.
Depends on: M4-T1 - Parent M4-T3 Mint claim units at marked pot NAV on deposit. Keep M2 1:1 USDC credit when treasury holds no marked xStock.
Depends on: M4-T2, M4-T5 - └ Subissue of M4-T3 M4-T4 Add tests for USDC-only deposit credit, post-buy marked-pot minting, and the README Alex/Blair worked example
- Parent M4-T5 Implement pot NAV as treasury USDC plus each xStock marked, with transaction cost basis for holdings.
Depends on: M4-T2 - └ Subissue of M4-T5 M4-T6 Add tests for pot NAV with USDC only, mixed pot, and empty shares
- M4-T7 Replace thin group create with persisted join policy, voter set, threshold, and expiry.
Depends on: M4-T2 - M4-T8 Implement join for open groups and for password groups.
Depends on: M4-T7 - M4-T9 Implement named voter subset with minimum size 1 and every-member voter set mode.
Depends on: M4-T7 - M4-T10 Enforce one user in many groups on membership rows.
Depends on: M4-T7 - M4-T11 Add
GET /v1/groups/{id}/assets?query=with backend xStocks mint resolution for mobile catalog search.
Depends on: M4-T2 - M4-T12 Add
POST /v1/groups/{id}/quotesand refuse proposal create when backend cannot quote USDC to the output mint.
Depends on: M4-T11, M3-T3 - M4-T13 Implement buy proposal create with symbol, USDC amount, proposer, and expiry deadline.
Depends on: M4-T9, M4-T12 - Parent M4-T14 Implement yes/no votes limited to current voter set members and tally to passed, failed, or expired.
Depends on: M4-T13- └ Subissue of M4-T14 M4-T15 Implement unanimous tally among the voter set
- └ Subissue of M4-T14 M4-T16 Implement majority tally among the voter set
- └ Subissue of M4-T14 M4-T17 Expire open proposals at deadline with failed status and no swap
- └ Subissue of M4-T14 M4-T18 Add tests for unanimous pass, majority pass, majority fail, and expiry fail
- Parent M4-T19 On pass, build the Jupiter v2 transaction, sign the treasury, POST execute, confirm Success code 0. Delete the M3 stub route.
Depends on: M4-T14, M3-T5- └ Subissue of M4-T19 M4-T20 Record treasury holdings and write a NAV snapshot on confirmed buy transaction
- └ Subissue of M4-T19 M4-T21 Add an idempotent execute guard keyed on proposal id and Jupiter tx signature
- └ Subissue of M4-T19 M4-T22 Add tests for idempotent deposit credit, buy execute, and withdrawal payout
- M4-T23 Implement member equity and percent return per group from positions.
Depends on: M4-T5 - M4-T24 Implement net USDC in per member per group from position columns.
Depends on: M4-T23 - M4-T25 Skip leaderboard rows when net USDC in equals zero.
Depends on: M4-T24 - M4-T26 Implement the in-group member board ranked by percent return.
Depends on: M4-T25 - M4-T27 Implement the app-wide group board ranked by pot percent return.
Depends on: M4-T5 - M4-T28 Implement the app-wide people board ranked by summed cross-group percent return.
Depends on: M4-T24 - M4-T29 Add tests for percent ranking, zero-net skip, and full exit drop from the in-group board.
Depends on: M4-T26, M4-T27, M4-T28 - Parent M4-T30 Implement partial redeem with share amount or dollar target and a dust minimum.
Depends on: M4-T5, M3-T9- └ Subissue of M4-T30 M4-T31 Debit position share units first in a row-locked Postgres transaction
- └ Subissue of M4-T30 M4-T32 Sell the redeemed slice of each xStock to USDC on Jupiter when the treasury holds stock
- └ Subissue of M4-T30 M4-T33 Pay USDC only to a payout address the user proved they own with a signed message. Persist a withdrawal row
- └ Subissue of M4-T30 M4-T34 Reject redeem when the payout pubkey fails ownership proof
- └ Subissue of M4-T30 M4-T35 Write a NAV snapshot and recompute boards after confirmed withdrawal payout
- └ Subissue of M4-T30 M4-T36 Add tests for debit-first ordering, slice USDC versus deposit refund, and board recompute
- M4-T37 Persist NAV snapshots on deposit, transaction confirm, and withdrawal payout.
Depends on: M4-T5 - M4-T38 Wire an after-hours flag when the Pyth equity mark is frozen.
Depends on: M4-T5 - M4-T39 Open decision ticket. Who may propose a buy. Any member, voter set only, or creator only. Do not pick.
- M4-T40 Open decision ticket. Failed Jupiter execute after a passed vote. Do not pick.
- M4-T41 Open decision ticket. Creator leave and group dissolve. Do not pick.
Ticket details¶
M4-T1: Add remaining schema for group settings, members, proposals, votes, NAV snapshots, and payout proofs¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T1 is wave 1 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Problem
Behavior for Add remaining schema for group settings, members, proposals, votes, NAV snapshots, and payout proofs is not implemented under supabase/migrations/.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
supabase/migrations/. No drive-by refactors. - Satisfy milestone gate:
just test backendand/orjust test mobileexit 0 with no new failing tests.
API / data contract
No new HTTP surface. Internal Go/Swift symbols in Owns paths only.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
supabase/migrations/
Out of scope (do not touch) - Recreating M1 wallet or M2 deposit tables
Acceptance Criteria
- [ ] Files and symbols named in Proposal exist under supabase/migrations/.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Files and symbols named in Proposal exist under supabase/migrations/.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - None - Wave: 1
M4-T2: Add domain types for join policy, voter set, threshold, expiry, proposal status, and withdrawal status¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T2 is wave 1 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Blocked until dependencies land: M4-T1.
Problem
Behavior for Add domain types for join policy, voter set, threshold, expiry, proposal status, and withdrawal status is not implemented under packages/domain/, apps/backend/internal/app/.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
packages/domain/,apps/backend/internal/app/. No drive-by refactors. - Satisfy milestone gate:
just test backendand/orjust test mobileexit 0 with no new failing tests.
API / data contract
No new HTTP surface. Internal Go/Swift symbols in Owns paths only.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
packages/domain/apps/backend/internal/app/
Out of scope (do not touch) - HTTP handlers - Swift types
Acceptance Criteria
- [ ] Files and symbols named in Proposal exist under packages/domain/, apps/backend/internal/app/.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Files and symbols named in Proposal exist under packages/domain/, apps/backend/internal/app/.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T1 — do not start while open - Wave: 1
M4-T3: Mint claim units at marked pot NAV on deposit. Keep M2 1:1 USDC credit when treasury holds no marked xStock¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T3 is wave 2 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Parent ticket; subissues: M4-T4.
Blocked until dependencies land: M4-T2, M4-T5.
Problem
Parent M4-T3 orchestration in apps/backend/internal/app/deposit.go, packages/domain/nav.go missing; subissues M4-T4 not wired together.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
apps/backend/internal/app/deposit.go,packages/domain/nav.go. No drive-by refactors. - Parent glue: wire subissues M4-T4 into
apps/backend/internal/app/deposit.goafter each subissue lands. - Add
TestSharesForDeposit_usdcOnlyPot_creditsOneToOneWithSweptUsdcin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert). - Add
TestSharesForDeposit_markedPot_mintsSharesFromPotNavin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
Parent orchestration. Public contract is union of subissue routes/symbols: M4-T4.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
apps/backend/internal/app/deposit.gopackages/domain/nav.go
Out of scope (do not touch) - Mobile share math
Acceptance Criteria
- [ ] Test TestSharesForDeposit_usdcOnlyPot_creditsOneToOneWithSweptUsdc exists, uses AAA comments, and passes.
- [ ] Test TestSharesForDeposit_markedPot_mintsSharesFromPotNav exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/deposit.go, packages/domain/nav.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] All subissues (M4-T4) closed before parent closes.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestSharesForDeposit_usdcOnlyPot_creditsOneToOneWithSweptUsdc exists, uses AAA comments, and passes.
- [ ] Test TestSharesForDeposit_markedPot_mintsSharesFromPotNav exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/deposit.go, packages/domain/nav.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] All subissues (M4-T4) closed before parent closes.
- [ ] Subissues closed: M4-T4.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T2 — do not start while open - M4-T5 — do not start while open - Wave: 2 - Subissue: M4-T4
M4-T4: Add tests for USDC-only deposit credit, post-buy marked-pot minting, and README Alex/Blair worked example¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T4 is wave 5 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Subissue of M4-T3.
Blocked until dependencies land: M4-T3.
Problem
Locked test names for M4-T4 are absent or not wired into just test recipes; packages/domain/*_test.go, apps/backend/internal/app/deposit_test.go incomplete.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
packages/domain/*_test.go,apps/backend/internal/app/deposit_test.go. No drive-by refactors. - Subissue of
M4-T3. Do not add routes or flows owned by parent/sibling subissues. - Add
TestDepositCredit_readmeAlexBlairWorkedExample_matchesLiteralsin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert). - Add
TestObserveSweep_postBuyDeposit_doesNotLetNewMemberCaptureUnrealizedGainin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new public route. Subissue of M4-T3. Internal symbols only in Owns paths.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
packages/domain/*_test.goapps/backend/internal/app/deposit_test.go
Out of scope (do not touch) - Manual demo script
Acceptance Criteria
- [ ] Test TestDepositCredit_readmeAlexBlairWorkedExample_matchesLiterals exists, uses AAA comments, and passes.
- [ ] Test TestObserveSweep_postBuyDeposit_doesNotLetNewMemberCaptureUnrealizedGain exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under packages/domain/*_test.go, apps/backend/internal/app/deposit_test.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestDepositCredit_readmeAlexBlairWorkedExample_matchesLiterals exists, uses AAA comments, and passes.
- [ ] Test TestObserveSweep_postBuyDeposit_doesNotLetNewMemberCaptureUnrealizedGain exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under packages/domain/*_test.go, apps/backend/internal/app/deposit_test.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T3 — do not start while open - Wave: 5 - Parent: M4-T3
M4-T5: Implement pot NAV as treasury USDC plus each xStock marked, with transaction cost basis for holdings¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T5 is wave 2 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Parent ticket; subissues: M4-T6.
Blocked until dependencies land: M4-T2.
Problem
Parent M4-T5 orchestration in packages/domain/nav.go, apps/backend/internal/app/views.go missing; subissues M4-T6 not wired together.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
packages/domain/nav.go,apps/backend/internal/app/views.go. No drive-by refactors. - Parent glue: wire subissues M4-T6 into
packages/domain/nav.goafter each subissue lands. - Satisfy milestone gate:
just test backendand/orjust test mobileexit 0 with no new failing tests.
API / data contract
Parent orchestration. Public contract is union of subissue routes/symbols: M4-T6.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
packages/domain/nav.goapps/backend/internal/app/views.go
Out of scope (do not touch) - Swift NAV math
Acceptance Criteria
- [ ] Files and symbols named in Proposal exist under packages/domain/nav.go, apps/backend/internal/app/views.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] All subissues (M4-T6) closed before parent closes.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Files and symbols named in Proposal exist under packages/domain/nav.go, apps/backend/internal/app/views.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] All subissues (M4-T6) closed before parent closes.
- [ ] Subissues closed: M4-T6.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T2 — do not start while open - Wave: 2 - Subissue: M4-T6
M4-T6: Add tests for pot NAV with USDC only, mixed pot, and empty shares¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T6 is wave 5 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Subissue of M4-T5.
Blocked until dependencies land: M4-T5.
Problem
Locked test names for M4-T6 are absent or not wired into just test recipes; packages/domain/nav_test.go incomplete.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
packages/domain/nav_test.go. No drive-by refactors. - Subissue of
M4-T5. Do not add routes or flows owned by parent/sibling subissues. - Add
TestComputePotNAV_usdcOnly_returnsTreasuryUsdcin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert). - Add
TestComputePotNAV_mixedPot_sumsUsdcAndMarkedHoldingsin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert). - Add
TestComputePotNAV_emptyShares_firstDepositUsesOneDollarPerSharein the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new public route. Subissue of M4-T5. Internal symbols only in Owns paths.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
packages/domain/nav_test.go
Out of scope (do not touch) - Pyth live HTTP in unit tests
Acceptance Criteria
- [ ] Test TestComputePotNAV_usdcOnly_returnsTreasuryUsdc exists, uses AAA comments, and passes.
- [ ] Test TestComputePotNAV_mixedPot_sumsUsdcAndMarkedHoldings exists, uses AAA comments, and passes.
- [ ] Test TestComputePotNAV_emptyShares_firstDepositUsesOneDollarPerShare exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under packages/domain/nav_test.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestComputePotNAV_usdcOnly_returnsTreasuryUsdc exists, uses AAA comments, and passes.
- [ ] Test TestComputePotNAV_mixedPot_sumsUsdcAndMarkedHoldings exists, uses AAA comments, and passes.
- [ ] Test TestComputePotNAV_emptyShares_firstDepositUsesOneDollarPerShare exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under packages/domain/nav_test.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T5 — do not start while open - Wave: 5 - Parent: M4-T5
M4-T7: Replace thin group create with persisted join policy, voter set, threshold, and expiry¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T7 is wave 2 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Blocked until dependencies land: M4-T2.
Problem
Behavior for Replace thin group create with persisted join policy, voter set, threshold, and expiry is not implemented under apps/backend/internal/app/governance.go, apps/backend/internal/httpapi/groups.go.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
apps/backend/internal/app/governance.go,apps/backend/internal/httpapi/groups.go. No drive-by refactors. - Add
TestPOST_groups_persistsJoinPolicyVoterSetThresholdExpiryin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new HTTP surface. Internal Go/Swift symbols in Owns paths only.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
apps/backend/internal/app/governance.goapps/backend/internal/httpapi/groups.go
Out of scope (do not touch) - Join password flow (M4-T8)
Acceptance Criteria
- [ ] Test TestPOST_groups_persistsJoinPolicyVoterSetThresholdExpiry exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/governance.go, apps/backend/internal/httpapi/groups.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestPOST_groups_persistsJoinPolicyVoterSetThresholdExpiry exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/governance.go, apps/backend/internal/httpapi/groups.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T2 — do not start while open - Wave: 2
M4-T8: Implement join for open groups and for password groups¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T8 is wave 2 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Blocked until dependencies land: M4-T7.
Problem
Behavior for Implement join for open groups and for password groups is not implemented under apps/backend/internal/app/governance.go, apps/backend/internal/httpapi/groups.go.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
apps/backend/internal/app/governance.go,apps/backend/internal/httpapi/groups.go. No drive-by refactors. - Add
TestPOST_join_openGroup_addsMemberWithoutPasswordin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert). - Add
TestPOST_join_passwordGroup_requiresCorrectPasswordin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert). - Add
TestPOST_join_wrongPassword_returns403in the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new HTTP surface. Internal Go/Swift symbols in Owns paths only.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
apps/backend/internal/app/governance.goapps/backend/internal/httpapi/groups.go
Out of scope (do not touch) - Dissolve on creator leave (needs-decision M4-T41)
Acceptance Criteria
- [ ] Test TestPOST_join_openGroup_addsMemberWithoutPassword exists, uses AAA comments, and passes.
- [ ] Test TestPOST_join_passwordGroup_requiresCorrectPassword exists, uses AAA comments, and passes.
- [ ] Test TestPOST_join_wrongPassword_returns403 exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/governance.go, apps/backend/internal/httpapi/groups.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestPOST_join_openGroup_addsMemberWithoutPassword exists, uses AAA comments, and passes.
- [ ] Test TestPOST_join_passwordGroup_requiresCorrectPassword exists, uses AAA comments, and passes.
- [ ] Test TestPOST_join_wrongPassword_returns403 exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/governance.go, apps/backend/internal/httpapi/groups.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T7 — do not start while open - Wave: 2
M4-T9: Implement named voter subset with minimum size 1 and every-member voter set mode¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T9 is wave 2 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Blocked until dependencies land: M4-T7.
Problem
Behavior for Implement named voter subset with minimum size 1 and every-member voter set mode is not implemented under apps/backend/internal/app/governance.go, packages/domain/votes.go.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
apps/backend/internal/app/governance.go,packages/domain/votes.go. No drive-by refactors. - Add
TestVoterSet_namedSubset_enforcesMinimumSizeOnein the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert). - Add
TestVoterSet_everyMemberMode_allowsAllMembersToVotein the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new HTTP surface. Internal Go/Swift symbols in Owns paths only.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
apps/backend/internal/app/governance.gopackages/domain/votes.go
Out of scope (do not touch) - Who may propose (needs-decision M4-T39)
Acceptance Criteria
- [ ] Test TestVoterSet_namedSubset_enforcesMinimumSizeOne exists, uses AAA comments, and passes.
- [ ] Test TestVoterSet_everyMemberMode_allowsAllMembersToVote exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/governance.go, packages/domain/votes.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestVoterSet_namedSubset_enforcesMinimumSizeOne exists, uses AAA comments, and passes.
- [ ] Test TestVoterSet_everyMemberMode_allowsAllMembersToVote exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/governance.go, packages/domain/votes.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T7 — do not start while open - Wave: 2
M4-T10: Enforce one user in many groups on membership rows¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T10 is wave 2 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Blocked until dependencies land: M4-T7.
Problem
Behavior for Enforce one user in many groups on membership rows is not implemented under apps/backend/internal/postgres/members.go.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
apps/backend/internal/postgres/members.go. No drive-by refactors. - Add
TestUser_inManyGroups_hasDistinctPositionsPerGroupin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new HTTP surface. Internal Go/Swift symbols in Owns paths only.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
apps/backend/internal/postgres/members.go
Out of scope (do not touch) - Cross-group people board math (M4-T28)
Acceptance Criteria
- [ ] Test TestUser_inManyGroups_hasDistinctPositionsPerGroup exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/postgres/members.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestUser_inManyGroups_hasDistinctPositionsPerGroup exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/postgres/members.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T7 — do not start while open - Wave: 2
M4-T11: Add GET /v1/groups/{id}/assets?query= with backend xStocks mint resolution for mobile catalog search¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T11 is wave 2 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Blocked until dependencies land: M4-T2.
Problem
GET /v1/groups/{id}/assets?query= and handler code under apps/backend/internal/httpapi/catalog.go, apps/backend/internal/xstocks/ do not exist (or fail locked tests).
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
apps/backend/internal/httpapi/catalog.go,apps/backend/internal/xstocks/. No drive-by refactors. - Register
GET /v1/groups/{id}/assets?query=in route table (apps/backend/cmd/api/orinternal/httpapi/). - Implement handler in
apps/backend/internal/xstocks/callinginternal/appmethod; return JSON status codes from API / data contract below. - Add
TestGET_assets_search_returnsBackendResolvedCatalogin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
Route: GET /v1/groups/{id}/assets?query=
Auth: Privy bearer token in Authorization header (same as M1 session middleware).
Happy: 200 with JSON body defined in milestone doc for this route.
Failure codes: 401 missing/invalid auth; 400 invalid body; 403 non-member; 404 unknown resource.
Idempotency: follow milestone doc keys (tx_signature, execute_request_id, privy_user_id, etc.).
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
apps/backend/internal/httpapi/catalog.goapps/backend/internal/xstocks/- Route:
GET /v1/groups/{id}/assets?query=
Out of scope (do not touch) - Swift xStocks calls
Acceptance Criteria
- [ ] Test TestGET_assets_search_returnsBackendResolvedCatalog exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/httpapi/catalog.go, apps/backend/internal/xstocks/.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestGET_assets_search_returnsBackendResolvedCatalog exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/httpapi/catalog.go, apps/backend/internal/xstocks/.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T2 — do not start while open - Wave: 2
M4-T12: Add POST /v1/groups/{id}/quotes and refuse proposal create when backend cannot quote USDC to output mint¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T12 is wave 2 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Blocked until dependencies land: M4-T11, M3-T3.
Problem
POST /v1/groups/{id}/quotes and handler code under apps/backend/internal/httpapi/quotes.go do not exist (or fail locked tests).
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
apps/backend/internal/httpapi/quotes.go. No drive-by refactors. - Register
POST /v1/groups/{id}/quotesin route table (apps/backend/cmd/api/orinternal/httpapi/). - Implement handler in
apps/backend/internal/httpapi/quotes.gocallinginternal/appmethod; return JSON status codes from API / data contract below. - Add
TestPOST_quotes_noRoute_returnsRoutableFalsein the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert). - Add
TestPOST_proposals_noRoute_refusesBeforeInsertin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
Route: POST /v1/groups/{id}/quotes
Auth: Privy bearer token in Authorization header (same as M1 session middleware).
Happy: 200 with JSON body defined in milestone doc for this route.
Failure codes: 401 missing/invalid auth; 400 invalid body; 403 non-member; 404 unknown resource.
Idempotency: follow milestone doc keys (tx_signature, execute_request_id, privy_user_id, etc.).
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
apps/backend/internal/httpapi/quotes.go- Route:
POST /v1/groups/{id}/quotes
Out of scope (do not touch) - Execute on pass
Acceptance Criteria
- [ ] Test TestPOST_quotes_noRoute_returnsRoutableFalse exists, uses AAA comments, and passes.
- [ ] Test TestPOST_proposals_noRoute_refusesBeforeInsert exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/httpapi/quotes.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestPOST_quotes_noRoute_returnsRoutableFalse exists, uses AAA comments, and passes.
- [ ] Test TestPOST_proposals_noRoute_refusesBeforeInsert exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/httpapi/quotes.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T11 — do not start while open - M3-T3 — do not start while open - Wave: 2
M4-T13: Implement buy proposal create with symbol, USDC amount, proposer, and expiry deadline¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T13 is wave 3 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Blocked until dependencies land: M4-T9, M4-T12.
Problem
Behavior for Implement buy proposal create with symbol, USDC amount, proposer, and expiry deadline is not implemented under apps/backend/internal/app/governance.go, apps/backend/internal/postgres/proposals.go.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
apps/backend/internal/app/governance.go,apps/backend/internal/postgres/proposals.go. No drive-by refactors. - Add
TestPOST_proposals_happyPath_createsOpenProposalWithExpiryin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new HTTP surface. Internal Go/Swift symbols in Owns paths only.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
apps/backend/internal/app/governance.goapps/backend/internal/postgres/proposals.go
Out of scope (do not touch) - Proposer permission rule until M4-T39 decision
Acceptance Criteria
- [ ] Test TestPOST_proposals_happyPath_createsOpenProposalWithExpiry exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/governance.go, apps/backend/internal/postgres/proposals.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestPOST_proposals_happyPath_createsOpenProposalWithExpiry exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/governance.go, apps/backend/internal/postgres/proposals.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T9 — do not start while open - M4-T12 — do not start while open - Wave: 3
M4-T14: Implement yes/no votes limited to current voter set members and tally to passed, failed, or expired¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T14 is wave 3 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Parent ticket; subissues: M4-T15, M4-T16, M4-T17, M4-T18.
Blocked until dependencies land: M4-T13.
Problem
Parent M4-T14 orchestration in packages/domain/votes.go, apps/backend/internal/app/governance.go missing; subissues M4-T15, M4-T16, M4-T17, M4-T18 not wired together.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
packages/domain/votes.go,apps/backend/internal/app/governance.go. No drive-by refactors. - Parent glue: wire subissues M4-T15, M4-T16, M4-T17, M4-T18 into
packages/domain/votes.goafter each subissue lands. - Satisfy milestone gate:
just test backendand/orjust test mobileexit 0 with no new failing tests.
API / data contract
Parent orchestration. Public contract is union of subissue routes/symbols: M4-T15, M4-T16, M4-T17, M4-T18.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
packages/domain/votes.goapps/backend/internal/app/governance.go
Out of scope (do not touch) - On-chain voting
Acceptance Criteria
- [ ] Files and symbols named in Proposal exist under packages/domain/votes.go, apps/backend/internal/app/governance.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] All subissues (M4-T15, M4-T16, M4-T17, M4-T18) closed before parent closes.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Files and symbols named in Proposal exist under packages/domain/votes.go, apps/backend/internal/app/governance.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] All subissues (M4-T15, M4-T16, M4-T17, M4-T18) closed before parent closes.
- [ ] Subissues closed: M4-T15, M4-T16, M4-T17, M4-T18.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T13 — do not start while open - Wave: 3 - Subissue: M4-T15 - Subissue: M4-T16 - Subissue: M4-T17 - Subissue: M4-T18
M4-T15: Implement unanimous tally among the voter set¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T15 is wave 3 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Subissue of M4-T14.
Blocked until dependencies land: M4-T14.
Problem
Subissue M4-T15 of M4-T14: symbols under packages/domain/votes.go not implemented; parent cannot close.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
packages/domain/votes.go. No drive-by refactors. - Subissue of
M4-T14. Do not add routes or flows owned by parent/sibling subissues. - Add
TestTallyProposal_unanimous_allYes_passesin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new public route. Subissue of M4-T14. Internal symbols only in Owns paths.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
packages/domain/votes.go
Out of scope (do not touch) - Majority mode
Acceptance Criteria
- [ ] Test TestTallyProposal_unanimous_allYes_passes exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under packages/domain/votes.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestTallyProposal_unanimous_allYes_passes exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under packages/domain/votes.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T14 — do not start while open - Wave: 3 - Parent: M4-T14
M4-T16: Implement majority tally among the voter set¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T16 is wave 3 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Subissue of M4-T14.
Blocked until dependencies land: M4-T14.
Problem
Subissue M4-T16 of M4-T14: symbols under packages/domain/votes.go not implemented; parent cannot close.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
packages/domain/votes.go. No drive-by refactors. - Subissue of
M4-T14. Do not add routes or flows owned by parent/sibling subissues. - Add
TestTallyProposal_majority_moreYesThanNo_passesin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert). - Add
TestTallyProposal_majority_moreNoThanYes_failsin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new public route. Subissue of M4-T14. Internal symbols only in Owns paths.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
packages/domain/votes.go
Out of scope (do not touch) - Unanimous mode
Acceptance Criteria
- [ ] Test TestTallyProposal_majority_moreYesThanNo_passes exists, uses AAA comments, and passes.
- [ ] Test TestTallyProposal_majority_moreNoThanYes_fails exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under packages/domain/votes.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestTallyProposal_majority_moreYesThanNo_passes exists, uses AAA comments, and passes.
- [ ] Test TestTallyProposal_majority_moreNoThanYes_fails exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under packages/domain/votes.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T14 — do not start while open - Wave: 3 - Parent: M4-T14
M4-T17: Expire open proposals at deadline with failed status and no swap¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T17 is wave 3 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Subissue of M4-T14.
Blocked until dependencies land: M4-T14.
Problem
Subissue M4-T17 of M4-T14: symbols under apps/backend/internal/app/governance.go not implemented; parent cannot close.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
apps/backend/internal/app/governance.go. No drive-by refactors. - Subissue of
M4-T14. Do not add routes or flows owned by parent/sibling subissues. - Add
TestTallyProposal_expiredOpenProposal_failsWithoutSwapin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new public route. Subissue of M4-T14. Internal symbols only in Owns paths.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
apps/backend/internal/app/governance.go
Out of scope (do not touch) - Failed execute retry (needs-decision M4-T40)
Acceptance Criteria
- [ ] Test TestTallyProposal_expiredOpenProposal_failsWithoutSwap exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/governance.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestTallyProposal_expiredOpenProposal_failsWithoutSwap exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/governance.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T14 — do not start while open - Wave: 3 - Parent: M4-T14
M4-T18: Add tests for unanimous pass, majority pass, majority fail, and expiry fail¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T18 is wave 5 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Subissue of M4-T14.
Blocked until dependencies land: M4-T15, M4-T16, M4-T17.
Problem
Locked test names for M4-T18 are absent or not wired into just test recipes; packages/domain/votes_test.go, apps/backend/internal/app/governance_test.go incomplete.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
packages/domain/votes_test.go,apps/backend/internal/app/governance_test.go. No drive-by refactors. - Subissue of
M4-T14. Do not add routes or flows owned by parent/sibling subissues. - Add
TestPOST_vote_nonVoterSetMember_returns403in the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert). - Add
TestPOST_vote_doubleVoteSameMember_isIdempotentOrRejectedin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert). - Add
TestPOST_vote_concurrentDoubleVote_recordsOneBallotin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new public route. Subissue of M4-T14. Internal symbols only in Owns paths.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
packages/domain/votes_test.goapps/backend/internal/app/governance_test.go
Out of scope (do not touch) - Jupiter execute integration
Acceptance Criteria
- [ ] Test TestPOST_vote_nonVoterSetMember_returns403 exists, uses AAA comments, and passes.
- [ ] Test TestPOST_vote_doubleVoteSameMember_isIdempotentOrRejected exists, uses AAA comments, and passes.
- [ ] Test TestPOST_vote_concurrentDoubleVote_recordsOneBallot exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under packages/domain/votes_test.go, apps/backend/internal/app/governance_test.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestPOST_vote_nonVoterSetMember_returns403 exists, uses AAA comments, and passes.
- [ ] Test TestPOST_vote_doubleVoteSameMember_isIdempotentOrRejected exists, uses AAA comments, and passes.
- [ ] Test TestPOST_vote_concurrentDoubleVote_recordsOneBallot exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under packages/domain/votes_test.go, apps/backend/internal/app/governance_test.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T15 — do not start while open - M4-T16 — do not start while open - M4-T17 — do not start while open - Wave: 5 - Parent: M4-T14
M4-T19: On pass, build Jupiter v2 transaction, sign treasury, POST execute, confirm Success code 0. Delete M3 stub route¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T19 is wave 4 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Parent ticket; subissues: M4-T20, M4-T21, M4-T22.
Blocked until dependencies land: M4-T14, M3-T5.
Problem
Parent M4-T19 orchestration in apps/backend/internal/app/start_buy.go, apps/backend/internal/httpapi/dev_buy.go missing; subissues M4-T20, M4-T21, M4-T22 not wired together.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
apps/backend/internal/app/start_buy.go,apps/backend/internal/httpapi/dev_buy.go. No drive-by refactors. - Parent glue: wire subissues M4-T20, M4-T21, M4-T22 into
apps/backend/internal/app/start_buy.goafter each subissue lands. - Add
TestExecuteOnPass_onlyAfterTallyPassed_callsJupiterin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert). - Add
TestDevBuyRoute_deleted_returns404in the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
Parent orchestration. Public contract is union of subissue routes/symbols: M4-T20, M4-T21, M4-T22.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
apps/backend/internal/app/start_buy.goapps/backend/internal/httpapi/dev_buy.go
Out of scope (do not touch) - Failed execute retry policy (needs-decision M4-T40)
Acceptance Criteria
- [ ] Test TestExecuteOnPass_onlyAfterTallyPassed_callsJupiter exists, uses AAA comments, and passes.
- [ ] Test TestDevBuyRoute_deleted_returns404 exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/start_buy.go, apps/backend/internal/httpapi/dev_buy.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] All subissues (M4-T20, M4-T21, M4-T22) closed before parent closes.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestExecuteOnPass_onlyAfterTallyPassed_callsJupiter exists, uses AAA comments, and passes.
- [ ] Test TestDevBuyRoute_deleted_returns404 exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/start_buy.go, apps/backend/internal/httpapi/dev_buy.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] All subissues (M4-T20, M4-T21, M4-T22) closed before parent closes.
- [ ] Subissues closed: M4-T20, M4-T21, M4-T22.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T14 — do not start while open - M3-T5 — do not start while open - Wave: 4 - Subissue: M4-T20 - Subissue: M4-T21 - Subissue: M4-T22
M4-T20: Record treasury holdings and write NAV snapshot on confirmed buy transaction¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T20 is wave 4 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Subissue of M4-T19.
Blocked until dependencies land: M4-T19.
Problem
Subissue M4-T20 of M4-T19: symbols under apps/backend/internal/postgres/nav_snapshots.go, apps/backend/internal/app/views.go not implemented; parent cannot close.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
apps/backend/internal/postgres/nav_snapshots.go,apps/backend/internal/app/views.go. No drive-by refactors. - Subissue of
M4-T19. Do not add routes or flows owned by parent/sibling subissues. - Add
TestExecuteOnPass_writesNavSnapshotOnConfirmin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new public route. Subissue of M4-T19. Internal symbols only in Owns paths.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
apps/backend/internal/postgres/nav_snapshots.goapps/backend/internal/app/views.go
Out of scope (do not touch) - Withdrawal snapshots (M4-T37)
Acceptance Criteria
- [ ] Test TestExecuteOnPass_writesNavSnapshotOnConfirm exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/postgres/nav_snapshots.go, apps/backend/internal/app/views.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestExecuteOnPass_writesNavSnapshotOnConfirm exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/postgres/nav_snapshots.go, apps/backend/internal/app/views.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T19 — do not start while open - Wave: 4 - Parent: M4-T19
M4-T21: Add idempotent execute guard keyed on proposal id and Jupiter tx signature¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T21 is wave 4 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Subissue of M4-T19.
Blocked until dependencies land: M4-T19.
Problem
Subissue M4-T21 of M4-T19: symbols under apps/backend/internal/app/start_buy.go, apps/backend/internal/postgres/transactions.go not implemented; parent cannot close.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
apps/backend/internal/app/start_buy.go,apps/backend/internal/postgres/transactions.go. No drive-by refactors. - Subissue of
M4-T19. Do not add routes or flows owned by parent/sibling subissues. - Add
TestExecuteOnPass_duplicateProposalAndSignature_executesOncein the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new public route. Subissue of M4-T19. Internal symbols only in Owns paths.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
apps/backend/internal/app/start_buy.goapps/backend/internal/postgres/transactions.go
Out of scope (do not touch) - Manual reconcile UX
Acceptance Criteria
- [ ] Test TestExecuteOnPass_duplicateProposalAndSignature_executesOnce exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/start_buy.go, apps/backend/internal/postgres/transactions.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestExecuteOnPass_duplicateProposalAndSignature_executesOnce exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/start_buy.go, apps/backend/internal/postgres/transactions.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T19 — do not start while open - Wave: 4 - Parent: M4-T19
M4-T22: Add tests for idempotent deposit credit, buy execute, and withdrawal payout¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T22 is wave 5 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Subissue of M4-T19.
Blocked until dependencies land: M4-T19, M4-T30.
Problem
Locked test names for M4-T22 are absent or not wired into just test recipes; apps/backend/internal/app/*_integration_test.go incomplete.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
apps/backend/internal/app/*_integration_test.go. No drive-by refactors. - Subissue of
M4-T19. Do not add routes or flows owned by parent/sibling subissues. - Add
TestIdempotency_depositSweepBuyWithdrawal_replaySignatureOnceEachin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new public route. Subissue of M4-T19. Internal symbols only in Owns paths.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
apps/backend/internal/app/*_integration_test.go
Out of scope (do not touch) - Mobile tests
Acceptance Criteria
- [ ] Test TestIdempotency_depositSweepBuyWithdrawal_replaySignatureOnceEach exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/*_integration_test.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestIdempotency_depositSweepBuyWithdrawal_replaySignatureOnceEach exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/*_integration_test.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T19 — do not start while open - M4-T30 — do not start while open - Wave: 5 - Parent: M4-T19
M4-T23: Implement member equity and percent return per group from positions¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T23 is wave 3 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Blocked until dependencies land: M4-T5.
Problem
Behavior for Implement member equity and percent return per group from positions is not implemented under packages/domain/pnl.go.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
packages/domain/pnl.go. No drive-by refactors. - Add
TestMemberEquity_matchesShareFractionTimesPotNavin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert). - Add
TestPercentReturn_equityOverNetInMinusOnein the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new HTTP surface. Internal Go/Swift symbols in Owns paths only.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
packages/domain/pnl.go
Out of scope (do not touch) - Swift formatters (M5-T23)
Acceptance Criteria
- [ ] Test TestMemberEquity_matchesShareFractionTimesPotNav exists, uses AAA comments, and passes.
- [ ] Test TestPercentReturn_equityOverNetInMinusOne exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under packages/domain/pnl.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestMemberEquity_matchesShareFractionTimesPotNav exists, uses AAA comments, and passes.
- [ ] Test TestPercentReturn_equityOverNetInMinusOne exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under packages/domain/pnl.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T5 — do not start while open - Wave: 3
M4-T24: Implement net USDC in per member per group from position columns¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T24 is wave 3 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Blocked until dependencies land: M4-T23.
Problem
Behavior for Implement net USDC in per member per group from position columns is not implemented under packages/domain/pnl.go.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
packages/domain/pnl.go. No drive-by refactors. - Add
TestPercentReturn_zeroNetIn_returnsNilin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new HTTP surface. Internal Go/Swift symbols in Owns paths only.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
packages/domain/pnl.go
Out of scope (do not touch) - People board aggregation
Acceptance Criteria
- [ ] Test TestPercentReturn_zeroNetIn_returnsNil exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under packages/domain/pnl.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestPercentReturn_zeroNetIn_returnsNil exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under packages/domain/pnl.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T23 — do not start while open - Wave: 3
M4-T25: Skip leaderboard rows when net USDC in equals zero¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T25 is wave 3 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Blocked until dependencies land: M4-T24.
Problem
Behavior for Skip leaderboard rows when net USDC in equals zero is not implemented under packages/domain/pnl.go, apps/backend/internal/app/views.go.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
packages/domain/pnl.go,apps/backend/internal/app/views.go. No drive-by refactors. - Add
TestBoards_skipRowsWhenNetUsdcInZeroin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new HTTP surface. Internal Go/Swift symbols in Owns paths only.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
packages/domain/pnl.goapps/backend/internal/app/views.go
Out of scope (do not touch) - Dollar-ranked boards
Acceptance Criteria
- [ ] Test TestBoards_skipRowsWhenNetUsdcInZero exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under packages/domain/pnl.go, apps/backend/internal/app/views.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestBoards_skipRowsWhenNetUsdcInZero exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under packages/domain/pnl.go, apps/backend/internal/app/views.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T24 — do not start while open - Wave: 3
M4-T26: Implement in-group member board ranked by percent return¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T26 is wave 3 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Blocked until dependencies land: M4-T25.
Problem
Behavior for Implement in-group member board ranked by percent return is not implemented under apps/backend/internal/app/views.go.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
apps/backend/internal/app/views.go. No drive-by refactors. - Add
TestInGroupBoard_ranksByPercentReturnNotDollarsin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new HTTP surface. Internal Go/Swift symbols in Owns paths only.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
apps/backend/internal/app/views.go
Out of scope (do not touch) - Mobile board UI (M5)
Acceptance Criteria
- [ ] Test TestInGroupBoard_ranksByPercentReturnNotDollars exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/views.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestInGroupBoard_ranksByPercentReturnNotDollars exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/views.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T25 — do not start while open - Wave: 3
M4-T27: Implement app-wide group board ranked by pot percent return¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T27 is wave 3 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Blocked until dependencies land: M4-T5.
Problem
Behavior for Implement app-wide group board ranked by pot percent return is not implemented under apps/backend/internal/app/views.go.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
apps/backend/internal/app/views.go. No drive-by refactors. - Add
TestGroupBoard_ranksPotsByPercentReturnin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new HTTP surface. Internal Go/Swift symbols in Owns paths only.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
apps/backend/internal/app/views.go
Out of scope (do not touch) - Swift home UI
Acceptance Criteria
- [ ] Test TestGroupBoard_ranksPotsByPercentReturn exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/views.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestGroupBoard_ranksPotsByPercentReturn exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/views.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T5 — do not start while open - Wave: 3
M4-T28: Implement app-wide people board ranked by summed cross-group percent return¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T28 is wave 3 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Blocked until dependencies land: M4-T24.
Problem
Behavior for Implement app-wide people board ranked by summed cross-group percent return is not implemented under apps/backend/internal/app/views.go.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
apps/backend/internal/app/views.go. No drive-by refactors. - Add
TestPeopleBoard_aggregatesCrossGroupNetInAndEquityin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new HTTP surface. Internal Go/Swift symbols in Owns paths only.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
apps/backend/internal/app/views.go
Out of scope (do not touch) - Profile UI (M5-T16)
Acceptance Criteria
- [ ] Test TestPeopleBoard_aggregatesCrossGroupNetInAndEquity exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/views.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestPeopleBoard_aggregatesCrossGroupNetInAndEquity exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/views.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T24 — do not start while open - Wave: 3
M4-T29: Add tests for percent ranking, zero-net skip, and full exit drop from in-group board¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T29 is wave 5 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Blocked until dependencies land: M4-T26, M4-T27, M4-T28.
Problem
Locked test names for M4-T29 are absent or not wired into just test recipes; packages/domain/pnl_test.go incomplete.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
packages/domain/pnl_test.go. No drive-by refactors. - Add
TestInGroupBoard_fullExit_dropsMemberFromBoardin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new API. Tests and/or Justfile wiring only.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
packages/domain/pnl_test.go
Out of scope (do not touch) - Snapshot UI tests
Acceptance Criteria
- [ ] Test TestInGroupBoard_fullExit_dropsMemberFromBoard exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under packages/domain/pnl_test.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestInGroupBoard_fullExit_dropsMemberFromBoard exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under packages/domain/pnl_test.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T26 — do not start while open - M4-T27 — do not start while open - M4-T28 — do not start while open - Wave: 5
M4-T30: Implement partial redeem with share amount or dollar target and dust minimum¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T30 is wave 4 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Parent ticket; subissues: M4-T31, M4-T32, M4-T33, M4-T34, M4-T35, M4-T36.
Blocked until dependencies land: M4-T5, M3-T9.
Problem
Parent M4-T30 orchestration in apps/backend/internal/app/redeem.go, packages/domain/redeem.go missing; subissues M4-T31, M4-T32, M4-T33, M4-T34, M4-T35, M4-T36 not wired together.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
apps/backend/internal/app/redeem.go,packages/domain/redeem.go. No drive-by refactors. - Parent glue: wire subissues M4-T31, M4-T32, M4-T33, M4-T34, M4-T35, M4-T36 into
apps/backend/internal/app/redeem.goafter each subissue lands. - Satisfy milestone gate:
just test backendand/orjust test mobileexit 0 with no new failing tests.
API / data contract
Parent orchestration. Public contract is union of subissue routes/symbols: M4-T31, M4-T32, M4-T33, M4-T34, M4-T35, M4-T36.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
apps/backend/internal/app/redeem.gopackages/domain/redeem.go
Out of scope (do not touch) - Creator dissolve (needs-decision M4-T41)
Acceptance Criteria
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/redeem.go, packages/domain/redeem.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] All subissues (M4-T31, M4-T32, M4-T33, M4-T34, M4-T35, M4-T36) closed before parent closes.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/redeem.go, packages/domain/redeem.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] All subissues (M4-T31, M4-T32, M4-T33, M4-T34, M4-T35, M4-T36) closed before parent closes.
- [ ] Subissues closed: M4-T31, M4-T32, M4-T33, M4-T34, M4-T35, M4-T36.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T5 — do not start while open - M3-T9 — do not start while open - Wave: 4 - Subissue: M4-T31 - Subissue: M4-T32 - Subissue: M4-T33 - Subissue: M4-T34 - Subissue: M4-T35 - Subissue: M4-T36
M4-T31: Debit position share units first in row-locked Postgres transaction¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T31 is wave 4 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Subissue of M4-T30.
Blocked until dependencies land: M4-T30.
Problem
Subissue M4-T31 of M4-T30: symbols under apps/backend/internal/app/redeem.go, apps/backend/internal/postgres/positions.go not implemented; parent cannot close.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
apps/backend/internal/app/redeem.go,apps/backend/internal/postgres/positions.go. No drive-by refactors. - Subissue of
M4-T30. Do not add routes or flows owned by parent/sibling subissues. - Add
TestRedeem_partialByShareAmount_debitsUnitsFirstin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert). - Add
TestRedeemJob_crashAfterDebit_resumesWithoutSecondDebitin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new public route. Subissue of M4-T30. Internal symbols only in Owns paths.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
apps/backend/internal/app/redeem.goapps/backend/internal/postgres/positions.go
Out of scope (do not touch) - Jupiter sell slice
Acceptance Criteria
- [ ] Test TestRedeem_partialByShareAmount_debitsUnitsFirst exists, uses AAA comments, and passes.
- [ ] Test TestRedeemJob_crashAfterDebit_resumesWithoutSecondDebit exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/redeem.go, apps/backend/internal/postgres/positions.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestRedeem_partialByShareAmount_debitsUnitsFirst exists, uses AAA comments, and passes.
- [ ] Test TestRedeemJob_crashAfterDebit_resumesWithoutSecondDebit exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/redeem.go, apps/backend/internal/postgres/positions.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T30 — do not start while open - Wave: 4 - Parent: M4-T30
M4-T32: Sell redeemed slice of each xStock to USDC on Jupiter when treasury holds stock¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T32 is wave 4 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Subissue of M4-T30.
Blocked until dependencies land: M4-T31.
Problem
Subissue M4-T32 of M4-T30: symbols under apps/backend/internal/app/redeem.go, apps/backend/internal/jupiter/sell.go not implemented; parent cannot close.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
apps/backend/internal/app/redeem.go,apps/backend/internal/jupiter/sell.go. No drive-by refactors. - Subissue of
M4-T30. Do not add routes or flows owned by parent/sibling subissues. - Add
TestRedeem_withXStockInTreasury_sellsSliceBeforePayoutin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new public route. Subissue of M4-T30. Internal symbols only in Owns paths.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
apps/backend/internal/app/redeem.goapps/backend/internal/jupiter/sell.go
Out of scope (do not touch) - Pay USDC to external wallet
Acceptance Criteria
- [ ] Test TestRedeem_withXStockInTreasury_sellsSliceBeforePayout exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/redeem.go, apps/backend/internal/jupiter/sell.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestRedeem_withXStockInTreasury_sellsSliceBeforePayout exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/redeem.go, apps/backend/internal/jupiter/sell.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T31 — do not start while open - Wave: 4 - Parent: M4-T30
M4-T33: Pay USDC only to payout address user proved with signed message. Persist withdrawal row¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T33 is wave 4 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Subissue of M4-T30.
Blocked until dependencies land: M4-T32.
Problem
Subissue M4-T33 of M4-T30: symbols under apps/backend/internal/privy/payout.go, apps/backend/internal/postgres/withdrawals.go not implemented; parent cannot close.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
apps/backend/internal/privy/payout.go,apps/backend/internal/postgres/withdrawals.go. No drive-by refactors. - Subissue of
M4-T30. Do not add routes or flows owned by parent/sibling subissues. - Add
TestRedeem_validProof_paysUsdcOnlyToProvenAddressin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert). - Add
TestRedeem_afterPayout_incrementsAmountWithdrawnAndWritesNavSnapshotin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new public route. Subissue of M4-T30. Internal symbols only in Owns paths.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
apps/backend/internal/privy/payout.goapps/backend/internal/postgres/withdrawals.go
Out of scope (do not touch) - Dissolve flow
Acceptance Criteria
- [ ] Test TestRedeem_validProof_paysUsdcOnlyToProvenAddress exists, uses AAA comments, and passes.
- [ ] Test TestRedeem_afterPayout_incrementsAmountWithdrawnAndWritesNavSnapshot exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/privy/payout.go, apps/backend/internal/postgres/withdrawals.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestRedeem_validProof_paysUsdcOnlyToProvenAddress exists, uses AAA comments, and passes.
- [ ] Test TestRedeem_afterPayout_incrementsAmountWithdrawnAndWritesNavSnapshot exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/privy/payout.go, apps/backend/internal/postgres/withdrawals.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T32 — do not start while open - Wave: 4 - Parent: M4-T30
M4-T34: Reject redeem when payout pubkey fails ownership proof¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T34 is wave 4 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Subissue of M4-T30.
Blocked until dependencies land: M4-T30.
Problem
Subissue M4-T34 of M4-T30: symbols under apps/backend/internal/app/redeem.go not implemented; parent cannot close.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
apps/backend/internal/app/redeem.go. No drive-by refactors. - Subissue of
M4-T30. Do not add routes or flows owned by parent/sibling subissues. - Add
TestRedeem_invalidPayoutProof_rejectedin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new public route. Subissue of M4-T30. Internal symbols only in Owns paths.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
apps/backend/internal/app/redeem.go
Out of scope (do not touch) - Retry UX for failed proof
Acceptance Criteria
- [ ] Test TestRedeem_invalidPayoutProof_rejected exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/redeem.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestRedeem_invalidPayoutProof_rejected exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/redeem.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T30 — do not start while open - Wave: 4 - Parent: M4-T30
M4-T35: Write NAV snapshot and recompute boards after confirmed withdrawal payout¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T35 is wave 4 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Subissue of M4-T30.
Blocked until dependencies land: M4-T33.
Problem
Subissue M4-T35 of M4-T30: symbols under apps/backend/internal/postgres/nav_snapshots.go, apps/backend/internal/app/views.go not implemented; parent cannot close.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
apps/backend/internal/postgres/nav_snapshots.go,apps/backend/internal/app/views.go. No drive-by refactors. - Subissue of
M4-T30. Do not add routes or flows owned by parent/sibling subissues. - Add
TestRedeem_afterPayout_incrementsAmountWithdrawnAndWritesNavSnapshotin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new public route. Subissue of M4-T30. Internal symbols only in Owns paths.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
apps/backend/internal/postgres/nav_snapshots.goapps/backend/internal/app/views.go
Out of scope (do not touch) - Mobile board refresh (M5-T19)
Acceptance Criteria
- [ ] Test TestRedeem_afterPayout_incrementsAmountWithdrawnAndWritesNavSnapshot exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/postgres/nav_snapshots.go, apps/backend/internal/app/views.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestRedeem_afterPayout_incrementsAmountWithdrawnAndWritesNavSnapshot exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/postgres/nav_snapshots.go, apps/backend/internal/app/views.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T33 — do not start while open - Wave: 4 - Parent: M4-T30
M4-T36: Add tests for debit-first ordering, slice USDC versus deposit refund, and board recompute¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T36 is wave 5 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Subissue of M4-T30.
Blocked until dependencies land: M4-T31, M4-T35.
Problem
Locked test names for M4-T36 are absent or not wired into just test recipes; apps/backend/internal/app/redeem_test.go incomplete.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
apps/backend/internal/app/redeem_test.go. No drive-by refactors. - Subissue of
M4-T30. Do not add routes or flows owned by parent/sibling subissues. - Add
TestRedeem_payoutEqualsRedeemedSliceNotDepositRefundin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert). - Add
TestRedeem_concurrentDoubleRedeem_debitsOncein the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new public route. Subissue of M4-T30. Internal symbols only in Owns paths.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
apps/backend/internal/app/redeem_test.go
Out of scope (do not touch) - Mainnet manual redeem
Acceptance Criteria
- [ ] Test TestRedeem_payoutEqualsRedeemedSliceNotDepositRefund exists, uses AAA comments, and passes.
- [ ] Test TestRedeem_concurrentDoubleRedeem_debitsOnce exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/redeem_test.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestRedeem_payoutEqualsRedeemedSliceNotDepositRefund exists, uses AAA comments, and passes.
- [ ] Test TestRedeem_concurrentDoubleRedeem_debitsOnce exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/app/redeem_test.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T31 — do not start while open - M4-T35 — do not start while open - Wave: 5 - Parent: M4-T30
M4-T37: Persist NAV snapshots on deposit, transaction confirm, and withdrawal payout¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T37 is wave 2 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Blocked until dependencies land: M4-T5.
Problem
Behavior for Persist NAV snapshots on deposit, transaction confirm, and withdrawal payout is not implemented under apps/backend/internal/postgres/nav_snapshots.go.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
apps/backend/internal/postgres/nav_snapshots.go. No drive-by refactors. - Add
TestNavSnapshot_writtenOnDepositConfirmTransactionConfirmWithdrawalPayoutin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new HTTP surface. Internal Go/Swift symbols in Owns paths only.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
apps/backend/internal/postgres/nav_snapshots.go
Out of scope (do not touch) - Chart history
Acceptance Criteria
- [ ] Test TestNavSnapshot_writtenOnDepositConfirmTransactionConfirmWithdrawalPayout exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/postgres/nav_snapshots.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestNavSnapshot_writtenOnDepositConfirmTransactionConfirmWithdrawalPayout exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/postgres/nav_snapshots.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T5 — do not start while open - Wave: 2
M4-T38: Wire after-hours flag when Pyth equity mark is frozen¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T38 is wave 2 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Blocked until dependencies land: M4-T5.
Problem
Behavior for Wire after-hours flag when Pyth equity mark is frozen is not implemented under apps/backend/internal/pyth/, apps/backend/internal/app/views.go.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
apps/backend/internal/pyth/,apps/backend/internal/app/views.go. No drive-by refactors. - Add
TestMarkedPot_afterHoursFlag_surfacesOnGroupViewin the*_test.goor Swift test target under Owns; use AAA comments (// Arrange,// Act,// Assert).
API / data contract
No new HTTP surface. Internal Go/Swift symbols in Owns paths only.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
apps/backend/internal/pyth/apps/backend/internal/app/views.go
Out of scope (do not touch) - Swift after-hours label (M5-T20)
Acceptance Criteria
- [ ] Test TestMarkedPot_afterHoursFlag_surfacesOnGroupView exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/pyth/, apps/backend/internal/app/views.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Test TestMarkedPot_afterHoursFlag_surfacesOnGroupView exists, uses AAA comments, and passes.
- [ ] Files and symbols named in Proposal exist under apps/backend/internal/pyth/, apps/backend/internal/app/views.go.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - M4-T5 — do not start while open - Wave: 2
M4-T39: Open decision ticket: who may propose a buy¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T39 is wave 5 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Problem
Product policy for Open decision ticket: who may propose a buy is not locked. Milestone doc lists options; no docs/ decision note or implementation exists yet.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
docs/. No drive-by refactors. - Create/update
docs/decision note listing options from milestone Open decisions. Do not implement API/UI behavior. Mark ticket blocked until product picks.
API / data contract
No API. Decision ticket only. Record chosen option in docs/ when product decides. Do not ship handler/UI behavior until decision ticket is closed.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
docs/
Out of scope (do not touch) - Implementation - API behavior - UI buttons - Custom on-chain programs - On-chain voting - Android/web - Privy production webhooks
Acceptance Criteria
- [ ] Files and symbols named in Proposal exist under docs/.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Files and symbols named in Proposal exist under docs/.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - None - Wave: 5
M4-T40: Open decision ticket: failed Jupiter execute after passed vote¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T40 is wave 5 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Problem
Product policy for Open decision ticket: failed Jupiter execute after passed vote is not locked. Milestone doc lists options; no docs/ decision note or implementation exists yet.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
docs/. No drive-by refactors. - Create/update
docs/decision note listing options from milestone Open decisions. Do not implement API/UI behavior. Mark ticket blocked until product picks.
API / data contract
No API. Decision ticket only. Record chosen option in docs/ when product decides. Do not ship handler/UI behavior until decision ticket is closed.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
docs/
Out of scope (do not touch) - Implementation - Automatic retry - Refund flows - Custom on-chain programs - On-chain voting - Android/web - Privy production webhooks
Acceptance Criteria
- [ ] Files and symbols named in Proposal exist under docs/.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Files and symbols named in Proposal exist under docs/.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - None - Wave: 5
M4-T41: Open decision ticket: creator leave and group dissolve¶
Context M4 replaces thin M1–M3 stubs with group rules, votes, marked NAV, redeem, and leaderboards before M5 product UI.
Ticket M4-T41 is wave 5 in docs/milestones/m4-domain.md under milestone M4 — Domain.
Problem
Product policy for Open decision ticket: creator leave and group dissolve is not locked. Milestone doc lists options; no docs/ decision note or implementation exists yet.
Proposal Implement in order. No drive-by refactors. Lock choices below; do not invent product policy.
- Edit only Owns paths:
docs/. No drive-by refactors. - Create/update
docs/decision note listing options from milestone Open decisions. Do not implement API/UI behavior. Mark ticket blocked until product picks.
API / data contract
No API. Decision ticket only. Record chosen option in docs/ when product decides. Do not ship handler/UI behavior until decision ticket is closed.
Test fakes (locked names):
- fakePythClient
- fakeJupiterClient
- fakePrivyClient
- integrationApp(t)
- domain factories in packages/domain tests
Scope¶
docs/
Out of scope (do not touch) - Implementation - Dissolve API - Treasury wind-down automation - Custom on-chain programs - On-chain voting - Android/web - Privy production webhooks
Acceptance Criteria
- [ ] Files and symbols named in Proposal exist under docs/.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
Verification
Baseline (run before marking done):
- just test backend
Done when (zero wiggle room):
- [ ] Every Acceptance Criteria checkbox is satisfied.
- [ ] Files and symbols named in Proposal exist under docs/.
- [ ] just test backend exits 0 with no new failures.
- [ ] Out of scope paths untouched; no unrelated refactors.
- [ ] No drive-by edits outside Scope; Out of scope untouched.
Dependencies - None - Wave: 5
Automated verification¶
Commands. just test backend (includes packages/domain unit tests and apps/backend integration tests).
Test layers¶
| Layer | Runs under | What it proves |
|---|---|---|
packages/domain unit |
just test backend |
Pure math: NAV, share minting on deposit, vote tally, percent return, redeem slice, board rank — no I/O, no clock. |
| Go unit | just test backend |
app orchestration with fake Pyth, fake Jupiter, fake Privy — governance, redeem state machine, view assembly. |
| Go integration | just test backend |
HTTP → Postgres lifecycle for join, propose, vote, pass-execute, redeem, boards; fake external clients, real local Postgres. |
| Concurrency | just test backend |
Double-vote, double-redeem, concurrent execute on same proposal/signature. |
Vote and tally tests do not call real Jupiter. Pass-execute integration uses fakeJupiterClient. Domain tests use README worked numbers as literals (Alex/Blair). AAA comments on every test.
Test map¶
M4-T3 / M4-T4 — deposit credit at NAV (domain + integration)
TestSharesForDeposit_usdcOnlyPot_creditsOneToOneWithSweptUsdc(M2 path preserved)TestSharesForDeposit_markedPot_mintsSharesFromPotNav(M4 minting)TestDepositCredit_readmeAlexBlairWorkedExample_matchesLiterals(M4-T4 — steps 1–6 from README)TestObserveSweep_postBuyDeposit_doesNotLetNewMemberCaptureUnrealizedGain
M4-T5 / M4-T6 — pot NAV (domain)
TestComputePotNAV_usdcOnly_returnsTreasuryUsdcTestComputePotNAV_mixedPot_sumsUsdcAndMarkedHoldingsTestComputePotNAV_emptyShares_firstDepositUsesOneDollarPerShareTestComputePotNAV_zeroTotalShares_returnsErrorOrBootstrapPrice
M4-T7 / M4-T8 — group rules (integration)
TestPOST_groups_persistsJoinPolicyVoterSetThresholdExpiryTestPOST_join_openGroup_addsMemberWithoutPasswordTestPOST_join_passwordGroup_requiresCorrectPasswordTestPOST_join_wrongPassword_returns403
M4-T9 / M4-T10 — membership (integration)
TestVoterSet_namedSubset_enforcesMinimumSizeOneTestVoterSet_everyMemberMode_allowsAllMembersToVoteTestUser_inManyGroups_hasDistinctPositionsPerGroup
M4-T11 / M4-T12 / M4-T13 — catalog and proposals (integration)
TestGET_assets_search_returnsBackendResolvedCatalog(mobile never calls xStocks)TestPOST_quotes_noRoute_returnsRoutableFalseTestPOST_proposals_noRoute_refusesBeforeInsertTestPOST_proposals_happyPath_createsOpenProposalWithExpiry
M4-T14 / M4-T15 / M4-T16 / M4-T17 / M4-T18 — votes (domain + integration)
TestTallyProposal_unanimous_allYes_passes(M4-T15)TestTallyProposal_majority_moreYesThanNo_passes(M4-T16)TestTallyProposal_majority_moreNoThanYes_failsTestTallyProposal_expiredOpenProposal_failsWithoutSwap(M4-T17)TestPOST_vote_nonVoterSetMember_returns403TestPOST_vote_doubleVoteSameMember_isIdempotentOrRejected(idempotency)TestPOST_vote_concurrentDoubleVote_recordsOneBallot(race)
M4-T19 / M4-T20 / M4-T21 — execute on pass (integration)
TestExecuteOnPass_onlyAfterTallyPassed_callsJupiterTestExecuteOnPass_setsProposalIdOnTransactionRowTestExecuteOnPass_writesNavSnapshotOnConfirmTestExecuteOnPass_duplicateProposalAndSignature_executesOnce(M4-T21)TestDevBuyRoute_deleted_returns404(M3 stub removed)
M4-T23 / M4-T24 / M4-T25 / M4-T26 / M4-T27 / M4-T28 / M4-T29 — P&L and boards (domain + integration)
TestPercentReturn_equityOverNetInMinusOneTestPercentReturn_zeroNetIn_returnsNil(skip row)TestMemberEquity_matchesShareFractionTimesPotNavTestInGroupBoard_ranksByPercentReturnNotDollars(M4-T26)TestGroupBoard_ranksPotsByPercentReturn(M4-T27)TestPeopleBoard_aggregatesCrossGroupNetInAndEquity(M4-T28)TestInGroupBoard_fullExit_dropsMemberFromBoard(M4-T29)TestBoards_skipRowsWhenNetUsdcInZero(M4-T25)
M4-T30–M4-T36 — redeem (unit + integration)
TestRedeem_partialByShareAmount_debitsUnitsFirst(M4-T31, row-lock in integration)TestRedeem_payoutEqualsRedeemedSliceNotDepositRefund(M4-T36)TestRedeem_withXStockInTreasury_sellsSliceBeforePayout(M4-T32)TestRedeem_invalidPayoutProof_rejected(M4-T34)TestRedeem_validProof_paysUsdcOnlyToProvenAddress(M4-T33)TestRedeem_afterPayout_incrementsAmountWithdrawnAndWritesNavSnapshot(M4-T35)TestRedeemJob_crashAfterDebit_resumesWithoutSecondDebit(state machine)TestRedeem_concurrentDoubleRedeem_debitsOnce(race)
M4-T37 / M4-T38 — snapshots and after-hours (integration)
TestNavSnapshot_writtenOnDepositConfirmTransactionConfirmWithdrawalPayoutTestMarkedPot_afterHoursFlag_surfacesOnGroupView
M4-T22 — idempotency suite (integration)
TestIdempotency_depositSweepBuyWithdrawal_replaySignatureOnceEach
Product constraints encoded in tests
- Swift consumes
GET /v1/groups/{id}/assetsonly — no xStocks/Jupiter/Pyth/Solana RPC from mobile. - Rank by percent return, never dollars.
- Redeem debits
share_unitsbefore outbound USDC. - Open decisions (M4-T39–T41) have no automated behavior until product locks them.
Fixtures / fakes¶
fakePythClient—USDCOnlyPot,MarkedPotwith configurable marks andafterHours.fakeJupiterClient,fakePrivyClient— from M3; addVerifyPayoutProof,PayUSDC.fakeClock— proposal expiry and vote deadlines.buildGroupWithRules(overrides),buildProposal(overrides),buildPosition(overrides),buildMarkedHolding(overrides).readmeAlexBlairScenario()— factory for the worked example inputs.integrationApp(t)— real Postgres, all external clients faked.
Testability note. Vote tally and NAV math belong in packages/domain; handlers should not duplicate formulas. Redeem resume requires injectable job store and explicit RedeemJobStatus transitions testable without mainnet.
Property / invariant tests¶
In packages/domain with testing/quick or rapid:
TestProperty_sharesForDeposit_neverExceedsDepositedValueAtCurrentNav— new shares × nav per share ≤ deposited USDC (fairness).TestProperty_memberEquity_sumsToPotNavWhenAllSharesHeld— Σ equity == pot NAV for partition of total shares.TestProperty_percentReturn_monotonicWithEquityAtFixedNetIn— higher equity ⇒ higher or equal % return.TestProperty_redeemSlice_proportionalToSharesRedeemed— redeem fraction × pot NAV bounds payout.TestProperty_tallyProposal_deterministic— same votes ⇒ same status.TestProperty_boardRanking_percentReturnOrder— shuffled members; board order matches sorted % return.
Manual verification¶
Two-account demo script on slim sim or devices. Automated tests cover math, idempotency, and board ordering; manual proves Privy/Jupiter/mainnet UX.
- Create a group via mobile with password join, named voter subset, majority threshold, and short expiry.
- Join from a second account with the password. Confirm two names on the in-group member board.
- Both deposit mainnet USDC. Confirm sweep UX and boards show 0% until a mark moves.
- Propose
AAPLx, pass the vote. Confirm swap success in explorer and proposal UI chips. - On the group screen, confirm pot composition, both slices, dollar P&L, and in-group percent board order.
- On app home, confirm group board and people board rows.
- Partial redeem with signed payout proof. Confirm USDC arrives at the payout wallet in explorer.
- Let a second proposal expire without pass. Confirm UI shows expired and no new swap in explorer.
Out of scope¶
Custom on-chain vault or share-token program. On-chain voting. Meteora DBC, DAMM, Clawpump. Privy production webhooks. Android or web. Copy-trading. Backed issuer mint or redeem APIs. App Store listing, full KYC or AML, securities licensing.
Open decisions¶
Who may propose. Failed execute after pass. Creator leave and dissolve. M4-T39 through M4-T41 record them only.