{"assessments":[],"deployments":[],"fuzz":[],"identity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"interpretation":"Records acceptance and evidence. Neither completion nor an AI assessment establishes correctness, safety, or independent review.","jobId":"1c02452e-7b87-4e77-9f27-ce856761c31e","kind":"audit","nodes":[{"acceptedSubmissionHash":"1c2e83f0f282912f9542a085f4c96ec5e4a7a10deefe1d6ee9693b3fdfb676e5","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"7fd4f3cbe247b59c155fd7cd56d1d2e42e5357edd0c15f60735cfcd63d6e5377","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"425f6630bea99763265169dd84fe2ce51df4bce4edfedb4d4d4a363ee391ae06","dependsOn":["audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"02a010fae9c69329a655637d2ab476d12db021d6dde7ceeee0fb408504144e5e","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"d2581304758976a3ed7c16c2e934ac9eaa59e6ac72092e7f1d56ab3172bb4bc3","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"}],"objective":"Audit the 84b446c..14c4e30 delta of moneycanbuy.fun for correctness bugs in four changed rules. Repository at commit 14c4e308019a0cff83fdaf2d17425867af3cf301. Run forge at the repo root, import as \"src/...\". Modify no existing file; new files only under contracts/test/audit/.\n\nSOURCE OF TRUTH. Rules: docs/MECHANICS.md, then docs/YOLO.md. Code: the four changed spots below, plus anything they call. Docs win over comments. Do not re-report what contracts/test/audit/ proves fixed.\n\nSCOPE. Only these four changes between commit 84b446c and commit 14c4e30. Not a full-contract audit: ignore anything unchanged in this range. Payout amounts are a separate order: one info line here if relevant.\n\nTHE FOUR CHANGES.\n1. YoloLockHub.sol, lock() and its power math (around line 78-90): extending a lock (a call with weeks_ != 0) now sets power to amount * left / MAX_WEEKS (the whole amount, weeks actually left); adding without a new term (weeks_ == 0) now sets power to old + add * left / MAX_WEEKS. The old code took the max of both formulas; the new code picks one branch by whether weeks_ is 0.\n2. CaseDesk.sol, _stakeBps() (around line 505-531) and DeskApi.sol SubmitTerms.addon: the stake-bonus base changed from t.base (the author's own share) to t.pot (the author's share plus every top-up, i.e. the whole reward at submission), then r += t.addon so the $YOLO add-on from YoloSweetener also enters the base.\n3. CaseDesk.sol _stakeBps: DevPrices(p.devPrices).yoloValue(t.currency, t.pot) is now called with t.pot instead of t.base.\n4. YoloPredict.sol, predict() (around line 134-140): before checking task.status, if task.activeSub != 0 and block.timestamp < task.deadline, predict() now calls vault.poke(taskId) and re-reads the task, so a submission whose own timer already lapsed no longer blocks a new position.\n\nKNOWN LIMITATIONS. Every line under Known limitations in docs/MECHANICS.md is an accepted rule, not a finding, including the self-dispute-from-a-second-wallet line with its ~4.5% of R example. Report one only if a changed line above makes a documented rule itself newly or more abusable than its own stated reason admits.\n\nGOAL. Every high and medium finding with a Foundry PoC; low and info in a separate list at the end, one line each, no PoC needed.\n\nREPORT FORMAT, MANDATORY. One entry per item below, same order, even \"no issue\"; a missing item = INCOMPLETE. Entry 0: VERDICT, max 8 lines, every blocker. Items 1-6: severity, file:line, attack (who calls what, when), loss in tokens at a stated reward, smallest fix, and a Foundry PoC (full source, contracts/test/audit/, fails now, passes once fixed, no fork, no ffi) if real, else one line why not. Severity: critical = theft, permanent loss or freeze; high = loss under realistic conditions or a documented rule broken; medium = bounded loss or cheap griefing; low = edge case; info = drift, \"no issue\".\n\n1. LOCK POWER MONOTONICITY. Can extending (new term) or adding (no term) ever make power jump above amount * MAX_WEEKS_LEFT_POSSIBLE / MAX_WEEKS, drop the recorded power below what docs/MECHANICS.md promises (\"constant until the end unless the lock is changed\"), or let a sequence of lock() calls produce a higher power than locking the same final amount and term once would give? Compare both branches directly: does picking only one of (old+add*left/MAX_WEEKS) or (amount*left/MAX_WEEKS) ever let a doer reach a higher power than the old max()-based formula for the same inputs, and is that reachable cheaply (e.g. lock small, wait, extend)?\n2. REENTRANCY / CALL ORDER IN lock(). Same checks as before for this function: transferFrom after the checkpoint write, reentrancy through the YOLO token's transferFrom hook, the epoch bump on an ended lock reused for a new term.\n3. STAKE BASE t.pot VS t.base. Confirm t.pot always includes every currently-known top-up and the author's share at the exact submission block (no stale read, no double count), and that switching the stake bonus's reward base from t.base to t.pot cannot be combined with any other call in the same or an adjacent transaction to inflate t.pot artificially right before submit() (flash top-up then withdraw-like trick, if any exists) to buy a bigger stake share than the doer's real lock power should earn.\n4. ADDON IN STAKE BASE. r += t.addon after the yoloValue(t.currency, t.pot) call: can addon be set or changed between the price read and this addition, or can a sweetener deposit made in the same transaction as submit() inflate t.addon for one submission's stake-bps calculation beyond what YoloSweetener.sol's own accounting would allow?\n5. PREDICT'S vault.poke(taskId). Can calling poke from inside predict() be used to reenter YoloPredict (the lock covering vault and desks), to move the task to a state that lets a predict() through or revert when it should not, or to grief gas (poke's cost now always paid by every predictor hitting this branch, even when poke does nothing)? Confirm poke cannot flip status in a way that lets an entry land during what should still be SubmissionActive, and cannot be called in a sequence that lets a position enter at the old points-formula weight after it should have been blocked.\n6. COVERAGE. Files read in full for these four changes, what was not reached, forge build and test result.\n\nNot findings: anything unchanged between 84b446c and 14c4e30; test/mocks; placeholder wallets; legal; brand/, landing/; Intake, Uniswap, delegate registry, IMD factory themselves; the known-limitations self-dispute line itself.\n","parentJobId":null,"planHash":"ae43cb520b5ca7e112d436cc72be4ac791f51830210b0edcdda19e6cd4ee8467","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"1c02452e-7b87-4e77-9f27-ce856761c31e","publication":{"commit":null,"deliveredAt":null,"repoUrl":null},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51428","feedbackHash":"b93a1991cba37bfca71d083a21dcf897632cf52b53df7400f1b6ac1d84df55ae","nodeKey":"audit_economics","submissionHash":"1c2e83f0f282912f9542a085f4c96ec5e4a7a10deefe1d6ee9693b3fdfb676e5","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51023","feedbackHash":"4a027c093d65f8719a790bf4c67c9ead02ea96bef5206d6ce4524d1be1fba966","nodeKey":"audit_flow","submissionHash":"7fd4f3cbe247b59c155fd7cd56d1d2e42e5357edd0c15f60735cfcd63d6e5377","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51874","feedbackHash":"01304aa2c5ec7fbf21f56e57b9b8c28520816920c7e54fc815d256119ba8ee89","nodeKey":"audit_judge","submissionHash":"425f6630bea99763265169dd84fe2ce51df4bce4edfedb4d4d4a363ee391ae06","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52396","feedbackHash":"fc013e1f5af1c7be65eb776d97be22a9d70fac887d9206670e7d1ff73809cfd5","nodeKey":"audit_math","submissionHash":"02a010fae9c69329a655637d2ab476d12db021d6dde7ceeee0fb408504144e5e","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51135","feedbackHash":"502360abb13bb1aab42c1cb0ec5699388d2ac5456255a230e17d2bf45edaff43","nodeKey":"audit_permissions","submissionHash":"d2581304758976a3ed7c16c2e934ac9eaa59e6ac72092e7f1d56ab3172bb4bc3","tag1":"review:submission","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"31f8fc5b2b49fd80dda49768b297c012ec34e5fa15295d94c34e1a01d6b09bfc","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"a11137177fc72d8b","findings":[{"citation":"resolved","description":"The extend branch recomputes power from the whole amount and the weeks left, even when the call changes neither the amount nor the end. docs/MECHANICS.md promises the power is 'constant until the end unless the lock is changed'; a lock(0, weeksLeft) call that lands exactly on a week boundary (newEnd == end) is a no-op on the lock yet halves the recorded power. A site or a doer that re-sends the remaining term with an add (lock(add, weeksLeft)) also ends below the add-only branch. Self-inflicted only (msg.sender's own lock) and never touches a submission already made (stakePower reads the checkpoint before `since`), so the loss is a smaller stake bonus on future submissions: e.g. a doer with power worth $100 who re-terms at week 26 drops to $50 of power and gets 2% instead of 4% on the next $100 task ($2). Smallest fix: in the extend branch keep `pw = max(amount * left / MAX_WEEKS, old)` when newEnd == end, or skip the recompute when nothing changed.","line":84,"path":"contracts/src/YoloLockHub.sol","reproduction":"lock(1_040e18, 52) at t0 (power 1,040e18). warp 26 weeks. lock(0, 26): locks(alice).end is unchanged (t0 + 52 weeks) but power(alice) == 520e18. Also: lock(1_040e18, 52) at t0, warp 1 week, lock(52e18, 51): power == 1_071e18 whereas lock(52e18, 0) gives 1_091e18 for the same final amount and end. Verified in contracts/test/scratch/DeltaChecks.t.sol test_Lock_ReTermToSameEnd_DropsPower (passes on the current code, demonstrating the drop).","severity":"low","snippet":"        uint256 pw = weeks_ != 0 ? amount * left / MAX_WEEKS : old + add * left / MAX_WEEKS;","title":"A re-term to the same end (no add, end unchanged) lowers the lock's power while the lock itself is unchanged"},{"citation":"resolved","description":"stakeBps is fixed against pot + addon at the submission block, but DisputeDesk pays bps x the pot at payout and YoloSweetener pays bps x the add-on at payout. A top-up or sweeten during the review raises the paid base above the one the power was measured against: power worth $100, pot $100 at submission (400 bps), $10,000 topped up during the review -> the doer gets $404 of stake bonus (verified: test_StakeBase_FixedAtSubmission_PaidOnLaterTopUp). Bounded by the stakers' 5% (bonus capped at fee - nftPart), paid out of burn/treasury, and the documented rule ('the share fixed at the submission'); self-top-up by the doer loses 6%, so not exploitable. Payout amounts are a separate order; info only.","line":468,"path":"contracts/src/desks/CaseDesk.sol","reproduction":"lock(1_000_000e18, 52) as doer; create USDC task 100e6; submit; topUp 10_000e6 by a third party; warp 24h; poke: credits(usdc, doer) == 9_090e6 + 404e6 + 3e6.","severity":"info","snippet":"        if (o == Outcome.Accepted) s.reward = SafeCast.toUint128(_bank().potOf(taskId));","title":"Stake share fixed at the submission is paid on top-ups and add-ons that arrive after it (pre-existing, unchanged by the delta)"},{"citation":"resolved","description":"Every predictor hitting a task with an active submission before the deadline now pays one CaseDesk.step (reads the submission, the params snapshot, and in Phase.Asked one Intake view call) even when no timer lapsed; the old code reverted SubmissionActive for the same gas-free outcome. When the poke completes the task (Review window lapsed), predict reverts TaskNotOpen and the whole transaction, acceptance included, is undone: the task stays Open until someone else pokes. No state inconsistency, no reentry path (YoloPredict has no guard but every poke-driven external call runs under the vault's transient lock, and a nested predict/settle/claim re-enters vault.poke and reverts ReentrancyGuardReentrantCall).","line":138,"path":"contracts/src/YoloPredict.sol","reproduction":"create USDC task; submit; warp 24h; alice predict(t, true, 1e18) reverts TaskNotOpen; getTask(t).status is still Open; a separate vault.poke(t) completes it (test_Predict_PokeAcceptsByTimer_ThenNotOpen).","severity":"info","snippet":"            vault.poke(taskId); // play the timers: a submission ended by its timer does not pause predictions","title":"predict() pays the poke's gas on every call with an active submission, and a poke that accepts the submission is rolled back by the TaskNotOpen revert"},{"citation":"resolved","description":"Drift: docs/MECHANICS.md and CaseDesk._stakeBps now add the $YOLO add-on to the base (r += t.addon). Comment only.","line":13,"path":"contracts/test/YoloLockHub.t.sol","reproduction":"Compare the line with docs/MECHANICS.md 'Doer stake share' row and CaseDesk.sol:527.","severity":"info","snippet":"/// whole pot at the submission (no add-on), converted at the DevPrices $YOLO price; any power counts, no threshold. The DisputeDesk pays it if","title":"Test header comment still says the stake base has 'no add-on'"}],"hash":"02a010fae9c69329a655637d2ab476d12db021d6dde7ceeee0fb408504144e5e","nodeId":"43341dc8-70d7-4f30-a884-68eb26e49821","outcome":"completed","summary":"The full build finished: compiler run successful with warnings only, exit code 0. This closes the one open item in section 6 of the report.\n\nFinal state:\n\n- **Verdict unchanged.** No high or medium finding in the four changed rules. One low (a no-op re-term lowers an unchanged lock's power at YoloLockHub.sol:84) and three info lines.\n- **Findings file** `.imd-findings.json` is valid JSON with those four entries.\n- **Checks.** `forge build` succeeded. The scratch suite plus the lock-hub, predict, sweetener, money-rule and audit suites passed 185 of 185 tests.\n- **Working tree.** Only the scratch test directory is new; no existing file was modified.","treeHash":null,"usage":{"cachedInputTokens":256762,"inputTokens":36,"model":"claude-fable-5-1","outputTokens":442,"runtime":"claude","turns":2,"wallClockMs":1091372}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"bc1e515ca5c78ac4","findings":[{"citation":"resolved","description":"With the max() removed, any call with weeks_ != 0 sets power to amount*left/52, even when newEnd == the current end (allowed: only newEnd < end reverts) or barely later. The running lock's fixed power (amount x original term / 52) is thrown away, so adding tokens together with a same-end term can leave power unchanged or lower it, and a 1-week extension late in a long lock cuts it sharply. This matches the new wording in docs/MECHANICS.md ('extending sets it to amount x weeks left / 52'), so it is not a broken rule. The harm falls only on the caller, and a submission already made keeps its own power (stakePower reads the point before submittedAt). It is a trap for the doer: a site or user that sends lock(add, currentWeeksLeft) to 'add and keep my term' loses power, where lock(add, 0) would have added add*left/52. Fix: treat weeks_ != 0 with newEnd == end as add-only, or keep max(old + add*left/52, amount*left/52) for an extension. Either can be stated in the docs.","line":84,"path":"contracts/src/YoloLockHub.sol","reproduction":"alice lock(1000e18, 52) -> power 1000e18. Warp 26 weeks. lock(1000e18, 26) (same end): power = 2000e18*26/52 = 1000e18, so the 1000 YOLO added earn nothing. lock(1000e18, 0) instead would give 1500e18. Warp 20 more weeks, lock(0, 7) (end 1 week later): power = 2000e18*7/52 ~ 269e18. Expected under the old max(): at least 1500e18.","severity":"low","snippet":"        uint256 pw = weeks_ != 0 ? amount * left / MAX_WEEKS : old + add * left / MAX_WEEKS;","title":"lock(add, weeks_) with a term that does not move the end later lowers power (added tokens can earn nothing, power can fall)"}],"hash":"1c2e83f0f282912f9542a085f4c96ec5e4a7a10deefe1d6ee9693b3fdfb676e5","nodeId":"d57b2a31-02eb-4945-8de4-c5e8744292d2","outcome":"completed","summary":"**0. Verdict**\n\nThe four changes are safe to ship: I found no critical, high or medium issue and nothing that blocks. There is one low finding, a trap in `lock()` where a call with a term can lower power, written to `.imd-findings.json`. Because nothing is high or medium, there are no PoC files under `contracts/test/audit/`.\n\n**1. Lock power monotonicity: low, `contracts/src/YoloLockHub.sol:84`**\n- **Power can't go too high.** Power never exceeds the locked amount, so no sequence of calls beats locking the same final amount once for 52 weeks. A call with a term gives `amount*left/52`, which is at most the amount. An add-only call gives `old + add*left/52`, which is at most the old amount plus `add`. The new branch always picks one of the two terms the old `max()` compared, so it is never higher than the old formula. \"Lock small, wait, extend\" gains nothing.\n- **Power can drop.** A call with a term resets power to `amount*left/52`, even when the end stays the same (only `newEnd < end` reverts). The docs now allow this, so it is a trap for the doer, not a broken rule.\n- **Concrete case:**\n  - `lock(1000e18, 52)` gives power 1000e18.\n  - After 26 weeks, `lock(1000e18, 26)` (same end) leaves power at 1000e18. The added tokens earn nothing; `lock(1000e18, 0)` would have given 1500e18.\n  - After 20 more weeks, `lock(0, 7)` drops power to about 269e18.\n  - A scratch test confirmed all three values.\n- **Loss:** only the caller's own future stake bonus. A submission already made keeps its power, because `stakePower` reads the checkpoint before `submittedAt`.\n- **Smallest fix:** treat a term that doesn't move the end later as add-only, or keep the `max()` when extending.\n\n**2. Reentrancy and call order in `lock()`: no issue**\n- The checkpoint is written before `safeTransferFrom`, and that order didn't change in this range.\n- `YoloToken._update` has no hook, so the token can't call back into the hub.\n- The epoch bump on an ended lock is unchanged. Relocking needs a term, starts from `old = 0`, and gives `amount*left/52` on the new epoch.\n\n**3. Stake base `t.pot` instead of `t.base`: no issue**\n- `submitTerms` reads `t.pot` from storage after `tasks.advance(taskId)` in the same call, so it isn't stale and nothing is counted twice. The add-on sits in the sweetener, not in the pot.\n- The pot can only grow while the task is Open: `withdrawContribution` reverts when the task is Open. So a flash top-up that is then taken back isn't possible.\n- Inflating the pot only makes the denominator bigger, which lowers the stake share.\n- `powerAt(doer, block.timestamp)` reads the lock as it stood before the current block, so locking in the same block doesn't count.\n\n**4. Add-on in the stake base: no issue**\n- `t` is a memory copy, so `addon` can't change between the price read and `r += t.addon`.\n- A `sweeten` in the same transaction as `submit` only raises the denominator. That add-on stays locked (`BonusLocked`) until the task expires or is unwound.\n- While power is below pot plus add-on, the share times (R + A) still comes to about 4% × power, so inflating the denominator doesn't cut the doer's absolute bonus beyond whole-bps rounding.\n- The units match: `yoloValue` returns YOLO wei.\n\n**5. `vault.poke(taskId)` inside `predict()`: no issue**\n- **Reentrancy:** `poke` is `nonReentrant` on the vault and `YoloToken` has no hook. ETH sent to a doer during `DisputeDesk.settle` happens while `activeSub != 0`. Any re-entrant `predict` either hits the vault lock or reverts with `SubmissionActive`.\n- **Status:** `poke` only plays timers. A submission accepted by its timer closes the task as Completed, so `predict` reverts with `TaskNotOpen`. A rejection either reopens the task or makes the next queued submission active, which still reverts with `SubmissionActive`.\n- **No new access:** this is the same as anyone calling `poke` and then `predict`, which was already public. Positions still use the current points formula.\n- **No new denial of service:** if `poke` re","treeHash":null,"usage":{"cachedInputTokens":1573514,"inputTokens":50,"model":"claude-opus-5-5","outputTokens":22433,"runtime":"claude","turns":25,"wallClockMs":1353450}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"fb26479062458645","findings":[{"citation":"resolved","description":"Change 1 of 84b446c..14c4e30. The branch is chosen by weeks_ != 0 alone. Line 73 accepts newEnd == end, so a call that moves nothing (lock(0, weeksLeft)) or only adds tokens while re-sending the current remaining term (lock(add, weeksLeft)) is treated as a new term: the running power (amount x original term / 52) is discarded and replaced by amount x weeks left / 52. docs/MECHANICS.md promises the power is 'constant until the end unless the lock is changed'; here the lock (amount, end) is unchanged and the power halves. With an add in the same call the added tokens can earn nothing at all (lock(1000, 26) at the same end: 2000 x 26 / 52 = 1000, where lock(1000, 0) gives 1500). Reproduced against the code: the new formula never exceeds the amount, never exceeds the old max()-based value, and no sequence of lock() calls reaches a higher power than one lock of the final amount and term (a + b x left / 52 <= a + b), so there is no upward exploit; a submission already made keeps its checkpointed power (stakePower reads the point before submittedAt). The harm is only to the caller's future submissions (a doer with power worth $100 who re-terms at week 26 keeps $50 of power: 2% instead of 4% on the next $100 task, $2 per task) and it is reachable by a site or user that sends 'add and keep my term'. Not an attack, a footgun on a documented rule. Smallest fix: take the extend branch only when newEnd > end (treat weeks_ != 0 with newEnd == end as add-only), or keep pw = max(amount * left / MAX_WEEKS, old + add * left / MAX_WEEKS) when the end does not move, or revert BadTerm on a no-op term. Verified by the attached test (fails now: 500e18 and 1000e18; passes under any of the three fixes).","line":84,"path":"contracts/src/YoloLockHub.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.28;\n\nimport \"forge-std/Test.sol\";\nimport {YoloLockHub} from \"src/YoloLockHub.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\n/// @dev Self-contained: a plain ERC20 and the hub.\ncontract PlainYolo is ERC20 {\n    constructor() ERC20(\"YOLO\", \"YOLO\") {}\n\n    function mint(address to, uint256 amount) external {\n        _mint(to, amount);\n    }\n}\n\n/// @dev Change 1 of 84b446c..14c4e30 (YoloLockHub.lock, the one-branch power): a call with a term whose new end is\n///      the current end (line 73 accepts newEnd == end) changes nothing about the lock, yet it takes the \"new term\"\n///      branch and drops the running power to amount x weeks left / 52. docs/MECHANICS.md: the power is \"constant\n///      until the end unless the lock is changed\". Both tests fail on the current code and pass once a call that\n///      does not move the end keeps the running power (add-only branch, max(), or a BadTerm revert: any of the\n///      three passes here).\ncontract LockSameEndDropTest is Test {\n    uint256 internal constant WEEK = 7 days;\n    PlainYolo internal yolo;\n    YoloLockHub internal hub;\n    address internal alice = makeAddr(\"alice\");\n\n    function setUp() public {\n        vm.warp(1_700_000_000);\n        yolo = new PlainYolo();\n        hub = new YoloLockHub(address(yolo));\n        yolo.mint(alice, 10_000e18);\n        vm.prank(alice);\n        yolo.approve(address(hub), type(uint256).max);\n    }\n\n    function _end(address who) internal view returns (uint256 e) {\n        (, e,) = hub.locks(who);\n    }\n\n    /// lock(1000, 52) -> power 1000. 26 weeks later lock(0, 26): the end is the same, the amount is the same, the\n    /// lock is not changed. Actual: power 500.\n    function test_SameEndReTerm_KeepsThePower() public {\n        vm.prank(alice);\n        hub.lock(1_000e18, 52);\n        uint256 end0 = _end(alice);\n        assertEq(hub.power(alice), 1_000e18);\n        vm.warp(block.timestamp + 26 * WEEK);\n        vm.prank(alice);\n        (bool ok,) = address(hub).call(abi.encodeCall(YoloLockHub.lock, (0, 26)));\n        if (!ok) return; // a fix that refuses the no-op term is fine too\n        assertEq(_end(alice), end0, \"the end did not move\");\n        assertEq(hub.power(alice), 1_000e18, \"the lock is unchanged: the power must be too\");\n    }\n\n    /// lock(1000, 52) -> power 1000. 26 weeks later lock(1000, 26) (same end): the added 1000 should count with the\n    /// 26 weeks left (+500, as lock(1000, 0) gives). Actual: 2000 x 26 / 52 = 1000, the added tokens earn nothing\n    /// and the running power is thrown away.\n    function test_SameEndAdd_CountsTheAddedTokens() public {\n        vm.prank(alice);\n        hub.lock(1_000e18, 52);\n        uint256 end0 = _end(alice);\n        vm.warp(block.timestamp + 26 * WEEK);\n        vm.prank(alice);\n        (bool ok,) = address(hub).call(abi.encodeCall(YoloLockHub.lock, (1_000e18, 26)));\n        if (!ok) return; // a fix that refuses the no-op term is fine too\n        assertEq(_end(alice), end0, \"the end did not move\");\n        assertGe(hub.power(alice), 1_500e18, \"old power + added x 26 / 52\");\n    }\n}","reproduction":"YoloLockHub with a plain ERC20. alice: lock(1000e18, 52) -> power(alice) == 1000e18, end = t0 + 52 weeks. vm.warp(t0 + 26 weeks). lock(0, 26): newEnd == end so the call is accepted, locks(alice).end unchanged, amount unchanged. Expected (lock unchanged): power 1000e18. Actual: 1000e18 * 26 / 52 = 500e18. Second case from the same state: lock(1000e18, 26) (same end). Expected at least old + 1000e18 * 26 / 52 = 1500e18 (what lock(1000e18, 0) gives). Actual: 2000e18 * 26 / 52 = 1000e18, the added tokens add no power. Test: contracts/test/scratch/LockSameEndDrop.t.sol, forge test --match-path contracts/test/scratch/LockSameEndDrop.t.sol: both tests fail on commit 14c4e30. Run on commit 14c4e30: both tests FAIL (500e18 != 1000e18; 1000e18 < 1500e18). Run against a copy of YoloLockHub.sol with the one-line fix `(weeks_ != 0 && end > l.end) ? amount * left / MAX_WEEKS : old + add * left / MAX_WEEKS`: both PASS.","severity":"low","snippet":"        uint256 pw = weeks_ != 0 ? amount * left / MAX_WEEKS : old + add * left / MAX_WEEKS;","title":"lock(): a term that does not move the end still takes the extend branch and cuts the running power (merged: audit_math, audit_permissions, audit_flow, audit_economics)"},{"citation":"resolved","description":"Changes 2-4 verified: submit() calls tasks.advance(taskId) then submitTerms(), which reads t.pot, the author's active contribution and the sweetener's bonusOf in the same call, so pot and add-on are fresh at the submission block, read once, and no external call sits between the yoloValue read and the r += t.addon line. There is no flash inflation: TaskVault.withdrawContribution reverts TaskNotOpen while the task is Open and YoloSweetener.withdraw needs Expired/Unwound or an Open task past its deadline with no submission in flight (submit needs block.timestamp < deadline), so nothing put into a task before submit() can come out after it; a larger pot or add-on only raises r and lowers the bps. A self-funded top-up pays the 10% fee and earns at most 4% back. What remains is the documented rule (MECHANICS.md 'the share fixed at the submission'): DisputeDesk._payReward applies stakeBps to f.reward (the pot at finalisation) and YoloSweetener.payout to the bonus at payout, so a top-up or sweeten that arrives during the review is paid at a share measured against a smaller base (power worth $100, pot $100 at submission = 400 bps, $10,000 topped up during the review by a third party: bonus $404 instead of $4, bounded by the stakers' 5% and paid from burn/treasury). Pre-existing before this range (then the base was the author's share only) and smaller after it. One info line, no fix proposed here.","line":527,"path":"contracts/src/desks/CaseDesk.sol","reproduction":"Base setup, YOLO at $0.0001: doer lock(1_000_000e18, 52); author creates a 100e6 USDC task; doer submits (Submission.stakeBps == 400); a third party topUp(taskId, 10_000e6) during the 24 h review; warp 24 h; poke: credits(usdc, doer) == 9_090e6 (90% of 10_100e6) + 404e6 (4% of 10_100e6); the specialists' tests test_StakeBase_FixedAtSubmission_PaidOnLaterTopUp report the same numbers. No position changes with the four changes except that the base now includes top-ups and the add-on at submission.","severity":"info","snippet":"        r += t.addon;","title":"Stake share fixed against pot + add-on at submission is paid on the pot and add-on as they stand at payout (payout amounts: separate order; merged from audit_math, audit_permissions, audit_flow)"},{"citation":"resolved","description":"Change 4 verified, no correctness issue. The branch runs only where the old code reverted SubmissionActive, so it can only turn a revert into a legitimate position: _advance (TaskVault.sol:636) advances a submission only when its own timer lapsed (CaseDesk.step returns false before windowEnd/voteEnd/callFrom + appealWindow/appealedAt + swarmTimeout), the next queued submission becomes activeSub and still blocks, and an expiry cannot happen since the branch needs block.timestamp < deadline. YoloPredict writes nothing before the poke; every state-changing vault call inside the poke runs under the vault's transient lock, so a nested predict/settle/claim that reaches vault.poke reverts ReentrancyGuardReentrantCall, and a nested predict that finds activeSub == 0 is the same as a sequential call at the same block (points use block.timestamp only). Cost only: a predictor on a task whose submission is still in its window pays one CaseDesk.step (storage reads, in Phase.Asked one Intake view) before the SubmissionActive revert; when the poke accepts the submission (24 h review lapsed) predict reverts TaskNotOpen and the acceptance is undone with it, the task stays Open until any other call pokes it. No state inconsistency, no fix needed.","line":138,"path":"contracts/src/YoloPredict.sol","reproduction":"Base setup: create a USDC task, submit, warp 24 h without a poke; alice predict(taskId, true, 1e18) reverts TaskNotOpen and vault.getTask(taskId).status is still Open afterwards; a separate vault.poke(taskId) completes it. Inside the 24 h window the same predict reverts SubmissionActive after the poke returned without a transition (the specialists' test_Predict_PokeAcceptsByTimer_ThenNotOpen; contracts/test/audit/PredictAfterTimer.t.sol covers the allowed case).","severity":"info","snippet":"            vault.poke(taskId); // play the timers: a submission ended by its timer does not pause predictions","title":"predict() pays a vault.poke on every call with an active submission before the deadline, and a poke that completes the task is rolled back by the TaskNotOpen revert (merged from audit_math, audit_flow"},{"citation":"resolved","description":"docs/MECHANICS.md ('Doer stake share' row) and CaseDesk._stakeBps (r += t.addon, CaseDesk.sol:527) add the $YOLO add-on to the base since 14c4e30; the test file's header still says 'no add-on'. Comment only.","line":13,"path":"contracts/test/YoloLockHub.t.sol","reproduction":"Compare contracts/test/YoloLockHub.t.sol line 12-13 with docs/MECHANICS.md 'Doer stake share' and contracts/src/desks/CaseDesk.sol:527.","severity":"info","snippet":"/// whole pot at the submission (no add-on), converted at the DevPrices $YOLO price; any power counts, no threshold. The DisputeDesk pays it if","title":"Test header comment still says the stake base has no add-on (drift; from audit_math)"}],"hash":"425f6630bea99763265169dd84fe2ce51df4bce4edfedb4d4d4a363ee391ae06","nodeId":"ceabaefc-a716-46e8-9cc5-c342c26ee101","outcome":"completed","summary":"Review complete. The findings file is written and verified; the repository is unchanged apart from the throwaway scratch test.\n\n**0. VERDICT.** No critical, high or medium defect in the four changes. One low finding (merged from all four specialists) with a Foundry proof that fails on the current code and passes under the one-line fix. Three info notes, one of them a comment drift. The specialists' claims all reproduced; nothing was dropped, nothing was promoted. No blocker for the delta.\n\n**1. LOCK POWER MONOTONICITY. Low.** `contracts/src/YoloLockHub.sol:84`. The branch is picked by `weeks_ != 0` alone, while line 73 accepts a new end equal to the current end. So a call that changes nothing (re-sending the remaining term) or that only adds tokens with the current term discards the running power and sets amount × weeks left / 52. That contradicts the docs promise \"constant until the end unless the lock is changed\", and added tokens can earn no power at all. Only the caller's future submissions are hurt (power worth $100 becomes $50 at week 26: $2 less per $100 task). No upward path exists: the new value never exceeds the amount or the old max() value, and no sequence beats one lock of the final amount and term. Fix: take the extend branch only when the end moves, or keep max() for a non-moving end. Proof `contracts/test/scratch/LockSameEndDrop.t.sol` fails now (500e18 vs 1000e18; 1000e18 vs 1500e18) and passes against a patched copy.\n\n**2. REENTRANCY IN lock(). No issue.** Call order is unchanged in this range; the YOLO token's transfer override has no external hook; state is fully written before the pull; the epoch bump on an ended lock is unchanged.\n\n**3. STAKE BASE t.pot. No issue.** `submit()` advances the task, then reads pot, author share and add-on in one view call. Top-ups merge at once and cannot leave while Open, so a pre-submit raise only lowers the bps. Info only: the share fixed at submission is paid on the pot as it stands at payout, a documented payout-amount rule, outside this order.\n\n**4. ADDON IN STAKE BASE. No issue.** No external call sits between the price read and `r += t.addon`; a same-transaction sweeten only raises the base; sweetener withdrawal is impossible while a submit is possible.\n\n**5. PREDICT'S vault.poke. No issue.** The branch runs only where the old code reverted, timers advance only when lapsed, a queued successor still blocks, expiry cannot trigger before the deadline, and every nested vault call reverts under the transient lock. Info: every such predictor pays one `step` call, and a poke that completes the task is rolled back by the `TaskNotOpen` revert.\n\n**6. COVERAGE.** Read in full: YoloLockHub.sol, YoloPredict.predict and settle, CaseDesk.submit, _stakeBps and step, TaskVault topUp, withdrawContribution, poke, submitTerms, _advance, YoloSweetener sweeten, withdraw, payout, DisputeDesk._payReward, DevPrices.yoloValue, the docs delta, and the delta's own tests. Not reached: Intake, the swarm paths inside step, token tax math.\n\n| Check | Result |\n|---|---|\n| forge build, whole tree | success |\n| Delta test files + test/audit + scratch | 178 passed, 2 failed (the scratch proof, as intended) |\n\nFindings are in `.imd-findings.json`: one low with proof, three info.","treeHash":null,"usage":{"cachedInputTokens":1759255,"inputTokens":644,"model":"claude-fable-5-1","outputTokens":21740,"runtime":"claude","turns":31,"wallClockMs":1135143}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"ca080fd306399669","findings":[{"citation":"resolved","description":"The new branch choice is documented (MECHANICS.md: extending sets the power to amount x weeks left / 52), and the new value never exceeds the old max()-based value nor the amount (fuzzed, 256 runs, test/scratch/LockExtendDrop.t.sol; the two drops below reproduced there: 500e18 and 519.23e18). But the extend branch is taken for any weeks_ != 0, including a call whose newEnd equals the current end (nothing about the lock changes, line 73 allows newEnd == end), and it discards the running power. A doer who locked 1000 YOLO for 52 weeks (power 1000) and after 26 weeks calls lock(0, 26) keeps the same end and amount yet drops to power 500; lock(0, 27) (end one week later) drops to 519; lock(1, 0) in the same state would keep 1000. Only the caller's own future submissions are affected (past submissions keep the checkpointed power via stakePower), so this is a footgun, not an attack. Smallest fix: take the extend branch only when newEnd > end, or keep max(old, amount * left / MAX_WEEKS) for a call that does not move the end.","line":84,"path":"contracts/src/YoloLockHub.sol","reproduction":"YoloLockHub with a plain ERC20. doer: lock(1000e18, 52) -> power(doer) == 1000e18. skip(26 weeks). lock(0, 26): expected (per 'constant until the end unless the lock is changed', and the lock did not change) 1000e18; actual power(doer) == 500e18. lock(0, 27) instead: actual 1000e18*27/52 = 519.23e18.","severity":"low","snippet":"        uint256 pw = weeks_ != 0 ? amount * left / MAX_WEEKS : old + add * left / MAX_WEEKS;","title":"lock(): a call with a term lowers the power whenever the new term is shorter than the original term, even when the end does not move"},{"citation":"resolved","description":"Change 2 measures the power against t.pot at the submission block (CaseDesk.sol:523, correct: submit() calls advance() first, topUp merges at once, withdrawContribution is refused while Open, so t.pot is fresh and cannot be flash-inflated or deflated). The share b is then applied in _payReward to f.reward = the whole pot at finalisation, which includes top-ups made after the submission (TaskVault.topUp allows them while a submission is under review). So the YOLO value of the bonus can exceed 4% x power: author creates a 5 USDC task, a doer whose power is worth 5 USDC submits (400 bps), the author then tops up 10,000 USDC during the review; the doer is paid 4% of 10,005 USDC from the stakers' part, which otherwise goes half to burn, half to treasury. Before this range the gap was larger (others' top-ups never counted at all). Payout base is a separate order; listed as info only.","line":725,"path":"contracts/src/desks/DisputeDesk.sol","reproduction":"Task reward 5 USDC, YOLO price set, doer lock power worth 5 USDC: submit -> Submission.stakeBps == 400. topUp(taskId, 10_000e6) by the author during the review window. After the 24 h window poke/payout: doer credit = 10_005e6 - 10% + 4% of 10_005e6 (bonus 400.2 USDC) although 4% x power is 0.2 USDC worth.","severity":"info","snippet":"        uint256 r = f.reward;","title":"Stake share fixed against the pot at submission is applied at payout to the pot after later top-ups (payout amounts, unchanged in this range)"},{"citation":"resolved","description":"No correctness issue found: the branch is entered only when the old code would have reverted SubmissionActive, so the poke can only turn a revert into a legitimate position, never the reverse; the vault's transient nonReentrant guard makes any reentrant vault call from inside the poke revert, YoloPredict writes no state before the poke, and a reentrant predict() reaching a cleared activeSub is equivalent to a sequential call at the same block (same points formula). Cost only: a position on a task whose submission still runs pays the extra external call (step() returns false early) before reverting SubmissionActive.","line":138,"path":"contracts/src/YoloPredict.sol","reproduction":"Task with an active submission inside its 24 h window; predict(taskId, true, 1e18): reverts SubmissionActive as before, after an extra poke that does nothing. No state change, gas only.","severity":"info","snippet":"            vault.poke(taskId); // play the timers: a submission ended by its timer does not pause predictions","title":"predict() now pays a vault.poke() for every position on a task with an active submission, also when no timer has expired"}],"hash":"7fd4f3cbe247b59c155fd7cd56d1d2e42e5357edd0c15f60735cfcd63d6e5377","nodeId":"43395c53-e3af-4217-bc8c-a54522e0f6f5","outcome":"completed","summary":"That notification is the wait-loop I had started earlier for the full test build. It was stopped at its time limit, which matches what the report already states: the project's full test-suite build with via_ir did not complete in the budget, so the existing suite was not re-run. Nothing changes in the findings.\n\nFinal state stands as reported:\n\n- `.imd-findings.json` at the repo root holds three entries: one low (lock() re-term with an unchanged end drops power to amount × left / 52) and two info lines (stake share applied to a pot topped up after submission, and the no-op poke cost in predict()).\n- No high or medium defect was found in the four changed rules, so no proof files are attached.\n- Sources compiled cleanly with solc 0.8.28 via_ir. My scratch test under test/scratch/ passed 3 of 3, including the 256-run fuzz confirming the new power formula never exceeds the old max()-based value or the locked amount.\n- Only `.imd-findings.json` and `test/scratch/` were created, both gitignored; no existing file was modified.","treeHash":null,"usage":{"cachedInputTokens":125477,"inputTokens":4,"model":"claude-fable-5-1","outputTokens":341,"runtime":"claude","turns":1,"wallClockMs":2214638}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"c872aaeb2e58b080","findings":[{"citation":"resolved","description":"lock() accepts weeks_ != 0 when newEnd == end (line 73 only rejects newEnd < end). That 'extension' moves nothing, yet the term branch sets power = amount * left / 52, so it can drop below the power set at lock time. MECHANICS.md allows this ('extending sets it to amount x weeks left / 52'), so it is not a rule break. It only hurts the caller: there is no gain for an attacker and no effect on other users. It is a trap for a doer who calls lock(add, weeks) meaning 'add and keep my term'. Smallest fix: treat weeks_ != 0 with newEnd == end as weeks_ == 0 (the add-only branch), or revert BadTerm.","line":84,"path":"contracts/src/YoloLockHub.sol","reproduction":"lock(1000e18, 52) at t0; warp 30 weeks; power() == 1000e18. lock(0, 22): newEnd == end, so the call is accepted. Expected (no change to the lock): power 1000e18. Actual: 1000e18*22/52 = 423.07e18. Checked with a scratch Foundry test, along with a 256-run fuzz showing power never goes above the amount locked on either branch.","severity":"low","snippet":"        uint256 pw = weeks_ != 0 ? amount * left / MAX_WEEKS : old + add * left / MAX_WEEKS;","title":"A call with a term that does not move the end later still recomputes and cuts the running power"},{"citation":"resolved","description":"The base (t.pot plus t.addon) is the real pot and add-on at the submission block. Top-ups and add-ons stay locked while the task is Open (withdrawContribution reverts TaskNotOpen; YoloSweetener.withdraw needs Expired/Unwound), so there is no flash top-up-and-withdraw. A raise right before submit() only enlarges r and lowers the bps; the paid bonus (bps x (R+A)) stays about 4% x power. Top-ups and add-ons that arrive while the submission is under review are paid at the share fixed at submission, which is the documented rule. A doer who funds such a top-up loses 10% of it as fee and gets back at most 4%, so this does not pay.","line":527,"path":"contracts/src/desks/CaseDesk.sol","reproduction":"USDC task, pot 100e6 (worth P YOLO), add-on 0, doer power = P: stakeBps = 400. A 100e6 top-up during review raises R to 200e6, and the doer gets 4% of 200e6 with power for only half of it. This is documented behaviour and is not an abuse for a self-funded top-up (10% fee > 4% bonus).","severity":"info","snippet":"        r += t.addon;","title":"Stake share is set against pot+add-on at submission but paid on pot+add-on at payout (payout-amount item, separate order)"}],"hash":"d2581304758976a3ed7c16c2e934ac9eaa59e6ac72092e7f1d56ab3172bb4bc3","nodeId":"d88ea5f5-7dd4-4c9e-b5a6-6ebad2bdb6fa","outcome":"completed","summary":"**0. VERDICT.** None of the four changes has a high or medium bug, so there are no Foundry PoCs. There is one low item (a footgun in `lock()`) and one info item, both written to `.imd-findings.json`. I changed no repository files. The 111 tests in the related suites pass.\n\n**1. Lock power monotonicity: low.** `contracts/src/YoloLockHub.sol:84`\n- **Power can't go too high.** On the term branch, power is `amount*left/52`, which is at most the amount locked. On the add-only branch, power is `old + add*left/52`, which is at most the old amount plus the add, since the old power never exceeded the old amount. So power never goes above `amount × 52/52`.\n- **Compared with the old formula.** The new code picks one of the two terms the old `max()` compared, so for the same inputs it is never higher than the old result.\n- **Lock small, wait, extend gains nothing.** Locking once for the full amount and term gives the same power.\n- **Add-only never lowers power**, which matches the doc's \"constant unless changed\".\n- **The low item.** Line 73 only rejects `newEnd < end`, so a call with a term that leaves the end where it is still goes through and still recomputes power.\n  - **Example:** `lock(1000e18, 52)`, wait 30 weeks, then `lock(0, 22)`. Power drops from 1000e18 to 423.07e18 although nothing about the lock changed.\n  - **Who loses:** only the caller; nobody else is affected.\n  - **Docs:** MECHANICS.md allows it, so it is not a rule break.\n  - **Smallest fix:** when `newEnd == end`, either run the add-only branch or revert.\n- **How I checked:** a scratch test reproduced the drop, and a 256-run fuzz showed power never above the amount locked. I deleted the scratch file afterwards.\n\n**2. Reentrancy and call order in `lock()`: no issue.**\n- The checkpoint is written before `safeTransferFrom`. A failed transfer reverts everything, so the early write can't leave bad state.\n- YoloToken's `_update` only moves tax and has no callback into the caller, so nothing can re-enter `lock()`.\n- Relocking an ended lock bumps its epoch and needs a new term. The old power counts as 0, and `stakePower` returns 0 for a submission made under the old lock. The existing `StakePowerRelock` audit test covers this.\n\n**3. Stake base `t.pot` vs `t.base`: no issue.**\n- `submitTerms` reads `t.pot` from storage in the same call as `submit()`, after `advance`. Every top-up has already been added to the pot when it was made, so there is no stale read and no double count.\n- You can't inflate the pot and take the money back: `withdrawContribution` reverts while the task is Open.\n- Raising the pot right before `submit()` only makes the denominator bigger. That lowers the bonus share, so it can't buy a bigger share than the doer's power earns. The bonus paid (share × (R + add-on)) stays at about 4% of the power.\n\n**4. Add-on in the stake base: no issue.**\n- `t.addon` comes from the same memory struct in the same view-only call, so nothing can change it between the price read and `r += t.addon`.\n- A sweetener deposit just before `submit()` is real, locked $YOLO (`withdraw` needs Expired or Unwound). It raises `r`, which lowers the share; it can't push the share above what the doer's power earns.\n- Both terms are in $YOLO wei (`yoloValue` returns the amount unchanged for $YOLO).\n- **Info only, payout amounts (separate order):** top-ups and add-ons made during review are paid at the share fixed at submission. That is the documented rule, and a doer funding them loses money: the 10% fee is more than the 4% bonus.\n\n**5. `vault.poke` inside `predict()`: no issue.**\n- Poke only runs when `activeSub != 0`. In that state the old code always reverted with `SubmissionActive`, so the change can only let more entries through, never block new ones, and no gas is added on a path that used to succeed.\n- After poke, an entry passes only if the task is Open, before its deadline, with `activeSub == 0`. That is a legitimate state, which anyone could already reach by calling the public `poke` fir","treeHash":null,"usage":{"cachedInputTokens":925147,"inputTokens":32,"model":"claude-opus-5-5","outputTokens":16765,"runtime":"claude","turns":17,"wallClockMs":536805}}],"verification":[]}