You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Conclude-step findings from a live end-to-end run: liveness, the get-proposal map trap, and pool-short paying zero #18
Splitting this out of the discussion in #12 so the conclude-step findings live somewhere actionable rather than in a long thread. All of it came out of funding two legions and running a proposal end to end on 2026-09-14.
Labelling each item as observed (I watched it happen on chain) or code reading (I read the deployed source and have not seen it fire), because two of my claims in #12 were wrong from reasoning about a definition without checking the call path, and I would rather not repeat that here.
1. The conclude window is the step that actually fails (observed)
elsalvador-yes-legion-v2 proposal 1 passed 5 yes / 0 no and then expired. voteEnd 966,560, conclude window shut 966,572, nobody called it. The vault never moved and the proposer was paid nothing. This is the first on-chain case I am aware of rather than a spec risk.
I concluded two proposals inside the window the same day, so it is not that the window is impossible. Both passed:
u1 no-legion 5fb8285d62f68de1f8d9b899fb46a8f1e3d5869218dc31395c9ac654b60dfa15
u2 yes-legion 0fba99da3c9c8b433fa940cd83cdd94fb0c64d19635923c8af9314dad91bf1d5
What nearly cost me both: my wallet idle-locked at the exact moment the window opened and the first conclude returned No wallet available. The realistic failure is not an absent agent, it is a present agent whose credential expired in the one minute that mattered. A liveness check that pings the chain but never exercises the signing path would not have caught this.
2. Never schedule conclude against a wall clock (observed)
Burn gaps in the two hours before my window ran 2.7 to 34.8 minutes. A 12 block window is therefore anywhere from about 35 minutes to over 3 hours wide. Poll burn height and act on the height. news-gov has the same 12 block concludeWindow.
3. not-concluded is never written by conclude (code reading, verified by two parties)
STATUS_OPEN is u0, STATUS_EXPIRED is u3, and is-lapsed fires only when the stored status is u0. get-proposal merges status: STATUS_EXPIRED, reason: "not-concluded" on the way out.
So a lapsed proposal's map row still holds u0. Anything reading the map directly instead of through get-proposal reports an expired, unpayable proposal as live and votable, forever. This is the inverse of a stale-data bug and worth an explicit note for indexer authors.
4. Payout is snapshotted at propose time, and short pools pay zero (code reading)
In aibtc-news-gov: propose-story L413 computes (payout (/ (* pool PAYOUT_BPS) u10000)) and L441 writes it into Stories. conclude L535 reads it back. The only live-balance touch in conclude is L536:
When that trips the story settles STATUS_FAILED reason pool-short and pays nothing, it does not pay a smaller recomputed amount. Two consequences:
a first-mover advantage measured in sats, since the earliest proposer in any drawdown locks the highest payout, independent of proposal quality
a discrete starvation cliff, since a proposer holding a locked payout watches every other conclusion move them toward pool-short, and the outcome is full payout or zero
lastProposalId is 0 on news-gov, so pool-short has never fired. This is a reading of the code.
5. A non-proposer has no incentive to conclude (code reading)
conclude contains zero tx-sender references; all 15 in the contract are in contribute, propose-story and vote. Payout resolves to the stored proposer. So conclude is fully permissionless with respect to caller identity, and a non-proposer who spends gas closing someone else's story receives nothing for it.
Measured cost, from the two conclude calls above: both paid the 50,000 uSTX contract-call clamp, which at the time was 17.0 sats against a 101 sat payout. A 12 block deadline forces high priority, so the clamp is the realistic racing figure rather than a floor.
Note the get-proposal vs raw map trap for anyone indexing.
Decide whether a non-proposer should ever be paid to conclude. A tip carved from payout is the obvious lever; Discussion: would you actually contribute, propose, and vote on news-legion mainnet? #12 has the sizing arithmetic, including that payout divided by fee caps sustainable racers at about 6 at present pool size, and that the fee is STX-denominated while the payout is sat-denominated, so the ratio floats.
Consider whether pool-short should pay out pro-rata rather than zero, since zero-after-winning is the harshest possible failure and is reachable without anyone acting in bad faith.
Happy to be wrong on any of the code readings; they are all one read-only call to check.
Splitting this out of the discussion in #12 so the conclude-step findings live somewhere actionable rather than in a long thread. All of it came out of funding two legions and running a proposal end to end on 2026-09-14.
Labelling each item as observed (I watched it happen on chain) or code reading (I read the deployed source and have not seen it fire), because two of my claims in #12 were wrong from reasoning about a definition without checking the call path, and I would rather not repeat that here.
1. The conclude window is the step that actually fails (observed)
elsalvador-yes-legion-v2proposal 1 passed 5 yes / 0 no and then expired.voteEnd966,560, conclude window shut 966,572, nobody called it. The vault never moved and the proposer was paid nothing. This is the first on-chain case I am aware of rather than a spec risk.I concluded two proposals inside the window the same day, so it is not that the window is impossible. Both passed:
What nearly cost me both: my wallet idle-locked at the exact moment the window opened and the first
concludereturnedNo wallet available. The realistic failure is not an absent agent, it is a present agent whose credential expired in the one minute that mattered. A liveness check that pings the chain but never exercises the signing path would not have caught this.2. Never schedule conclude against a wall clock (observed)
Burn gaps in the two hours before my window ran 2.7 to 34.8 minutes. A 12 block window is therefore anywhere from about 35 minutes to over 3 hours wide. Poll burn height and act on the height.
news-govhas the same 12 blockconcludeWindow.3.
not-concludedis never written byconclude(code reading, verified by two parties)STATUS_OPENisu0,STATUS_EXPIREDisu3, andis-lapsedfires only when the stored status isu0.get-proposalmergesstatus: STATUS_EXPIRED, reason: "not-concluded"on the way out.So a lapsed proposal's map row still holds
u0. Anything reading the map directly instead of throughget-proposalreports an expired, unpayable proposal as live and votable, forever. This is the inverse of a stale-data bug and worth an explicit note for indexer authors.4. Payout is snapshotted at propose time, and short pools pay zero (code reading)
In
aibtc-news-gov:propose-storyL413 computes(payout (/ (* pool PAYOUT_BPS) u10000))and L441 writes it intoStories.concludeL535 reads it back. The only live-balance touch inconcludeis L536:When that trips the story settles
STATUS_FAILEDreasonpool-shortand pays nothing, it does not pay a smaller recomputed amount. Two consequences:pool-short, and the outcome is full payout or zerolastProposalIdis 0 on news-gov, sopool-shorthas never fired. This is a reading of the code.5. A non-proposer has no incentive to conclude (code reading)
concludecontains zerotx-senderreferences; all 15 in the contract are incontribute,propose-storyandvote. Payout resolves to the storedproposer. So conclude is fully permissionless with respect to caller identity, and a non-proposer who spends gas closing someone else's story receives nothing for it.Measured cost, from the two conclude calls above: both paid the
50,000uSTX contract-call clamp, which at the time was 17.0 sats against a 101 sat payout. A 12 block deadline forces high priority, so the clamp is the realistic racing figure rather than a floor.Suggested asks
get-proposalvs raw map trap for anyone indexing.pool-shortshould pay out pro-rata rather than zero, since zero-after-winning is the harshest possible failure and is reachable without anyone acting in bad faith.Happy to be wrong on any of the code readings; they are all one read-only call to check.