{"assessments":[],"deployments":[{"attestationHash":"522bff373119fd624c7c100c97863e9e715e6bcbe23954c03852a5c853fd1159","chainId":1,"contracts":[{"address":"0xf4f56ec5b2d972a7695bf145717bcaadcb1f2d44","blockNumber":26289146,"name":"StepsBranch","role":"other","txHash":"0x11b7d60fabf1ddb73f6e39e19eb71035d77d5b0b0da3b61dbc5c3917c4060862"}],"id":"b5a61709-c39d-4923-8b35-35051d7c3170","manifestHash":"f300b376c78b2702e839df00e964b7a1f1ad838d101ca7211070aded7d2456ed","status":"live"}],"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":"82977c98-a44e-4dae-b16d-442d35ed3241","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"9c28128d6d7c001b219788e1e6e3cc73c93abc989d9fb279840b5d2bc80edb73","dependsOn":["audit_imported_code"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"ea5db8ea23a93d7611f9ab749d6255ad6596ee76a985c1261d43e63612dfc3cf","skillId":"adapt-contract-project","tools":[]},"key":"adapt_contract_project","kind":"code","role":"implement","skillHash":"ea5db8ea23a93d7611f9ab749d6255ad6596ee76a985c1261d43e63612dfc3cf","skillId":"adapt-contract-project","state":"accepted"},{"acceptedSubmissionHash":"11d55ee890f94e10c68518b569487c0296a0ebe9dd84286b6f222d0c434bab18","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"89d6154e2d1938885b7ac276eff205ca7d73db5adcf59f96ab611a0fd1543455","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"16af6a96291c40adaf01d93a8ca09b05c9a7972be83436e31247c3ac330c0138","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"b72293c3366fff3f0b93151bca72cc6cbd189dad31d1ccd1d34dd483f2165a36","skillId":"audit-imported-code","subagents":4,"tools":[]},"key":"audit_imported_code","kind":"code","role":"review","skillHash":"b72293c3366fff3f0b93151bca72cc6cbd189dad31d1ccd1d34dd483f2165a36","skillId":"audit-imported-code","state":"accepted"},{"acceptedSubmissionHash":"b2bb99a89bc89c79034278d55d06ca43cff63f68b6ec1a49fbfa59a74467da9a","dependsOn":["adapt_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"b89279670f5a00f5d26286edee00c97dd158b997031350a01cafe6e0329ad227","skillId":"audit-judge","subagents":4,"tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"b89279670f5a00f5d26286edee00c97dd158b997031350a01cafe6e0329ad227","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"8a5eba1d23ec5eaf4fab92037de124ed833102a21a1147b26943761e2329b800","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"2194a66c9c38d30902240e59696b43a40dfd1d1f2f1561d0774ec3f8728f1bb0","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"94cb9eee55eac8d7add7260abe1975cbe27c2833004954ae2062794f9950c850","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"89517671e98318c406e2f4c5199df97ea334d57ce6fba192c30ac27074ceaa21","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"606842ccca0e7c5d3017a54684625438eab9b351eb51dcf0cf88c43d1ea41464","skillId":"write-foundry-tests","subagents":4,"tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"606842ccca0e7c5d3017a54684625438eab9b351eb51dcf0cf88c43d1ea41464","skillId":"write-foundry-tests","state":"accepted"}],"objective":"Deploy exactly one new STEPS (Stepstoke) v2 StepsBranch ledger on Ethereum mainnet (chainId 1), from the exact public repository and commit in this request. This is a contracts-only launch through the official IMD service, not a source-only repair job. No website work, token, pool, hook, proxy, additional contract, paid child job, funded product round, branding or social posting. Test contracts and MockToken must never be deployment entries.\n\nUse launch.json: StepsBranch(intake, asset, price, initialOwner) with intake=0xa43e6f75ee006411f79ac1c84120606c2330de82, asset=0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7, price=500000000000000000, initialOwner=$owner. Request owner is 0x0bced6bf6f6db1d7ac5bc0b540482d170a027352. The factory must not become owner. Preserve existing price-only management and two-step ownership transfer; add no other authority. Constructors are static and nonpayable, with no post-deployment initialization. Verify the real same-chain Intake/token bindings; empty-chain constructor rehearsal is supported by nonzero address checks. Compiler: Solidity 0.8.26, optimizer 200, Cancun, bytecode_hash none, no via-IR or linked libraries.\n\nPreserve all supplied runtime behavior: exact committed JSON body and parent, maximum-payment protection, exact ERC20 transfer and temporary approval cleanup, official Callback tuple and requestWithFailure, one pending request per logical branch, independent fork heads, immutable successful checkpoints, authenticated callbacks for locally admitted request IDs, and same-chain-only operation. No IntakeDelivery trust or manual result binding. Forks create independent branch IDs, not fake successful steps. Reference forks remain available; snapshot forks persist at most 4096 bytes of exact hash-verified result JSON, not the referenced files.\n\nPreserve Draft/Pending/Succeeded/Refused/Failed/Expired numbering 0..5. Seven-day expiry requires an explicit transaction, releases only local waiting, and does not claim IMD failure, cancel upstream work, refund payment or append a checkpoint. A late authenticated terminal outcome is recorded once in lateReceiptOf and the separate late event. It never changes an expired proposal, branch head/revision/checkpoint count, or a newer pending request. received=false with default status=0 is not success. Reject foreign, unknown, duplicate/conflicting callbacks and zero result hashes. Keep the 2048-byte event URI bound, URI hash commitment and UriOmitted event.\n\nAudit the imported source before changes, perform only necessary official launch packaging, and independently review the final source. Reproduce the 57 supplied Solidity tests, 256-run sibling-order fuzzing, 16 SDK tests and independent cold callback cases with the actual 200000-gas stipend, including first checkpoint from nonzero proposal, long URIs and late success/refusal/failure while newer work is pending. Preserve 4 KiB persisted snapshot verification. Local validation is evidence to reproduce, not an external audit or mainnet acceptance. Do not weaken tests, fake evidence, or deploy unresolved material defects. If substantive changes are needed, report the blocker instead of silently changing the product.\n\nThe source includes local user/assistant repairs; preserve that provenance. Deliver the exact final public commit and full diff from the imported commit, build settings, launch manifest, ABIs/hashes, deployed runtime and immutable bindings, launch ID, Ethereum contract address, deployment transaction and receipt/block, actual outer sender and contract-creation caller, owner/intake/asset/price readbacks, and the official deployment handoff. Missing deployment evidence must remain missing; completed source work is not a live deployment. All instructions, output files and documentation must be English. Keep paid live product tests NOT_RUN and productionReady=false until later independent verification.","parentJobId":null,"planHash":"78dbf86d02dbee4cca3b3e79a25205542526ee291ed2a9edbd4335db8ab89ef4","previousHash":"75fc7f1092604e6a40664fc53e5c813cc209514c22af214263272caddd485dd0","projectId":"82977c98-a44e-4dae-b16d-442d35ed3241","publication":{"commit":"3a49506fe018281ed11f6873a3189e12b66545d1","deliveredAt":"2026-10-28T05:30:06.816Z","repoUrl":"https://github.com/identity-md-launches/launch-2628-stepsbranch"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[],"site":null,"snapshotHash":"121c032d3987afb11891ccfbf3e3c221a4e29b083445b4c74ef3d9c0e0a2fa3b","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"fc87c05048aca47d","findings":[{"citation":"resolved","description":"Economic Security / Flow Gap (execution x periphery x first principles). fundWithPayment pays the ledger's own owner-set `price` and the constant ACTION = \"job.open@job-1\" (line 77). It never reads Intake.priceOf(ACTION, asset). The intake-client reference states the Intake pulls exactly `amount`, forwards it to the treasury, refunds nothing, reverts below its price, and that action version and price are owner-settable on the Intake side and 'must not need a redeploy'. Three concrete consequences: (a) Intake price below ledger price: every funder overpays the difference to the IMD treasury, maxPayment cannot protect because it caps the ledger price, not the live price; (b) Intake price above ledger price: every fundWithPayment reverts (ShortPayment at the Intake) until the owner raises price, no loss; (c) the Intake stops selling job.open@job-1 (priceOf == 0, e.g. a job-2 version): ACTION is a compile-time constant and intake/asset are immutable, so the ledger can never append another checkpoint; existing lineage is frozen for good. The brief limits management to price-only, so this is a design dependency the requester must accept explicitly or widen scope for. Minimal fix that keeps price-only management: read `uint256 live = intake.priceOf(ACTION, asset)` in fundWithPayment, revert if live == 0, pay `live` and keep `price` purely as the owner's ceiling (require live <= price and live <= maxPayment). Making ACTION owner-settable would need a scope decision.","line":181,"path":"src/StepsBranch.sol","reproduction":"Scratch test test/scratch/Economics.t.sol (PricedIntake models the reference's rules). (a) testOverpaysWhenIntakePriceDrops: ledger price 0.5e18, Intake priceOf = 0.3e18; funder calls propose(0,0,BODY) then fundWithPayment(id,BODY,0.5e18). Expected: pay at most the live 0.3e18. Actual: funder balance 10e18 -> 9.5e18, treasury receives 0.5e18, 0.2e18 overpaid and unrefundable. (b)/(c) testFundingRevertsWhenIntakePriceAboveLedgerOrActionRetired: Intake price 0.6e18 -> fundWithPayment(id,BODY,1e18) reverts ShortPayment although the funder offered 1e18; Intake price 0 -> reverts ActionNotSold forever. Live readback of priceOf was not reproducible here (public RPCs unreachable); evidence/LOCAL-REPRODUCTION.json claims 0.5e18 on 2026-10-28.","severity":"low","snippet":"        uint256 amount = price;\n        if (amount > maxPayment) revert PriceAboveLimit();","title":"Payment amount and action are not bound to the live Intake price: a moved Intake price overpays the treasury or bricks funding, and a retired job-1 action bricks the ledger permanently"},{"citation":"resolved","description":"Invariant (break commutativity) / Flow Gap. Once fundedAt + 7 days has passed, any address may call expirePending(id). The Intake's complete transaction and a third party's expirePending commute differently: expire-then-deliver archives the authenticated success in lateReceiptOf with no checkpoint and no head move, and there is no path to promote a late receipt into a checkpoint later. A griefer who watches the mempool for the writer's complete(requestId) at or after day 7 front-runs it with expirePending(id) for gas only; the funder's 0.5 IMD bought no step on the ledger and the griefer can immediately occupy the freed slot with its own paid proposal. The brief requires expiry to stay an explicit transaction and late outcomes to stay archived, so the mechanism itself is intended; the gap is that the funder has no priority at the boundary. Preconditions are narrow (a job still pending after 7 days, which the reference says takes minutes to hours) and loss is bounded to one request, hence low. A fix that preserves the design: let only p.funder (or p.proposer) expire during a grace window after day 7 and anyone afterwards, or let the funder re-anchor a late success as a new proposal; both need a scope decision.","line":253,"path":"src/StepsBranch.sol","reproduction":"test/scratch/Economics.t.sol testExpiryFrontRunsDeliveredSuccess: funder 0xF00D proposes on branch 0 and funds id 0 (0.5e18 spent). vm.warp(+7 days). In the same block, griefer 0xBAD calls expirePending(0) before intake.deliver(rid, keccak256('result'), 'ipfs://x/result.json'). Expected (funder's view): checkpoint 0 appended, head != 0. Actual: delivery returns true, lateReceiptOf(0) = {received:true,status:0,resultHash:keccak256('result')}, count() == 0, branches(0).head == 0, treasury holds 0.5e18, and the griefer then funds its own proposal on branch 0 (pendingProposalPlusOne = 2).","severity":"low","snippet":"    function expirePending(uint256 id) external nonReentrant {\n        if (id >= proposals.length) revert UnknownProposal();\n        Proposal storage p = proposals[id];\n        if (p.status != Status.Pending) revert NotPending();\n        if (block.timestamp < uint256(p.fundedAt) + PENDING_TIMEOUT) revert TooEarly();","title":"Permissionless expirePending can be ordered in front of the Intake's delivery at the 7-day boundary, turning a paid success into a late receipt that can never become a checkpoint"},{"citation":"resolved","description":"Economic Security (cheapest griefing vector). Branches have no owner; propose and fundWithPayment are permissionless. Cost to the griefer: 0.5 IMD per occupation (and they receive a real job for it), so it is cheap but not free. Victims lose no funds (BranchBusy and StaleAnchor revert before any token moves) and can forkFromOrigin. Already reproduced by test/AuditFindings.t.sol testAuditFindingSharedBranchCanBeTakenWithoutVictimLoss and recorded in ADAPTATION.md as not fixed because the brief forbids adding authority. Reported here only so the judge sees the economic angle is covered; not a new defect.","line":179,"path":"src/StepsBranch.sol","reproduction":"VICTIM proposes vid on branch b; ATTACKER proposes aid and funds it (0.5e18); VICTIM fundWithPayment(vid) reverts BranchBusy; after the attacker's success VICTIM fundWithPayment(vid) reverts StaleAnchor forever; VICTIM balance unchanged. See test/AuditFindings.t.sol lines 54-88.","severity":"info","snippet":"        if (b.pendingProposalPlusOne != 0) revert BranchBusy();","title":"Known, accepted: any payer can occupy a shared branch's single pending slot and append its own step, invalidating other proposers' Drafts (duplicate of import finding 2ba0657b)"},{"citation":"resolved","description":"Invariant (conservation). The ledger is designed to hold no tokens: balance before the pull must equal balance after the request. Because both checks are relative to beforeBalance, a stray transfer of IMD to the ledger address neither blocks funding nor is ever recoverable, since there is no sweep and the brief forbids adding authority. No victim other than the sender; recorded as a trust assumption, not a defect.","line":189,"path":"src/StepsBranch.sol","reproduction":"test/scratch/Economics.t.sol testDirectTransferIsStrandedButHarmless: token.mint(ledger, 1e18); funder proposes and funds normally; ledger balance remains 1e18 after funding and no function can move it.","severity":"info","snippet":"        uint256 beforeBalance = IERC20(asset).balanceOf(address(this));","title":"IMD sent directly to the ledger is stranded forever; it does not disturb the funding delta checks"}],"hash":"11d55ee890f94e10c68518b569487c0296a0ebe9dd84286b6f222d0c434bab18","nodeId":"e557c60a-2b8f-43dc-ab23-4b99f06accb1","outcome":"completed","summary":"The review is complete. The findings file is written, all four snippets match the tree, and no repository file was changed (only `.imd-findings.json` and my scratch tests under `test/scratch/`, which are not kept).\n\n**Outcome: no critical, high or medium defect in the economics area.** The supplied suite passes 59 of 59, and a stateful fuzz I wrote (64 runs, 2,560 calls over propose, fund, deliver, fail, expire and fork) kept three invariants: the ledger never holds IMD or a residual approval, every pending slot points at exactly one consistent Pending proposal, and every first callback under the 200k stipend is delivered.\n\n**Findings recorded in `.imd-findings.json`:**\n\n1. **Low. Payment is not bound to the live Intake price, and the action is a constant.** The ledger pays its own owner-set price for the constant action `job.open@job-1` and never reads the Intake's live price. If the Intake lowers its price, every funder overpays the difference to the IMD treasury and `maxPayment` cannot stop it. If the Intake raises it or retires the job-1 version, funding reverts forever and the lineage freezes. Reproduced with a priced Intake model in `test/scratch/Economics.t.sol`. The fix needs a scope decision since the brief restricts management to price only.\n\n2. **Low. Permissionless expiry can be ordered ahead of the Intake's delivery at the seven-day boundary.** A third party front-runs the writer's `complete` with `expirePending`, so the paid success becomes a late receipt with no path to ever become a checkpoint, and the griefer takes the freed slot. Loss is bounded to one request and the precondition is narrow, so low.\n\n3. **Info.** The shared-branch pending-slot griefing already recorded as 2ba0657b, restated with its economics so the judge sees it is covered.\n\n4. **Info.** IMD sent straight to the ledger is stranded, by design, and does not break the funding delta checks.\n\n**Coverage:** all 14 listed entry points have rows, plus three invariant rows and a manifest row. Two items are marked unreached: the 16 SDK tests, because viem is not installed and the network is blocked here, and the live mainnet readbacks of price, callback gas and token symbol, because public RPCs were unreachable from this machine. The evidence file's claimed values are therefore the requester's word, not something I confirmed.","treeHash":null,"usage":{"cachedInputTokens":1218836,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":31892,"runtime":"claude","turns":31,"wallClockMs":411288}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"a93cc1aa32976b68","findings":[{"citation":"resolved","description":"propose() and fundWithPayment() are permissionless, and a Branch records no creator or owner. Each branch allows one pending request, and a success moves its head, so anyone who pays 0.5 IMD can (a) hold any branch busy until the callback arrives, or for up to 7 days if it never does, and (b) append their own job result as the branch's next checkpoint. After that, every Draft other users made on that branch reverts StaleAnchor forever. A victim can fork to a new branch, but the attacker can squat that branch the same way. No victim funds are lost: the victim's funding reverts before any token moves, and the attacker pays each time. This is how the supplied design works (a shared append-only ledger with price-only owner authority). The brief forbids adding authority, so the adapter must NOT fix this silently by adding branch ownership. It is reported so the requester explicitly accepts it as a known griefing and liveness property, or changes the product. It does not block deployment of the supplied design.","line":179,"path":"src/StepsBranch.sol","reproduction":"Mock intake (DocumentedIntake from test/StepsBranch.t.sol), price 0.5 IMD. 1) victim: b = forkFromOrigin(); victim: vid = propose(b, 0x0, vb). 2) attacker: aid = propose(b, 0x0, ab); attacker: rid = fundWithPayment(aid, ab, 0.5e18). 3) victim: fundWithPayment(vid, vb, 0.5e18) reverts BranchBusy. 4) intake.deliver(rid, keccak256(\"r\"), \"ipfs://x/result.json\") succeeds, and the head of b becomes the attacker's checkpoint digest. 5) victim: fundWithPayment(vid, vb, 0.5e18) reverts StaleAnchor permanently. Expected (if branches were meant to be the creator's): the victim's step can be funded. Actual: the attacker controls the branch's next step. Reproduced locally in a scratch test (PASS on the current code).","severity":"low","snippet":"        if (b.pendingProposalPlusOne != 0) revert BranchBusy();","title":"Any address can take a logical branch's single pending slot and advance its head, making other proposers' Drafts permanently stale"},{"citation":"resolved","description":"The live mainnet Intake 0xa43e…de82 exposes callbackGas() = 200000 as a storage value, not a bytecode constant (PUSH3 0x030d40 is absent from its runtime). The heaviest success path (the ledger's first checkpoint, from a nonzero proposal id, with a 2048-byte URI, on a cold transaction) used 192,259 gas in the supplied cold Anvil run (including mock call overhead), which I reproduced exactly. On a mainnet fork, a cold onResult with a 2048-byte URI for the first checkpoint used 220,558 total, minus 55,136 intrinsic, which is about 165k of execution. That fits under 200000 today. A future reduction of callbackGas below about 195k would make first or heavy checkpoints undeliverable: the callback fails, the result is not recorded, and the branch waits 7 days for expirePending. ACTION, intake and asset cannot be changed, so retiring job.open@job-1 or the v2 Intake would make funding revert. That revert is atomic, so no funds are at risk. This matches the brief's 'price-only management, add no other authority' requirement, so it is not a defect to fix. It is a dependency to monitor.","line":77,"path":"src/StepsBranch.sol","reproduction":"cast call 0xa43e6f75ee006411f79ac1c84120606c2330de82 'callbackGas()(uint256)' on chainId 1 returns 200000. sdk/local-integration.mjs on an isolated Anvil: the observation 'first checkpoint, nonzero proposal, 2048-byte URI, cold transaction' has callGasIncludingMockOverhead=192259. Mainnet-fork deploy of StepsBranch(intake, IMD, 5e17, 0x0bced6bf…7352), propose plus fundWithPayment with real IMD, then an impersonated Intake calls onResult(requestId, 0x11..11, 'ipfs://'+2041*'a'): status 1, gasUsed 220558. With a hypothetical callbackGas of 190000, the 192259-gas case would be recorded as not delivered.","severity":"info","snippet":"    bytes32 public constant ACTION = \"job.open@job-1\";","title":"First-checkpoint callback depends on the Intake's owner-settable callbackGas staying at 200000; the action id and Intake are fixed at deployment"}],"hash":"16af6a96291c40adaf01d93a8ca09b05c9a7972be83436e31247c3ac330c0138","nodeId":"50b98ee3-e6c1-43f6-ab69-d4e9da4a308e","outcome":"completed","summary":"I found no material security or liveness defect in the imported source, so nothing here blocks deployment. Two non-blocking findings are in `.imd-findings.json`. I changed no repository files: everything I ran used copies in `/tmp`, and I removed the `out/` and `cache/` folders my test run created.\n\n**What I read:** `src/StepsBranch.sol` (every state-changing function), `src/MockToken.sol` (test-only), both test files, the SDK, `launch.json`, `README.md`, the local validation evidence, the committed ABI and build files, and the protected checks. On Ethereum mainnet I also read the live Intake (`0xa43e…de82`) and IMD token (`0xd34a…63b7`), but only their bytecode and public read functions. I could not reach the Intake's source code.\n\n**Reproduced:**\n- **Tests:** all 57 Solidity tests pass, including the 256-run sibling-order fuzz test. All 16 SDK tests pass.\n- **Cold callbacks (isolated local chain):** all 17 cases ran in separate transactions. The heaviest is the first checkpoint from a nonzero proposal with a 2048-byte URI, at 192,259 gas (including the test intake's overhead), the same figure as the evidence file. Late success, refusal and failure while newer work is pending did not change the branch.\n- **Fresh build:** the source fingerprint matches the evidence file, and the build, deployed code and ABI exactly match the committed files. The deployed code is 11,460 bytes, with no DELEGATECALL, CALLCODE or SELFDESTRUCT.\n- **Mainnet bindings:** the Intake has `requestWithFailure` with the official callback tuple. Its price for `job.open@job-1` in IMD is 0.5 IMD, and its `callbackGas()` is 200000. Both callback selectors match the official shapes.\n- **Mainnet-fork rehearsal against the real Intake and IMD:**\n  - Deployment with owner `0x0bced6bf…7352` reads back the correct owner, intake, asset and price.\n  - Funding pulled exactly 0.5 IMD and left the ledger with no balance and no remaining approval.\n  - A first-checkpoint success callback with a 2048-byte URI, sent as if from the Intake, used about 165,000 gas of execution.\n- **Logic checked against the brief:**\n  - The maximum-payment limit holds.\n  - Each branch allows only one pending request.\n  - Expiry changes only local waiting.\n  - A late outcome is recorded once in `lateReceiptOf` and never moves a branch.\n  - Foreign, unknown, duplicate and zero-hash callbacks are rejected.\n  - The 2048-byte URI cap and the 4 KiB snapshot hash check work.\n  - Ownership uses two steps, and the owner can only change the price.\n\n**Findings:**\n1. **Low (design property, needs your decision):** anyone can propose and fund on any branch. For 0.5 IMD, an attacker can hold a branch busy and add their own result as its next step, which makes other people's drafts on that branch permanently stale. Forked branches can be taken over the same way. Victims lose no funds, because their funding transaction reverts before any token moves. I reproduced this in a scratch test. Fixing it would mean adding branch ownership, which the brief forbids, so the adapter should not change it without your approval.\n2. **Info:** the live Intake's 200000 callback gas is a setting IMD can change, not a fixed value. The heaviest callback fits today with roughly 8,000–35,000 gas to spare. If IMD lowered the setting below about 195,000, first checkpoints would fail and the branch would wait 7 days for expiry. The action id and Intake address can't change after deployment, so if IMD retires them, funding stops working. No funds are at risk, because the transaction reverts.\n\nThese local checks are not an external audit or proof that it works on mainnet. Deployment evidence (launch ID, contract address, transaction) doesn't exist yet, and paid live tests remain NOT_RUN.","treeHash":null,"usage":{"cachedInputTokens":1774897,"inputTokens":56,"model":"claude-opus-5-5","outputTokens":26922,"runtime":"claude","turns":31,"wallClockMs":406671}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"ee8bab8367ea6e91","findings":[{"citation":"resolved","description":"Trust-gap seam access x economics. The amount sent to the Intake is the ledger's stored `price` (owner-managed, src/StepsBranch.sol:181 and :194), never the Intake's live `priceOf(ACTION, asset)`. The verified mainnet Intake at 0xa43e6f75ee006411f79ac1c84120606c2330de82 (`_request`: `if (amount < price) revert PriceNotMet; ... safeTransferFrom(msg.sender, payTo, amount)`) accepts any amount at or above its price and forwards it whole to its treasury; an eth_call against mainnet with a token code override confirmed amount 0.4 IMD reverts PriceNotMet while 0.6 IMD is accepted. Two states therefore make funders overpay irrecoverably: (a) the Intake owner lowers the live price below the ledger's `price` (e.g. 0.25 IMD while the ledger still stores 0.5 IMD) and every funder with maxPayment >= 0.5 pays the full 0.5 until the ledger owner reacts; (b) the ledger owner sets `price` above the live price. `maxPayment` is only a cap on the stored price, not a check against the price actually charged. The intake-client reference's worked example reads `priceOf` on each ask precisely so that a moved price needs no change in the client. The upward case is harmless (the whole transaction reverts, nothing is spent). This is the supplied price-only management design, so it is reported as a bounded, owner-visible trust gap rather than a permission bypass; the surplus goes to IMD's payTo, not to the ledger owner.","line":181,"path":"src/StepsBranch.sol","reproduction":"Deploy StepsBranch(intake, token, 0.5 ether, owner) against an Intake whose priceOf(ACTION, token) is 0.25 ether and which pulls `amount` whole (the mainnet rule). Funder: propose(0, 0, body); fundWithPayment(id, body, 0.5 ether). Expected: the funder pays the action's price (0.25) or the call refuses the stale price. Actual: funder is debited 0.5 ether, the Intake treasury receives 0.5 ether, 0.25 ether above the live price; nothing refunds it. Mirror case with owner setPrice(1 ether) while the live price is 0.5: a funder with maxPayment 1 ether pays 1 ether. Scratch reproduction: test/scratch/PriceGap.t.sol (testStoredPriceIsForwardedWholeWhenLivePriceIsLower, testOwnerRaisedPriceIsForwardedWhole; testLivePriceAboveStoredPriceRevertsWithoutLoss shows the upward case loses nothing). Minimal fix that keeps price-only management: in fundWithPayment read `uint256 live = intake.priceOf(ACTION, asset);` and revert (or charge min(price, live)) when `live != price`, keeping the maxPayment cap; this adds no authority and no constructor-time external call.","severity":"low","snippet":"        uint256 amount = price;\n        if (amount > maxPayment) revert PriceAboveLimit();","title":"fundWithPayment forwards the owner-set price to the Intake without reading the live priceOf, so any surplus over the live price is paid whole to the IMD treasury with no refund path"},{"citation":"resolved","description":"Access x asymmetry. expirePending has no caller restriction and does not require the funder's consent; elapsed time alone changes nothing, so whether a result that lands at or after fundedAt + 7 days becomes a checkpoint (onResult Pending path, src/StepsBranch.sol:218-229) or only a late receipt (Expired path, :208-217) is decided by whoever sends expirePending first. An unrelated account can watch the writer's `complete` transaction near the boundary and front-run it with expirePending for gas only; the funder's 0.5 IMD stays spent (the Intake never refunds) and the result can never be bound to the branch afterwards because no manual binding exists. The brief explicitly requires this behaviour ('Seven-day expiry requires an explicit transaction ... anyone may call'), so this is not a defect to fix, only the concrete permission asymmetry the requester is accepting. It is already exercised by the supplied test testLateSuccessArchivedWithoutCheckpointOrHeadChange (expiry by 0xBEEF, then success archived with count()==0).","line":253,"path":"src/StepsBranch.sol","reproduction":"Funder F: propose(0,0,body); fundWithPayment(id, body, 0.5 ether) at t0. At t0 + 7 days, before the Intake writer's complete() is mined, any address A calls expirePending(id). Then the writer completes with status 0. Expected (from F's point of view): the paid result is appended. Actual: proposals[id].status == Expired, lateReceiptOf[id].received == true, count() == 0, head unchanged; F's 0.5 IMD was spent. See test/StepsBranch.t.sol testLateSuccessArchivedWithoutCheckpointOrHeadChange.","severity":"info","snippet":"    function expirePending(uint256 id) external nonReentrant {\n        if (id >= proposals.length) revert UnknownProposal();\n        Proposal storage p = proposals[id];\n        if (p.status != Status.Pending) revert NotPending();\n        if (block.timestamp < uint256(p.fundedAt) + PENDING_TIMEOUT) revert TooEarly();","title":"Any third party may expire another funder's pending request at exactly seven days, turning an on-time-but-late IMD success into an archived late receipt that never becomes a checkpoint (requested desi"},{"citation":"resolved","description":"Access control at deployment: whoever holds `owner` after the factory transaction holds setPrice and the two-step transfer. The manifest uses `$owner`, which the launch service resolves to the request body's `owner`, else the wallet that paid. The brief itself names the owner (0x0bced6bf6f6db1d7ac5bc0b540482d170a027352) and the task reference says an owner address the brief states is written as that static address. With `$owner` the deployed owner is correct only while the request's owner field names that address; if the request had no owner field, or the service resolved $owner to the paying wallet, the deployed owner would silently differ from the brief. The constructor itself cannot assign the factory (it takes initialOwner_ and rejects zero), so this is a manifest-resolution dependency, not a contract defect.","line":10,"path":"launch.json","reproduction":"Resolve launch.json with a request whose owner field is absent or differs from 0x0bced6bf6f6db1d7ac5bc0b540482d170a027352: StepsBranch.owner() returns that other wallet and only it can call setPrice/transferOwner. Expected: owner() == 0x0bced6bf6f6db1d7ac5bc0b540482d170a027352 for every resolution. Writing the static address in constructorArgs[3] removes the dependency; if the service requires `$owner`, the deployment readback of owner() must be compared to the brief's address.","severity":"info","snippet":"        \"$owner\"","title":"launch.json writes the initial owner as $owner although the brief states the owner address; ownership depends on $owner resolving to 0x0bced6bf6f6db1d7ac5bc0b540482d170a027352"}],"hash":"2194a66c9c38d30902240e59696b43a40dfd1d1f2f1561d0774ec3f8728f1bb0","nodeId":"25895f43-e28e-430b-b27d-e8d06a34fc34","outcome":"completed","summary":"The review is complete. The findings file is written, no tracked file changed, and the harness excludes `.imd-findings.json` and `test/scratch/` from git by design.\n\n**Outcome: no permission bypass found.** The permission model of `StepsBranch` holds against the Access Control, Trust Gap and Asymmetry guides. I wrote one low finding and two info notes to `.imd-findings.json`, with an 18-row coverage record answering all 14 listed entry points plus four invariants.\n\n**Findings**\n\n- **Low, `fundWithPayment` (src/StepsBranch.sol:181).** The ledger forwards its owner-managed stored price to the Intake and never reads the live `priceOf`. I pulled the verified mainnet Intake source and confirmed by simulation that it reverts below its price but accepts and forwards any amount above it, whole, to the treasury. So if IMD lowers the live price, or the owner raises the stored one, every funder overpays irrecoverably while `maxPayment` only caps the stored figure. Reproduced in `test/scratch/PriceGap.t.sol`. The upward case loses nothing. Suggested fix keeps price-only management: compare against `priceOf` at funding time.\n- **Info, `expirePending`.** Anyone may expire another funder's request at the seven-day boundary, converting an on-time-but-late success into an archived late receipt with the payment spent. This is the design the brief requires, so it is recorded as the trust assumption being accepted, not a defect.\n- **Info, `launch.json`.** The owner is written as `$owner` although the brief states the owner address. Correct ownership depends on the service resolving `$owner` to that address. The constructor itself cannot hand control to the factory.\n\n**What I verified**\n\n- Every state-changing entry point, its guard, and its writes. The only privileged powers are `setPrice` and the two-step transfer. Callbacks require the Intake sender, a locally admitted request id, and Pending-with-consistent-branch or Expired-without-receipt. Late paths never touch heads, checkpoints or newer pending requests.\n- The confused-deputy route the real Intake leaves open: any payer can name the ledger as a callback target with any selector. Request ids are `keccak(chainid, intake, nonce)`, so they cannot collide, and the first argument word is always that id. A scratch test drives every ledger selector that way and shows nothing privileged or indexed is reachable, only a harmless extra origin branch.\n- Mainnet bindings read-only: the Intake and IMD token have code, the live price is 0.5 IMD, callback gas is 200000, and IMD is a plain LayerZero OFT with no pause, blacklist or fee.\n- Baseline reproduction: 59 of 59 Foundry tests including the 256-run fuzz, 16 of 16 SDK tests with viem installed outside the repository, and the committed ABI and bytecode match a fresh build.\n\n**Not reached:** nothing in the assigned area. Gas-stipend margin, token periphery and economics beyond the price gap belong to the other specialists and were only checked as far as the mainnet source made it cheap.","treeHash":null,"usage":{"cachedInputTokens":2376058,"inputTokens":578,"model":"claude-fable-5-1","outputTokens":46985,"runtime":"claude","turns":46,"wallClockMs":668851}},{"artifacts":[],"attempt":1,"bundleHash":"0afa567d4405812d1ba4309228f50bfbbccdfb2a8fb07dc96f536e424ac2e65b","device":"a93cc1aa32976b68","findings":[],"hash":"5650bae318895857a165cdddfb93e32289e0329e7062211b43a792aebe6003c2","nodeId":"d1c3c0b3-bcf7-4c24-818a-17b017ad9c72","outcome":"completed","summary":"The imported StepsBranch already meets the launch rules, so I changed no contract code, configuration or manifest. `forge build` passes, and `forge test` with the verifier's fuzz seed passes all 62 tests. The work is committed as `981a42f` on top of the imported `3962d42`. Nothing has been deployed.\n\n**Why no source change was needed:**\n- The constructor is nonpayable and takes four plain arguments. It only checks they are nonzero and calls no other contract, so it runs on an empty chain.\n- The owner comes from the `initialOwner` argument, never from the factory or `tx.origin`.\n- There is no initialization after deployment, no proxy, and none of the forbidden opcodes.\n- Tokens are pulled only from `msg.sender`, and the runtime is 11,460 bytes.\n- The existing `launch.json` is correct, and I left it as it was.\n\n**Full diff from the imported commit** (4 files, additions only):\n- `test/LaunchPackaging.t.sol` — 5 new tests:\n  - a factory-style CREATE2 deployment on an empty chain 1 with the manifest arguments, which checks the owner, intake, asset and price readbacks and that the factory isn't the owner;\n  - a size and forbidden-opcode scan;\n  - zero-argument rejection;\n  - one test reproducing each audit finding.\n- `ADAPTATION.md` — the changes, why the source needed none, the audit findings, my review and what was run.\n- `evidence/LAUNCH-PACKAGING.json` — build settings, code hashes, immutable bindings, mainnet readbacks and test results. Every deployment field is null or `NOT_DEPLOYED`, paid live product tests are `NOT_RUN`, and `productionReady` is false.\n- `README.md` — a short pointer section.\n\n**What I reproduced:**\n- **Solidity tests:** the 57 supplied tests pass, including the 256-run sibling-order fuzz test.\n- **SDK tests:** 16/16 pass, with viem installed outside the tree.\n- **Cold callbacks:** 17 separate local callback transactions all delivered under the 200,000-gas limit. The heaviest used 192,259 gas and the 4 KiB snapshot copy used 3,021,993, exactly matching the supplied evidence.\n- **Mainnet bindings:** read-only checks at block 26288856 found code at both addresses, a 200000 callback gas limit, a 0.5 IMD price for `job.open@job-1`, and an asset named IMD with 18 decimals.\n- **Mainnet-fork run:** funding through the real Intake debited exactly 0.5 IMD and left no balance or approval behind. The heaviest first-checkpoint callback delivered under a 200,000 limit using 191,358 gas, about 8.6k to spare.\n\n**Audit findings — both reproduce, and I fixed neither:**\n- **Branch squatting (low):** anyone who pays 0.5 IMD can take a branch's single pending slot and append the next step, making other proposers' drafts unusable. Victims lose no funds. Fixing it means adding branch ownership, which the brief forbids. **You need to decide:** accept this as a known griefing and liveness property, or order a product change.\n- **Callback gas (info):** heavy callbacks depend on the Intake's owner-settable callback gas limit staying at 200000. If it drops below about 192k, a heavy first checkpoint can't be delivered and that branch waits seven days to be released. No funds are at risk; it's something to monitor.\n\n**Not run:** Slither isn't installed on this machine, so I checked the forbidden patterns by reading the source and with the opcode-scan test. The launch ID, contract address, deployment transaction and block, sender and creation caller, live readbacks and official handoff don't exist yet. They come from IMD's deployment af\n\nNot delivered: artifacts/LaunchPackaging.t.sol/BranchUser.json, artifacts/LaunchPackaging.t.sol/Create2LaunchFactory.json, artifacts/LaunchPackaging.t.sol/LaunchPackagingTest.json, artifacts/LaunchPackaging.t.sol/LaunchVm.json, artifacts/LaunchPackaging.t.sol/RecordingIntake.json, artifacts/LaunchPackaging.t.sol/StipendRelay.json, artifacts/LaunchPackaging.t.sol/VmContractHelper15.json, artifacts/MockToken.sol/MockToken.json and 18 more. Under artifacts/, only this step's named outputs leave the machine.","treeHash":"22dae72785a5310f09e51d6c7d5df31f9cebf144","usage":{"cachedInputTokens":1818988,"inputTokens":57,"model":"claude-opus-5-5","outputTokens":27939,"runtime":"claude","turns":33,"wallClockMs":430989}},{"artifacts":[],"attempt":1,"bundleHash":"92d272a00de2a39f5038f64f2351ae23a09e2140801d9f100c5bfa7e1aaf5f76","device":"faf271d05ee8ea0f","findings":[{"description":"onResult hashes the full calldata URI (keccak256(bytes(uri))) before deciding to omit it from the event. Memory expansion and hashing grow with the URI length, so a success callback carrying a very large URI runs out of the Intake's fixed 200000-gas stipend, is recorded by the Intake as not delivered, and the branch waits for expirePending. Real Intake URIs (ipfs://<CID>/result.json or https://api.imd.fun/intake-results/<id>) are about 100 bytes, so this is not reachable in normal operation and needs no change; it is noted as a bound on the 'long URI' guarantee. No funds are at risk.","line":207,"path":"src/StepsBranch.sol","reproduction":"DocumentedIntake (test/StepsBranch.t.sol) delivering onResult with call{gas:200000} after one funded proposal on a fresh ledger: uri length 32768, 49152, 65536 and 81920 bytes -> delivered=true; 98304, 131072, 196608 and 262144 bytes -> delivered=false (out of gas). test/StepsBranchEdges.t.sol::testVeryLongUriCommittedUnderStipend pins the 32 KiB case as delivered.","severity":"info","title":"Success callback with a URI longer than roughly 80-96 KiB exceeds the 200000-gas stipend"}],"hash":"89517671e98318c406e2f4c5199df97ea334d57ce6fba192c30ac27074ceaa21","nodeId":"8fc0353d-9f3c-4f83-92a6-874841301597","outcome":"completed","summary":"I added two test files. `forge build` succeeds and `forge test` passes 82 of 82 with the verifier's fuzz seed: the 59 existing tests, 22 new unit tests and one invariant suite. I found no material defect in `src/StepsBranch.sol`. I changed nothing outside `test/` except `.imd-findings.json`.\n\n**`test/StepsBranchInvariant.t.sol`** drives the ledger with random call sequences (256 runs × 100 calls, no reverts). The calls are: propose (current or stale anchor), fund, success and failure callbacks through a mock Intake that calls back once with the real 200,000-gas limit, a time jump of up to 8 days, expiry, all three fork types, a price change by the owner or a stranger, callbacks from a stranger, and Intake callbacks with an unknown id, a zero result hash or failure status 0. After every sequence these must hold:\n- **Payment:** the ledger holds no tokens and leaves no approval behind. The Intake received exactly what was paid, total supply is unchanged, and no funder pays more than their stated maximum or is charged when funding fails.\n- **Callbacks:** every real callback is delivered within the gas limit, and no fake or invalid one is ever accepted.\n- **Consistency:** each branch's single pending slot points to its one Pending proposal. Checkpoints match Succeeded proposals one to one, every branch head matches its checkpoint, and late receipts exist only on Expired proposals.\n- **Nothing finished changes:** a finished proposal, a checkpoint or a late receipt never changes once it exists. A stale anchor is never accepted.\n- **Authority:** the owner, the Intake and token addresses never change, and the price is never zero.\n\nTo show the suite isn't vacuous, I temporarily made it fail on first reaching a checkpoint or a late delivery. Both were reached with the verifier seed.\n\n**`test/StepsBranchEdges.t.sol`** pins edge and failure cases not already tested:\n- **Setup:** Status numbers 0..5, the constants and both callback selectors; the constructor rejects each zero argument and never makes the deployer the owner.\n- **Owner powers:** a stranger can't set the price, zero is refused, and every failure path of the two-step ownership transfer is covered.\n- **Payment protection:** a fuzzed test of the maximum-payment limit (512 runs). Funding is rolled back when the Intake pulls less or nothing, when the token takes a fee, or when the Intake tries to call back during the request itself. A stranger can't spend the proposer's approval.\n- **Expiry:** the boundary is exact at 7 days minus 1 second; expiry can't run twice, refunds nothing and creates no checkpoint. A missing receipt (`received=false`, status 0) is not success, and on-time outcomes never write a late receipt.\n- **Late outcomes:** a late success with a 2048-byte URI fits the gas limit while newer work waits, and the newer request then becomes the first checkpoint from a nonzero proposal. Late refusal and failure leave the newer pending request alone, and invalid late outcomes are rejected.\n- **Forks and snapshots:** a 32 KiB URI is still delivered. All fork and snapshot failure paths are covered, including a 4097-byte snapshot that matches the hash but is still rejected while a reference fork of it works. Forks advance independently with distinct digests.\n\n**Findings** (`.imd-findings.json`, one info item): a success callback with a URI of about 96 KiB or more runs out of the 200,000 gas. Real Intake URIs are about 100 bytes, so this isn't reachable in normal use and needs no fix. The earlier contributor's two notes (`ADAPTATION.md`) still stand and I didn't repeat them: anyone can take a shared branch's single pending slot (low), and callbacks depend on the Intake keeping its callback gas at about 195,000 or more.\n\nThe files are left uncommitted. This was test work only: nothing was deployed, deployment evidence is still missing, paid live tests stay NOT_RUN and `productionReady` stays false.","treeHash":"76a2cf3ea0c8eb625e1664e7cfc16bb3d6c5fbd0","usage":{"cachedInputTokens":1956123,"inputTokens":56,"model":"claude-opus-5-5","outputTokens":40602,"runtime":"claude","turns":30,"wallClockMs":433181}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"d5e40d018a0986f3","findings":[{"citation":"resolved","description":"Any address may call expirePending once block.timestamp >= fundedAt + 7 days. The brief and README ask for exactly this (anyone may stop waiting after seven days; a late outcome is archived in lateReceiptOf and never becomes a checkpoint). The consequence not spelled out in the README is that a third party, not the funder, decides whether a slow but successful job enters the lineage: if the writer's complete() for a request lands after a stranger's expirePending, the funder's 0.5 IMD was spent, the result is archived in lateReceiptOf, and no later call can turn it into a checkpoint or fork from it. The attacker gains nothing and pays only gas, jobs normally finish in minutes to hours, and the behavior matches the requested specification, so this is recorded as a trust assumption for the requester to accept, not as a defect requiring a change. No code change is recommended under the current brief (preserving price-only management and adding no other authority).","line":253,"path":"src/StepsBranch.sol","reproduction":"Setup: StepsBranch with DocumentedIntake and MockToken; ALICE holds 10 IMD and approved the ledger. 1) ALICE: propose(0, 0x0, BODY) -> id 0; fundWithPayment(0, BODY, 0.5e18) -> requestId R; read fundedAt. 2) vm.warp(fundedAt + 7 days). 3) MALLORY (any address): expirePending(0) succeeds. 4) Intake: deliver(R, keccak256('r'), 'ipfs://x') is delivered (returns true). Expected by a funder: their paid success appends checkpoint 0. Actual: lateReceiptOf(0).received == true with resultHash keccak256('r'); count() == 0; branches(0).head == 0; proposals(0).status == 5 (Expired); ALICE balance == 9.5 IMD, no refund. Verified locally with a scratch Foundry test (6/6 probes passed) against the current tree.","severity":"info","snippet":"    function expirePending(uint256 id) external nonReentrant {\n        if (id >= proposals.length) revert UnknownProposal();\n        Proposal storage p = proposals[id];\n        if (p.status != Status.Pending) revert NotPending();\n        if (block.timestamp < uint256(p.fundedAt) + PENDING_TIMEOUT) revert TooEarly();","title":"Permissionless expirePending at exactly day 7 can front-run a late writer complete, permanently excluding a paid successful result from the branch (specified design, documented here as a trust/livenes"}],"hash":"89d6154e2d1938885b7ac276eff205ca7d73db5adcf59f96ab611a0fd1543455","nodeId":"bb70c1c4-c504-4822-9033-1b48c2283216","outcome":"completed","summary":"Review complete. The control-flow area (Execution Trace, Periphery, First Principles) of `StepsBranch` holds, and the findings record is written to `.imd-findings.json` with one info-level note and 18 coverage rows.\n\n**What I checked**\n\n- **Execution flow.** Every state-changing path in `src/StepsBranch.sol` was traced from entry to final state. Funding reserves the branch slot before any external call, so each revert rolls the payment back. The balance delta is exact, the approval goes 0 → amount → 0, and the request id must be nonzero and unused. Callbacks require the immutable Intake as sender and a locally admitted request id. For a Pending proposal the branch consistency check can never fail, so a valid delivery cannot be lost. Late outcomes write only the receipt and late event.\n- **Periphery.** Selector constants match the real `onResult` and `onFailure` signatures, and the action constant matches the documented bytes32. The ABI copies in `deployment/` and `sdk/` equal the compiled ABI, and the committed artifact's init and runtime bytes equal a fresh build with the required compiler settings. The source hash matches the adaptation notes.\n- **Live bindings.** Read-only mainnet calls confirmed the Intake has code, prices `job.open@job-1` at 0.5 IMD, and exposes a 200,000 callback gas. The asset reads as IMD with 18 decimals, and the request owner is an externally owned account.\n- **Reproduction.** All 59 forge tests pass. The sibling-order fuzz ran 256 runs with the recorded seed. The 16 SDK tests pass with viem installed outside the repository. The heaviest cold callback measured 192,259 gas, matching the recorded evidence.\n- **Probes.** Six scratch tests covered paths the suite leaves implicit: third-party funding of another's draft, expiry racing a late success, an older draft after a refusal, a child branch surviving parent advances, owner edge cases, and an empty-document snapshot. All behaved as designed. The scratch directory was removed afterward.\n\n**Finding recorded**\n\n- **Info.** Anyone can call `expirePending` at exactly seven days, ahead of the writer's completion. The funder's paid success is then archived and can never become a checkpoint. This is the specified design, so it is documented as a trust and liveness assumption with a concrete reproduction, not as a defect to fix.\n\n**Not reached**\n\n- The Anvil cold-callback integration script was not rerun. The in-forge gas measurement matches its recorded maximum.","treeHash":null,"usage":{"cachedInputTokens":1887829,"inputTokens":514,"model":"claude-fable-5-1","outputTokens":41996,"runtime":"claude","turns":41,"wallClockMs":610102}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"410247426ec5a6df","findings":[{"citation":"resolved","description":"Numerical Gap (boundary x invariant). The brief's invariant is 'exact payment under the funder's cap'. The ledger enforces exactness against its own owner-set `price` (set at deployment to 500000000000000000), never against the Intake's live `priceOf(ACTION, asset)`. The live Intake at 0xa43e6f75ee006411f79ac1c84120606c2330de82 (chain 1) was probed with eth_call on 2026-10-28: requestWithFailure reverts with a custom error (selector 0xbc03bccd, args price/amount) when amount < priceOf, but accepts and pulls the full `amount` when amount >= priceOf (verified with amount = price, price+1 and 2*price under a token code override). priceOf currently equals the manifest price (0.5 IMD), so the two prices agree at launch. The Intake's owner can move priceOf at any time; the IntakeJobHub reference reads priceOf on every ask for exactly this reason. If priceOf drops to e.g. 0.4 IMD, every fundWithPayment keeps pulling 0.5 IMD from the funder and the Intake keeps the surplus 0.1 IMD with no refund path, until the ledger owner notices and calls setPrice. maxPayment does not protect the funder here because it is compared with the stale stored price, not the live one. In the opposite direction (priceOf rises above `price`) nothing is lost: the Intake reverts on chain and funding is blocked until setPrice is raised. This is a trust/liveness dependency on the owner, documented here rather than a bypass. Suggested minimal fix that keeps price-only management: read `uint256 live = intake.priceOf(ACTION, asset)` at funding, pay `live`, and treat `price` as the owner's ceiling (`if (live > price || live > maxPayment) revert PriceAboveLimit();`). That is a semantic change to what `price` means, so it is a scope decision for the requester; alternatively document that the owner must track the Intake's price.","line":181,"path":"src/StepsBranch.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport \"forge-std/Test.sol\";\nimport {StepsBranch, IIntake} from \"src/StepsBranch.sol\";\nimport {MockToken} from \"src/MockToken.sol\";\n\n/// Models the live Intake's payment rule observed on mainnet (0xa43e…de82) with eth_call:\n/// amount below priceOf reverts, amount at or above priceOf is accepted and pulled in full.\ncontract PricedIntake {\n    struct Callback { address target; bytes4 selector; }\n    MockToken public token;\n    uint256 public livePrice;\n    uint256 public nonce;\n    error ShortPayment(uint256 price, uint256 amount);\n    constructor(MockToken t, uint256 p) { token = t; livePrice = p; }\n    function setPrice(uint256 p) external { livePrice = p; }\n    function priceOf(bytes32, address) external view returns (uint256) { return livePrice; }\n    function requestWithFailure(bytes32, bytes calldata, Callback calldata, bytes4, address asset, uint256 amount) external returns (bytes32 id) {\n        if (amount < livePrice) revert ShortPayment(livePrice, amount);\n        require(MockToken(asset).transferFrom(msg.sender, address(this), amount), \"pull\");\n        id = bytes32(++nonce);\n    }\n}\n\ncontract PriceSeamTest is Test {\n    bytes constant BODY = '{\"objective\":\"x\",\"skill\":\"research-report\"}';\n    uint256 constant LEDGER_PRICE = 0.5 ether;\n    MockToken token; PricedIntake intake; StepsBranch ledger;\n    address funder = address(0xF00D);\n\n    function setUp() public {\n        token = new MockToken();\n        intake = new PricedIntake(token, LEDGER_PRICE);\n        ledger = new StepsBranch(IIntake(address(intake)), address(token), LEDGER_PRICE, address(this));\n        token.mint(funder, 10 ether);\n        vm.prank(funder); token.approve(address(ledger), type(uint256).max);\n    }\n\n    /// The Intake lowers its live price to 0.4 IMD. The ledger still charges its stored 0.5 IMD,\n    /// the Intake accepts the surplus, and the funder overpays 0.1 IMD with no refund path.\n    function testFunderPaysStoredPriceNotLiveIntakePrice() public {\n        intake.setPrice(0.4 ether);\n        assertEq(intake.priceOf(ledger.ACTION(), address(token)), 0.4 ether, \"live price\");\n\n        vm.startPrank(funder);\n        uint256 id = ledger.propose(0, bytes32(0), BODY);\n        ledger.fundWithPayment(id, BODY, LEDGER_PRICE);\n        vm.stopPrank();\n\n        // Expected: the funder is charged the Intake's live price for the action (0.4 IMD).\n        // Actual on this code: 0.5 IMD leaves the funder and the Intake keeps all of it.\n        assertEq(token.balanceOf(funder), 10 ether - 0.4 ether, \"funder charged more than the live price\");\n        assertEq(token.balanceOf(address(intake)), 0.4 ether, \"intake received more than its price\");\n    }\n}","reproduction":"State: ledger deployed with price = 0.5e18; Intake (modelled on the live one) has priceOf = 0.4e18 and accepts any amount >= priceOf. Funder holds 10 IMD and approved the ledger. Calls: funder -> propose(0, 0x0, body); funder -> fundWithPayment(id, body, 0.5e18). Expected: funder is charged the live price 0.4e18 (balance 9.6e18, Intake holds 0.4e18). Actual: funder is charged 0.5e18 (balance 9.5e18), the Intake holds 0.5e18, and the 0.1e18 difference is unrecoverable. test/scratch/PriceSeam.t.sol fails on this code with 'funder charged more than the live price: 9500000000000000000 != 9600000000000000000'.","severity":"low","snippet":"        uint256 amount = price;","title":"fundWithPayment charges the ledger's stored price, not the Intake's live price: funders overpay whenever the Intake's priceOf drops below `price`"},{"citation":"resolved","description":"Boundary (bytes input x gas). onResult hashes the full URI calldata and copies it to memory before deciding whether it fits the 2048-byte event bound. The cost grows with the URI length (keccak 6 gas/word, calldata copy, and quadratic memory expansion). Measured cold with a mock Intake that forwards exactly 200000 gas (test/scratch/GasProbe.t.sol, testOmittedUriThreshold): 2049 B delivered, 16384 B delivered, 65536 B delivered, 98304 B NOT delivered (the callback runs out of gas and the proposal stays Pending until expirePending). The Intake's documented URIs are short (ipfs://<CID>/result.json or https://api.imd.fun/intake-results/<id>), so this is not reachable with the current writer; it bounds the 'longer locators remain hash-committed' promise at roughly 64-96 KiB. No change recommended; recorded so the limit is known. The heaviest realistic path (first checkpoint ever, origin branch, nonzero proposal id, 2048-byte URI) costs 188122 gas inside the callee under cold storage, 11878 under the stipend; late success with a 2048-byte URI costs 103071 and onFailure 57905.","line":207,"path":"src/StepsBranch.sol","reproduction":"State: funded Pending proposal on a fresh origin branch. Call from the Intake with gas limit 200000: onResult(requestId, keccak256('r'), uri) with uri = 98304 bytes of 'a'. Expected per README: URI omitted from the event, checkpoint recorded with uriHash. Actual: the call runs out of gas, no checkpoint is recorded, proposal stays Pending. With uri = 65536 bytes the same call succeeds.","severity":"info","snippet":"        bytes32 uriHash = keccak256(bytes(uri));","title":"Callback delivery of a result whose URI exceeds roughly 64 KiB does not fit the 200000-gas stipend, so such a result is never recorded"}],"hash":"8a5eba1d23ec5eaf4fab92037de124ed833102a21a1147b26943761e2329b800","nodeId":"d4313945-fad4-4ce2-a08f-ed59e4633b91","outcome":"completed","summary":"All checks are complete and the working tree has no modified tracked files. Here is the report.\n\n**Outcome.** The math, boundary and numerical-gap review of `StepsBranch` found one low-severity defect and one informational boundary. The findings and an 18-row coverage record are in `.imd-findings.json`. Baseline evidence was reproduced: 59 of 59 Foundry tests pass including the 256-run sibling-order fuzz, and the 16 SDK tests pass with viem installed outside the repository.\n\n**Finding 1, low: funding pays the stored price, not the Intake's live price.** The ledger pulls its owner-set price and checks maxPayment against it. I probed the live Intake on chain 1 with eth_call: it reverts when the amount is below its priceOf, but accepts and keeps any amount at or above it. The two prices agree today at 0.5 IMD. If the Intake's price drops and the owner has not yet lowered the ledger's price, every funder overpays the difference with no refund. The proof test under test/scratch fails on the current code and passes once funding reads the live price with the stored price as a ceiling. That changes what the price field means, so it is a scope decision for the requester.\n\n**Finding 2, info: very long result URIs cannot be delivered.** Measured cold with a 200000-gas mock Intake, a 64 KiB URI is recorded but a 96 KiB one runs out of gas and leaves the proposal Pending. The Intake's real locators are short, so this only bounds the hash-commitment promise.\n\n**What holds.** All plus-one sentinels and uint64 casts are guarded so they cannot wrap. The body, event-URI and snapshot limits are inclusive at 16384, 2048 and 4096 bytes, and the body limit equals the live Intake's MAX_BODY_BYTES. Expiry is allowed at exactly seven days. The heaviest callback, a first checkpoint from a nonzero proposal with a 2048-byte URI, costs 188122 gas inside the callee under cold storage. Live readbacks match the manifest: priceOf is 0.5 IMD, callbackGas is 200000, the asset is IMD with 18 decimals.\n\n**Not reached.** Nothing in the assigned area was left unexamined. Access control, economics and control flow outside the math seams belong to the other specialists.","treeHash":null,"usage":{"cachedInputTokens":2278550,"inputTokens":642,"model":"claude-fable-5-1","outputTokens":43227,"runtime":"claude","turns":44,"wallClockMs":604991}},{"artifacts":[],"attempt":2,"bundleHash":"d86e524d8b7c0caae16e03c5bf0a4d253767889b2947a0861c3dad204a4f8200","device":"dff6c0d3de4aa913","findings":[],"hash":"94cb9eee55eac8d7add7260abe1975cbe27c2833004954ae2062794f9950c850","nodeId":"9eba441a-0476-414b-8675-d998166ad431","outcome":"completed","summary":"Done. launch.json is written and validated, and it is the only file changed.\n\n**What the manifest contains**\n\n- One contract entry, StepsBranch, with constructor arguments in declaration order: the Intake address, the asset address, the price, and `$owner` for initialOwner.\n- The intake and asset values are the exact same-chain bindings from the brief, and the price is the brief's static word.\n- The notes field explains the owner readback expectation, the owner's only powers (setPrice and two-step handover), compiler settings, the preserved runtime behavior, and that deployment evidence stays NOT_AVAILABLE with productionReady false.\n\n**What I verified**\n\n- The constructor in src/StepsBranch.sol takes exactly those four parameters, is nonpayable, makes no external call, and rejects only zero addresses and a zero price. The factory receives no role.\n- A scratch test deployed the exact manifest arguments from a fake factory on an empty chain with chainId 1 and confirmed every readback and the runtime size bound.\n- The manifest has only the keys kind, contracts and notes, string arguments, an ASCII identifier under 32 characters, and notes under 4000 characters.\n\n**Test results with the verifier's fuzz seed**\n\n| Suite | Result |\n|---|---|\n| Solidity (Supplied, StepsBranch, AuditFindings) | 59 passed, 0 failed |\n| Sibling-order fuzz | 256 runs, passed |\n| Scratch manifest rehearsal | 1 passed |\n| SDK tests (viem installed in test/scratch only) | 16 passed, 0 failed |\n\n**On the previous attempt**\n\nIt failed because the model backend reported a past-due subscription, not because of anything in the repository. The committed launch.json from the earlier contributor already had the correct constructor entries. I kept those entries unchanged and only expanded the notes, so no accepted work was rewritten.\n\n**Not done, by design**\n\nNo deployment evidence exists yet. Launch ID, address, transaction, receipt and live readbacks remain missing until the official IMD deployment, and paid live product tests stay NOT_RUN.","treeHash":"5a9e0efeb26d1bc7305ad271fdba017dbc9e573b","usage":{"cachedInputTokens":520064,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":10349,"runtime":"claude","turns":26,"wallClockMs":238137}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"970da38f1d564250","findings":[],"hash":"964c4315923128ad8724fd1d63f6250fce65d242841d4cf68b06dbc82470b9ee","nodeId":"9eba441a-0476-414b-8675-d998166ad431","outcome":"failed","summary":"Claude Code can't use your Claude subscription, usually because a payment is past due · Pay or renew at https://claude.ai/… (if you just paid, try again shortly), or use an Anthropic API key instead","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":null,"outputTokens":0,"runtime":"claude","turns":1,"wallClockMs":942}},{"artifacts":[],"attempt":2,"bundleHash":"1992a287fca25b5c37dafb54b3c96776af6d75419b39d2251838d61aace5c1e3","device":"bfeaa965edf57623","findings":[],"hash":"9c28128d6d7c001b219788e1e6e3cc73c93abc989d9fb279840b5d2bc80edb73","nodeId":"d1c3c0b3-bcf7-4c24-818a-17b017ad9c72","outcome":"completed","summary":"StepsBranch is ready for the launch. The contract source didn't need any change, and `forge build` and `forge test` pass: 59/59 with the verifier's fuzz seed. The changes are uncommitted in the working tree, because this machine has no git identity and I'm not allowed to change `.git/`.\n\n**Why the previous attempt was rejected.** On this machine forge writes its build output to `artifacts/`, and a forced or clean build (`forge build --force`, `forge clean`) wipes that folder. It held two tracked files, `artifacts/StepsBranch.abi.json` and `artifacts/StepsBranch.artifact.json`, so the build deleted them. I confirmed that, then moved both, byte-identical, to `deployment/`. A forced build now leaves the tree clean.\n\n**What changed:**\n- **Contract source:** unchanged. `src/StepsBranch.sol` still has SHA-256 `6451f1c4…ec1a7`, the value in the supplied evidence. It already met the launch rules: one contract, four static arguments, nonpayable constructor, explicit `$owner`, no forbidden opcodes. `foundry.toml` and `launch.json` are untouched.\n- **New test file:** `test/AuditFindings.t.sol` has two tests reproducing the audit's findings. The 57 supplied tests are unchanged.\n- **New evidence:** `evidence/LOCAL-REPRODUCTION.json` (hashes, build settings, constructor arguments, results) and `evidence/LOCAL-INTEGRATION-REPRODUCED.json` (the cold-callback run).\n- **Docs:** `ADAPTATION.md` lists each change and why. `README.md` has the new artifact location and a note on the branch-sharing property below.\n\n**Audit findings.** Both reproduce, and I fixed neither, because both describe the supplied design:\n- **Low, branch squatting:** anyone who pays can take a shared branch's single pending slot and add their own checkpoint, which makes other people's drafts on that branch permanently stale. The victim is never charged and can fork their own branch. Fixing it would mean adding branch ownership, which the brief forbids. The requester needs to accept this property or change the product later.\n- **Info, callback gas:** the heaviest callback needs about 192k of the Intake's 200,000-gas allowance, and the Intake's owner can change that setting. If it dropped below about 195k, the heaviest first checkpoints would stop being delivered and would wait for the 7-day expiry. No funds are at risk; it's something to monitor.\n\n**What I reproduced locally:**\n- **Solidity tests:** the 57 supplied tests plus the 2 new ones all pass. Sibling-order fuzzing ran its 256 runs.\n- **SDK tests:** 16/16 pass, with viem installed outside the repo.\n- **Cold callbacks:** 17 separate transactions on a local Anvil chain with disposable accounts, each under the real 200,000-gas allowance, and all delivered. The heaviest used 192,259 gas: first checkpoint, nonzero proposal, 2048-byte URI. Late success, refusal and failure while newer work was pending all left the branch alone. The 4 KiB snapshot copy cost 3,021,993 gas. These match the supplied evidence exactly.\n- **Launch-check rehearsal:** a CREATE2 deployment on chain 1 with no code at the Intake or token addresses succeeds. The owner reads back as `0x0bced6…7352`, neither the factory nor the launch wallet. Runtime is 11,460 bytes, under the 24,576 limit, with no reachable DELEGATECALL, CALLCODE or SELFDESTRUCT.\n- **Live mainnet reads (no transactions):** the Intake at `0xa43e…de82` has code and exposes `requestWithFailure`. It prices `job.open@job-1` at 0.5 IMD, matching the constructor price, and its callback gas is 200000. The token at `0xd34a…63b7` is IMD with 18\n\nNot delivered: artifacts/AuditFindings.t.sol/AuditFindingsTest.json, artifacts/AuditFindings.t.sol/AuditVm.json, artifacts/AuditFindings.t.sol/VmContractHelper13.json, artifacts/MockToken.sol/MockToken.json, artifacts/StepsBranch.sol/IERC20.json, artifacts/StepsBranch.sol/IIntake.json, artifacts/StepsBranch.sol/StepsBranch.json, artifacts/StepsBranch.t.sol/DocumentedIntake.json and 14 more. Under artifacts/, only this step's named outputs leave the machine.","treeHash":"2d8ad63f4b251888dc322dda11d2b755f19e6915","usage":{"cachedInputTokens":2161119,"inputTokens":62,"model":"claude-opus-5-5","outputTokens":27051,"runtime":"claude","turns":37,"wallClockMs":566193}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"ddfb1efa72fe9a94","findings":[{"citation":"resolved","description":"Reproduced. fundWithPayment pays `price` (owner-set at deployment to 500000000000000000) for the compile-time constant ACTION = \"job.open@job-1\" (line 77) and never reads intake.priceOf(ACTION, asset). The intake-client reference states the Intake pulls exactly `amount`, forwards it whole to the treasury, refunds nothing, reverts below its price, and that the price and action version are owner-settable on the Intake side (\"a moved price must not need a redeploy\"). Consequences: (a) if the Intake owner lowers priceOf below the stored price, every funder with maxPayment >= price pays the stored price and the surplus goes to IMD's treasury irrecoverably; maxPayment does not protect because it is compared with the stale stored value, not the amount actually charged. (b) If priceOf rises above the stored price, the Intake reverts and nothing is spent, but funding is blocked until the owner calls setPrice. (c) If the Intake stops selling job.open@job-1 (priceOf == 0, e.g. a job-2 version), ACTION is a constant and intake/asset are immutable, so the ledger can never append another checkpoint. Today priceOf equals the manifest price (readback 500000000000000000 on 2026-10-28 in evidence/LOCAL-REPRODUCTION.json), so there is no loss at launch; this is a bounded, owner-visible dependency on the Intake's price staying at or below `price`, not a permission bypass. The brief restricts management to price only, so the minimal fix that preserves that design is to read `uint256 live = intake.priceOf(ACTION, asset)` in fundWithPayment, revert when live == 0 or live > price or live > maxPayment, and pay `live` (treating `price` as the owner's ceiling). Making ACTION owner-settable is a scope decision for the requester. Deciding whether to accept the dependency as documented or widen scope is the requester's call; it is not a deployment blocker on its own.","line":181,"path":"src/StepsBranch.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport \"forge-std/Test.sol\";\nimport {StepsBranch, IIntake} from \"src/StepsBranch.sol\";\nimport {MockToken} from \"src/MockToken.sol\";\n\n/// Models the live Intake's payment rule observed on mainnet (0xa43e…de82) with eth_call:\n/// amount below priceOf reverts, amount at or above priceOf is accepted and pulled in full.\ncontract PricedIntake {\n    struct Callback { address target; bytes4 selector; }\n    MockToken public token;\n    uint256 public livePrice;\n    uint256 public nonce;\n    error ShortPayment(uint256 price, uint256 amount);\n    constructor(MockToken t, uint256 p) { token = t; livePrice = p; }\n    function setPrice(uint256 p) external { livePrice = p; }\n    function priceOf(bytes32, address) external view returns (uint256) { return livePrice; }\n    function requestWithFailure(bytes32, bytes calldata, Callback calldata, bytes4, address asset, uint256 amount) external returns (bytes32 id) {\n        if (amount < livePrice) revert ShortPayment(livePrice, amount);\n        require(MockToken(asset).transferFrom(msg.sender, address(this), amount), \"pull\");\n        id = bytes32(++nonce);\n    }\n}\n\ncontract PriceSeamTest is Test {\n    bytes constant BODY = '{\"objective\":\"x\",\"skill\":\"research-report\"}';\n    uint256 constant LEDGER_PRICE = 0.5 ether;\n    MockToken token; PricedIntake intake; StepsBranch ledger;\n    address funder = address(0xF00D);\n\n    function setUp() public {\n        token = new MockToken();\n        intake = new PricedIntake(token, LEDGER_PRICE);\n        ledger = new StepsBranch(IIntake(address(intake)), address(token), LEDGER_PRICE, address(this));\n        token.mint(funder, 10 ether);\n        vm.prank(funder); token.approve(address(ledger), type(uint256).max);\n    }\n\n    /// The Intake lowers its live price to 0.4 IMD. The ledger still charges its stored 0.5 IMD,\n    /// the Intake accepts the surplus, and the funder overpays 0.1 IMD with no refund path.\n    function testFunderPaysStoredPriceNotLiveIntakePrice() public {\n        intake.setPrice(0.4 ether);\n        assertEq(intake.priceOf(ledger.ACTION(), address(token)), 0.4 ether, \"live price\");\n\n        vm.startPrank(funder);\n        uint256 id = ledger.propose(0, bytes32(0), BODY);\n        ledger.fundWithPayment(id, BODY, LEDGER_PRICE);\n        vm.stopPrank();\n\n        // Expected: the funder is charged the Intake's live price for the action (0.4 IMD).\n        // Actual on this code: 0.5 IMD leaves the funder and the Intake keeps all of it.\n        assertEq(token.balanceOf(funder), 10 ether - 0.4 ether, \"funder charged more than the live price\");\n        assertEq(token.balanceOf(address(intake)), 0.4 ether, \"intake received more than its price\");\n    }\n}","reproduction":"State: StepsBranch(intake, token, 0.5e18, owner) against an Intake modelled on the mainnet rule (amount < priceOf reverts; amount >= priceOf is pulled whole); funder holds 10e18 and approved the ledger. Intake.priceOf set to 0.4e18. Calls: funder -> propose(0, 0x0, body) -> id; funder -> fundWithPayment(id, body, 0.5e18). Expected: the funder is charged at most the live price (balance 9.6e18, Intake holds 0.4e18). Actual: funder balance 9.5e18, Intake holds 0.5e18; the 0.1e18 difference has no refund path. Ran test/scratch/PriceSeam.t.sol (the audit_math proof): FAIL 'funder charged more than the live price: 9500000000000000000 != 9600000000000000000'. Related: with priceOf == 0, fundWithPayment(id, body, 1e18) reverts ActionNotSold forever; with priceOf == 0.6e18 it reverts ShortPayment(0.6e18, 0.5e18) although the funder offered 1e18 (test/scratch/Probe.t.sol testActionRetiredBricksFunding, passes on this code, no funds lost in those two cases).","severity":"low","snippet":"        uint256 amount = price;\n        if (amount > maxPayment) revert PriceAboveLimit();","title":"fundWithPayment forwards the ledger's stored price, never the Intake's live priceOf: a lowered Intake price is overpaid to the treasury with no refund, and a raised price or retired job-1 action block"},{"citation":"resolved","description":"Reproduced. Any address may expire a Pending proposal once seven days have passed; the funder has no priority and there is no path to promote a late receipt into a checkpoint. Whoever lands first at the boundary decides whether a slow but successful job enters the lineage. A third party can front-run the writer's complete() with expirePending for gas only; the funder's 0.5 IMD is already spent (the Intake never refunds) and the freed slot can be taken immediately by the griefer's own paid proposal. The brief and README require exactly this (anyone may call expirePending after seven days; late outcomes are archived once and never advance a head), jobs normally finish in minutes to hours, and the attacker gains nothing, so this is recorded as the permission asymmetry the requester is accepting, not a defect to fix under the current brief (price-only management, no other authority). A funder-only grace window after day 7 would need a scope decision.","line":253,"path":"src/StepsBranch.sol","reproduction":"State: fresh ledger, funder 0xF00D proposes id 0 on branch 0 and funds it (0.5e18 spent), requestId R. vm.warp(fundedAt + 7 days). Griefer 0xBAD calls expirePending(0) -> succeeds. Intake then delivers onResult(R, keccak256('r'), 'ipfs://x/result.json') -> delivered true. Expected from the funder's view: checkpoint 0 appended. Actual: lateReceiptOf(0) = {received:true, status:0, resultHash:keccak256('r')}, count() == 0, branches(0).head == 0, proposals(0).status == 5 (Expired), funder balance 9.5e18 with no refund, and the griefer's propose+fundWithPayment on branch 0 succeeds. test/scratch/Probe.t.sol testExpiryFrontRunsLateSuccess passes on this code; the supplied test/StepsBranch.t.sol testLateSuccessArchivedWithoutCheckpointOrHeadChange (line 330) pins the same path with expiry by 0xBEEF.","severity":"info","snippet":"    function expirePending(uint256 id) external nonReentrant {\n        if (id >= proposals.length) revert UnknownProposal();\n        Proposal storage p = proposals[id];\n        if (p.status != Status.Pending) revert NotPending();\n        if (block.timestamp < uint256(p.fundedAt) + PENDING_TIMEOUT) revert TooEarly();","title":"Permissionless expirePending at exactly fundedAt + 7 days can be ordered ahead of a late Intake delivery, so a paid success is archived in lateReceiptOf and never becomes a checkpoint (specified behav"},{"citation":"resolved","description":"Reproduced. keccak256 over the full calldata URI plus the memory expansion for the string argument grow with URI length, so a very large URI exhausts the Intake's fixed 200000-gas stipend; the Intake records the callback as not delivered and the proposal stays Pending until expirePending. Real Intake URIs (ipfs://<CID>/result.json or the plane's /intake-results/<id>) are about 100 bytes, so this is not reachable in normal operation; it bounds the README's 'longer locators remain hash-committed' promise. No funds at risk, no change recommended.","line":207,"path":"src/StepsBranch.sol","reproduction":"State: funded Pending proposal on a fresh ledger; a mock Intake forwards exactly 200000 gas to onResult. URI of n bytes of 'a': n = 2048, 2049, 16384, 65536, 81920 -> delivered, count() == 1; n = 98304 -> not delivered (out of gas), count() == 0, proposal still Pending. Expected per README: URI omitted from the event and the checkpoint recorded with its uriHash for any length. test/scratch/Probe.t.sol testUriStipendThreshold logs these values on this code.","severity":"info","snippet":"        bytes32 uriHash = keccak256(bytes(uri));","title":"A success callback whose URI exceeds roughly 80-96 KiB does not fit the 200000-gas stipend because onResult hashes and copies the whole URI before deciding to omit it (merged: write_foundry_tests 775a"},{"citation":"resolved","description":"Reproduced by the supplied test/AuditFindings.t.sol testAuditFindingSharedBranchCanBeTakenWithoutVictimLoss. The victim is never charged (BranchBusy and StaleAnchor revert before any token moves) and can continue on its own forkFromOrigin or fork branch. The griefer pays the real 0.5 IMD price per occupation and receives a real job for it. The brief forbids adding authority, so branch ownership would be a product change; recorded as an accepted liveness property, not a defect.","line":179,"path":"src/StepsBranch.sol","reproduction":"VICTIM: propose(b, head, body) -> vid. ATTACKER: propose(b, head, body2) -> aid; fundWithPayment(aid, body2, 0.5e18). VICTIM: fundWithPayment(vid, body, 0.5e18) reverts BranchBusy. After the attacker's success callback, VICTIM: fundWithPayment(vid, ...) reverts StaleAnchor permanently; VICTIM's balance is unchanged. See test/AuditFindings.t.sol lines 54-88.","severity":"info","snippet":"        if (b.pendingProposalPlusOne != 0) revert BranchBusy();\n        if (b.head != p.anchor || b.revision != p.baseRevision) revert StaleAnchor();","title":"Branches are shared and permissionless: any payer can occupy a branch's single pending slot and, on success, strand other proposers' Drafts as StaleAnchor (known, documented in ADAPTATION.md; audit_ec"},{"citation":"resolved","description":"Reproduced. Both balance checks are relative to beforeBalance, so a stray transfer neither blocks funding nor is ever recoverable: there is no sweep, and the brief forbids adding authority. Only the sender loses; recorded as a trust note.","line":189,"path":"src/StepsBranch.sol","reproduction":"token.mint(ledger, 1e18) (or any direct transfer); funder proposes and funds normally -> succeeds; token.balanceOf(ledger) remains 1e18 and no function can move it. test/scratch/Probe.t.sol testStrandedDirectTransfer passes on this code.","severity":"info","snippet":"        uint256 beforeBalance = IERC20(asset).balanceOf(address(this));\n        _tokenCall(abi.encodeCall(IERC20.transferFrom, (msg.sender, address(this), amount)));\n        if (IERC20(asset).balanceOf(address(this)) != beforeBalance + amount) revert PaymentMismatch();","title":"IMD transferred directly to the ledger is stranded forever; it does not disturb the funding delta checks (audit_economics d52b39b9)"}],"hash":"b2bb99a89bc89c79034278d55d06ca43cff63f68b6ec1a49fbfa59a74467da9a","nodeId":"775e3202-8ad4-46cd-af6a-4187812da67d","outcome":"completed","summary":"All snippets match the tree at their stated lines, and the working tree holds no tracked changes. The review is complete.\n\n**Outcome:** no blocking defect. The findings file holds one low and four info findings with a coverage row for all 14 entry points plus the constructor/manifest and test reproduction. The findings file is at `.imd-findings.json`.\n\n**What I verified**\n- Full suite is green: 82 Foundry tests including the 256-run invariant campaign, and the SDK suite passes 16 of 16 with viem installed outside the repository.\n- Callback selectors, the action id, the committed ABI, the source hash in ADAPTATION.md, and creation/runtime sizes under the factory limits all check out. The constructor is static, nonpayable, makes no external call, and gives the factory no role.\n- launch.json uses only schema keys, four string arguments in declaration order, and `$owner`, which the brief itself specifies for initialOwner. I dropped the specialist note asking for a static address since the brief wins over the reference here.\n\n**Findings kept, after merging duplicates across specialists**\n1. **Low, with proof.** fundWithPayment pays the owner-set stored price and never reads the Intake's live priceOf. A lowered Intake price is overpaid to the treasury with no refund, a raised price or retired job-1 action blocks funding until the owner reacts. The audit_math proof fails on this code for its stated reason, so it is attached. Three specialists reported this same root cause.\n2. **Info.** Permissionless expirePending at the seven-day boundary can be ordered ahead of a late delivery, archiving a paid success. This is the behaviour the brief requires, recorded as an accepted asymmetry. Three specialists reported it.\n3. **Info.** URIs above roughly 80 KiB exceed the 200000-gas stipend. Not reachable with real Intake URIs. Two specialists reported it.\n4. **Info.** Shared-branch slot occupation, already documented in ADAPTATION.md.\n5. **Info.** IMD sent directly to the ledger is stranded.\n\nMy own Pashov pass found nothing beyond these. The remaining open item for the requester is a scope decision on finding 1: accept the documented dependency on the Intake price, or let fundWithPayment read priceOf and treat the stored price as a ceiling.","treeHash":null,"usage":{"cachedInputTokens":913543,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":26623,"runtime":"claude","turns":28,"wallClockMs":387421}}],"verification":[{"checks":[{"durationMs":2219,"exitCode":0,"name":"build","output":"Compiling 5 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.05s\nCompiler run successful!\n","passed":true},{"durationMs":233,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 5 tests for test/LaunchPackaging.t.sol:LaunchPackagingTest\n[PASS] testAuditFindingBranchSquattingIsDocumentedDesign() (gas: 2458691)\n[PASS] testAuditFindingHeaviestCallbackNeedsTheLive200000Stipend() (gas: 1740659)\n[PASS] testConstructorRejectsEachZeroStaticArgument() (gas: 4059)\n[PASS] testFactoryCreate2LaunchOnEmptyMainnetChainMatchesManifest() (gas: 3102299)\n[PASS] testRuntimeIsBoundedAndHasNoReachableEscapeOpcodes() (gas: 4186646)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 80.78ms (67.79ms CPU time)\n\nRan 7 tests for test/Supplied.t.sol:ReviewTest\n[PASS] testConflictClearsLock() (gas: 784789)\n[PASS] testDeliveryRejected() (gas: 1245279)\n[PASS] testHappyPathAndCap() (gas: 647877)\n[PASS] testLongUriAnnounced() (gas: 1963523)\n[PASS] testSilentProposalExpires() (gas: 812205)\n[PASS] testSnapshotIsPersisted() (gas: 693219)\n[PASS] testZeroResultHashRejected() (gas: 438345)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 86.29ms (8.20ms CPU time)\n\nRan 50 tests for test/StepsBranch.t.sol:StepsBranchTest\n[PASS] testBodyCannotChangeAtFunding() (gas: 177788)\n[PASS] testBodySizeBounds() (gas: 149015)\n[PASS] testConstructorRehearsesWithStaticMainnetBindingsOnEmptyChain() (gas: 18512)\n[PASS] testDeadlineAloneDoesNotExpireOrDiscardSuccess() (gas: 258694)\n[PASS] testDuplicateOrConflictingLateOutcomesCannotOverwrite() (gas: 345236)\n[PASS] testDuplicateSuccessAndLateFailureRejected() (gas: 298539)\n[PASS] testExpiredOldParentCannotAffectIndependentFork() (gas: 1434081)\n[PASS] testExpiryBoundaryAndPermissionlessRelease() (gas: 274845)\n[PASS] testExpiryRejectsUnknownDraftAndSettled() (gas: 412406)\n[PASS] testFactoryDeploymentAssignsExplicitOwner() (gas: 321394)\n[PASS] testFalseApprovalRollsBackTokenTransfer() (gas: 537049)\n[PASS] testFalseTokenReturnDoesNotCreatePaidState() (gas: 420221)\n[PASS] testFirstRealisticUriCallbackFitsStipend() (gas: 282525)\n[PASS] testForkDoesNotMoveSourceHeadOrCreateSuccessfulCheckpoint() (gas: 895723)\n[PASS] testFuzzSiblingCompletionOrder(uint8,uint256) (runs: 256, μ: 2675829, ~: 2749233)\n[PASS] testHistoricalForkKeepsSourceRevision() (gas: 1382536)\n[PASS] testHttpFallbackUriFitsStipend() (gas: 213642)\n[PASS] testIntakeRejectionRollsBackPaymentAndBranchReservation() (gas: 432412)\n[PASS] testInvalidAndUnauthorizedLateCallbacksLeaveArchiveEmpty() (gas: 390348)\n[PASS] testInvalidFailureStatusCannotSettle() (gas: 92368)\n[PASS] testLargeLateUriOmittedButCommittedUnderStipend() (gas: 370345)\n[PASS] testLargeUriIsCommittedWithoutUnboundedEvent() (gas: 329523)\n[PASS] testLateFailureCannotBeReplacedBySuccess() (gas: 287137)\n[PASS] testLateFailureCannotClearNewPendingOrOverwriteReason() (gas: 785249)\n[PASS] testLateRefusalArchivedWithoutChangingExpiredStatus() (gas: 245294)\n[PASS] testLateSuccessAfterFailureCannotMoveHead() (gas: 721207)\n[PASS] testLateSuccessArchivedWithoutCheckpointOrHeadChange() (gas: 296044)\n[PASS] testLateSuccessCannotChangeAdvancedHeadOrAnotherPending() (gas: 1350272)\n[PASS] testLateSuccessCannotClearNewPendingRequest() (gas: 894259)\n[PASS] testMaxEventUriFitsStipend() (gas: 573982)\n[PASS] testMaximumLateEventUriFitsStipend() (gas: 289747)\n[PASS] testMaximumSnapshotIsPersistedAndReferenceRemainsEmpty() (gas: 1081798)\n[PASS] testNoResultUnlocksBranchWithoutChangingHead() (gas: 703945)\n[PASS] testOfficialTupleAbiAndNestedPayment() (gas: 48828)\n[PASS] testOwnershipUsesTwoStepsAndCannotChangeLineage() (gas: 158821)\n[PASS] testParallelForksCompleteInReverseOrderWithSeparateHeads() (gas: 1563409)\n[PASS] testPendingOriginCanBeForkedWithoutFakeSettlement() (gas: 668286)\n[PASS] testPriceChangeCannotExceedFunderLimit() (gas: 268420)\n[PASS] testReferenceForkNeedsNoRecorderOrFiles() (gas: 308827)\n[PASS] testRefusalUnlocksBranchWithoutCheckpoint() (gas: 721837)\n[PASS] testSecondPaidRequestOnSameBranchRejectedBeforeSpending() (gas: 190512)\n[PASS] testSnapshotCapDoesNotRestrictReferenceFork() (gas: 354883)\n[PASS] testSnapshotForkVerifiesExactResultBytes() (gas: 399193)\n[PASS] testStaleDraftCannotFundAfterHeadAdvances() (gas: 363683)\n[PASS] testSubsequentCallbackFitsStipendAndPinsParent() (gas: 760715)\n[PASS] testTokenCannotReenterBranchMutation() (gas: 833477)\n[PASS] testUnknownAnchorAndUnknownBranchRejected() (gas: 74322)\n[PASS] testUnknownAndForeignCallbacksRejected() (gas: 103012)\n[PASS] testZeroAndDuplicateRequestIdsRollBack() (gas: 667582)\n[PASS] testZeroInitialOwnerRejected() (gas: 8240)\nSuite result: ok. 50 passed; 0 failed; 0 skipped; finished in 86.44ms (91.77ms CPU time)\n\nRan 3 test suites in 90.64ms (253.51ms CPU time): 62 tests passed, 0 failed, 0 skipped (62 total tests)\n","passed":true}],"detail":"the build modified tracked files","evaluation":"checks","profile":"foundry","status":"rejected","submissionHash":"5650bae318895857a165cdddfb93e32289e0329e7062211b43a792aebe6003c2","verifiedTreeHash":"22dae72785a5310f09e51d6c7d5df31f9cebf144","verifierVersion":"0.1.0+1d98f47e"},{"checks":[{"durationMs":3109,"exitCode":0,"name":"build","output":"Compiling 7 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.03s\nCompiler run successful!\n","passed":true},{"durationMs":8891,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/AuditFindings.t.sol:AuditFindingsTest\n[PASS] testAuditFindingHeaviestFirstCheckpointFitsStipend() (gas: 1205922)\n[PASS] testAuditFindingSharedBranchCanBeTakenWithoutVictimLoss() (gas: 1306341)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 14.33ms (4.11ms CPU time)\n\nRan 7 tests for test/Supplied.t.sol:ReviewTest\n[PASS] testConflictClearsLock() (gas: 784789)\n[PASS] testDeliveryRejected() (gas: 1245279)\n[PASS] testHappyPathAndCap() (gas: 647877)\n[PASS] testLongUriAnnounced() (gas: 1963523)\n[PASS] testSilentProposalExpires() (gas: 812205)\n[PASS] testSnapshotIsPersisted() (gas: 693219)\n[PASS] testZeroResultHashRejected() (gas: 438345)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 59.82ms (7.76ms CPU time)\n\nRan 22 tests for test/StepsBranchEdges.t.sol:StepsBranchEdgesTest\n[PASS] testCallbacksForDraftOrUnknownIdsRejected() (gas: 189522)\n[PASS] testConstructorDoesNotAssignDeployerOrOrigin() (gas: 56716)\n[PASS] testConstructorRejectsEachZeroArgument() (gas: 6250)\n[PASS] testExpiryBoundaryAndRepeat() (gas: 578668)\n[PASS] testFeeOnTransferAssetIsRejected() (gas: 625128)\n[PASS] testForkAndSnapshotFailurePaths() (gas: 804419)\n[PASS] testForkCheckpointIsIndependentAndDigestDiffersPerBranch() (gas: 2530953)\n[PASS] testFundingFailurePaths() (gas: 989141)\n[PASS] testFuzzMaxPaymentBoundary(uint256,uint256) (runs: 512, μ: 339981, ~: 252139)\n[PASS] testIntakeThatPullsLessIsRejectedAndRolledBack() (gas: 758821)\n[PASS] testIntakeThatPullsNothingIsRejected() (gas: 678633)\n[PASS] testInvalidLateOutcomesRejectedAfterExpiry() (gas: 577913)\n[PASS] testLateMaxUriSuccessWhileNewerPendingThenFirstCheckpointFromNonzeroProposal() (gas: 1709798)\n[PASS] testLateRefusalAndFailureWhileNewerPendingUnderStipend() (gas: 1492388)\n[PASS] testOnTimeOutcomesNeverWriteLateReceipts() (gas: 1140241)\n[PASS] testOnlyOwnerSetsNonzeroPrice() (gas: 103080)\n[PASS] testOversizedExactSnapshotRejectedButReferenceForkWorks() (gas: 1368620)\n[PASS] testStatusNumberingAndConstants() (gas: 74764)\n[PASS] testSynchronousCallbackFromIntakeIsRefusedByGuard() (gas: 730389)\n[PASS] testTwoStepOwnershipFailurePaths() (gas: 330475)\n[PASS] testUnfundedApprovalCannotBeUsedByStranger() (gas: 267306)\n[PASS] testVeryLongUriCommittedUnderStipend() (gas: 5668423)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 59.91ms (46.04ms CPU time)\n\nRan 50 tests for test/StepsBranch.t.sol:StepsBranchTest\n[PASS] testBodyCannotChangeAtFunding() (gas: 177788)\n[PASS] testBodySizeBounds() (gas: 149015)\n[PASS] testConstructorRehearsesWithStaticMainnetBindingsOnEmptyChain() (gas: 18512)\n[PASS] testDeadlineAloneDoesNotExpireOrDiscardSuccess() (gas: 258694)\n[PASS] testDuplicateOrConflictingLateOutcomesCannotOverwrite() (gas: 345236)\n[PASS] testDuplicateSuccessAndLateFailureRejected() (gas: 298539)\n[PASS] testExpiredOldParentCannotAffectIndependentFork() (gas: 1434081)\n[PASS] testExpiryBoundaryAndPermissionlessRelease() (gas: 274845)\n[PASS] testExpiryRejectsUnknownDraftAndSettled() (gas: 412406)\n[PASS] testFactoryDeploymentAssignsExplicitOwner() (gas: 321394)\n[PASS] testFalseApprovalRollsBackTokenTransfer() (gas: 537049)\n[PASS] testFalseTokenReturnDoesNotCreatePaidState() (gas: 420221)\n[PASS] testFirstRealisticUriCallbackFitsStipend() (gas: 282525)\n[PASS] testForkDoesNotMoveSourceHeadOrCreateSuccessfulCheckpoint() (gas: 895723)\n[PASS] testFuzzSiblingCompletionOrder(uint8,uint256) (runs: 256, μ: 2551022, ~: 2122757)\n[PASS] testHistoricalForkKeepsSourceRevision() (gas: 1382536)\n[PASS] testHttpFallbackUriFitsStipend() (gas: 213642)\n[PASS] testIntakeRejectionRollsBackPaymentAndBranchReservation() (gas: 432412)\n[PASS] testInvalidAndUnauthorizedLateCallbacksLeaveArchiveEmpty() (gas: 390348)\n[PASS] testInvalidFailureStatusCannotSettle() (gas: 92368)\n[PASS] testLargeLateUriOmittedButCommittedUnderStipend() (gas: 370345)\n[PASS] testLargeUriIsCommittedWithoutUnboundedEvent() (gas: 329523)\n[PASS] testLateFailureCannotBeReplacedBySuccess() (gas: 287137)\n[PASS] testLateFailureCannotClearNewPendingOrOverwriteReason() (gas: 785249)\n[PASS] testLateRefusalArchivedWithoutChangingExpiredStatus() (gas: 245294)\n[PASS] testLateSuccessAfterFailureCannotMoveHead() (gas: 721207)\n[PASS] testLateSuccessArchivedWithoutCheckpointOrHeadChange() (gas: 296044)\n[PASS] testLateSuccessCannotChangeAdvancedHeadOrAnotherPending() (gas: 1350272)\n[PASS] testLateSuccessCannotClearNewPendingRequest() (gas: 894259)\n[PASS] testMaxEventUriFitsStipend() (gas: 573982)\n[PASS] testMaximumLateEventUriFitsStipend() (gas: 289747)\n[PASS] testMaximumSnapshotIsPersistedAndReferenceRemainsEmpty() (gas: 1081798)\n[PASS] testNoResultUnlocksBranchWithoutChangingHead() (gas: 703945)\n[PASS] testOfficialTupleAbiAndNestedPayment() (gas: 48828)\n[PASS] testOwnershipUsesTwoStepsAndCannotChangeLineage() (gas: 158821)\n[PASS] testParallelForksCompleteInReverseOrderWithSeparateHeads() (gas: 1563409)\n[PASS] testPendingOriginCanBeForkedWithoutFakeSettlement() (gas: 668286)\n[PASS] testPriceChangeCannotExceedFunderLimit() (gas: 268420)\n[PASS] testReferenceForkNeedsNoRecorderOrFiles() (gas: 308827)\n[PASS] testRefusalUnlocksBranchWithoutCheckpoint() (gas: 721837)\n[PASS] testSecondPaidRequestOnSameBranchRejectedBeforeSpending() (gas: 190512)\n[PASS] testSnapshotCapDoesNotRestrictReferenceFork() (gas: 354883)\n[PASS] testSnapshotForkVerifiesExactResultBytes() (gas: 399193)\n[PASS] testStaleDraftCannotFundAfterHeadAdvances() (gas: 363683)\n[PASS] testSubsequentCallbackFitsStipendAndPinsParent() (gas: 760715)\n[PASS] testTokenCannotReenterBranchMutation() (gas: 833477)\n[PASS] testUnknownAnchorAndUnknownBranchRejected() (gas: 74322)\n[PASS] testUnknownAndForeignCallbacksRejected() (gas: 103012)\n[PASS] testZeroAndDuplicateRequestIdsRollBack() (gas: 667582)\n[PASS] testZeroInitialOwnerRejected() (gas: 8240)\nSuite result: ok. 50 passed; 0 failed; 0 skipped; finished in 81.51ms (96.90ms CPU time)\n\nRan 1 test for test/StepsBranchInvariant.t.sol:StepsBranchInvariantTest\n[PASS]\nStepsBranchInvariantTest invariants:\n[PASS] invariant_authorityIsOnlyTheExplicitOwner\n[PASS] invariant_callbacksAreAuthenticatedAndNeverLost\n[PASS] invariant_finishedRecordsNeverChange\n[PASS] invariant_paymentIsForwardedExactlyAndNothingIsHeld\n[PASS] invariant_pendingSlotsAndCheckpointsAreConsistent\n StepsBranchInvariantTest invariants (runs: 256, calls: 25600, reverts: 0)\n\n╭--------------+-------------------+-------+---------+----------╮\n| Contract     | Selector          | Calls | Reverts | Discards |\n+===============================================================+\n| StepsHandler | badIntakeCallback | 2155  | 0       | 0        |\n|--------------+-------------------+-------+---------+----------|\n| StepsHandler | deliverFailure    | 2089  | 0       | 0        |\n|--------------+-------------------+-------+---------+----------|\n| StepsHandler | deliverSuccess    | 2160  | 0       | 0        |\n|--------------+-------------------+-------+---------+----------|\n| StepsHandler | expire            | 2171  | 0       | 0        |\n|--------------+-------------------+-------+---------+----------|\n| StepsHandler | foreignCallback   | 2159  | 0       | 0        |\n|--------------+-------------------+-------+---------+----------|\n| StepsHandler | fork              | 2128  | 0       | 0        |\n|--------------+-------------------+-------+---------+----------|\n| StepsHandler | forkFromOrigin    | 2155  | 0       | 0        |\n|--------------+-------------------+-------+---------+----------|\n| StepsHandler | forkWithSnapshot  | 2145  | 0       | 0        |\n|--------------+-------------------+-------+---------+----------|\n| StepsHandler | fund              | 2053  | 0       | 0        |\n|--------------+-------------------+-------+---------+----------|\n| StepsHandler | propose           | 2181  | 0       | 0        |\n|--------------+-------------------+-------+---------+----------|\n| StepsHandler | setPrice          | 2169  | 0       | 0        |\n|--------------+-------------------+-------+---------+----------|\n| StepsHandler | warp              | 2035  | 0       | 0        |\n╰--------------+-------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 8.78s (8.77s CPU time)\n\nRan 5 test suites in 8.80s (9.00s CPU time): 82 tests passed, 0 failed, 0 skipped (82 total tests)\n","passed":true},{"durationMs":52,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"MockToken.approve(address,uint256)\",\"MockToken.mint(address,uint256)\",\"MockToken.transferFrom(address,address,uint256)\",\"StepsBranch.acceptOwner()\",\"StepsBranch.expirePending(uint256)\",\"StepsBranch.fork(uint256)\",\"StepsBranch.forkFromOrigin()\",\"StepsBranch.forkWithSnapshot(uint256,bytes)\",\"StepsBranch.fundWithPayment(uint256,bytes,uint256)\",\"StepsBranch.onFailure(bytes32,uint8,bytes32,uint16,uint16,bytes)\",\"StepsBranch.onResult(bytes32,bytes32,string)\",\"StepsBranch.propose(uint64,bytes32,bytes)\",\"StepsBranch.setPrice(uint256)\",\"StepsBranch.transferOwner(address)\"],\"files\":{\".gitignore\":4,\"ADAPTATION.md\":98,\"README.md\":40,\"deployment/StepsBranch.abi.json\":1236,\"deployment/StepsBranch.artifact.json\":2633,\"evidence/LOCAL-INTEGRATION-REPRODUCED.json\":159,\"evidence/LOCAL-REPRODUCTION.json\":67,\"evidence/LOCAL-VALIDATION.json\":23,\"foundry.toml\":8,\"launch.json\":15,\"sdk/local-integration.mjs\":139,\"sdk/package.json\":12,\"sdk/steps-branch-v2.abi.json\":1236,\"sdk/steps-branch-v2.abi.mjs\":1237,\"sdk/steps-branch-v2.mjs\":175,\"sdk/steps-branch-v2.test.mjs\":112,\"src/MockToken.sol\":26,\"src/StepsBranch.sol\":375,\"test/AuditFindings.t.sol\":117,\"test/StepsBranch.t.sol\":439,\"test/StepsBranchEdges.t.sol\":485,\"test/StepsBranchInvariant.t.sol\":522,\"test/Supplied.t.sol\":186},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"89517671e98318c406e2f4c5199df97ea334d57ce6fba192c30ac27074ceaa21","verifiedTreeHash":"76a2cf3ea0c8eb625e1664e7cfc16bb3d6c5fbd0","verifierVersion":"0.1.0+1d98f47e"},{"checks":[{"durationMs":1779,"exitCode":0,"name":"build","output":"Compiling 5 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.73s\nCompiler run successful!\n","passed":true},{"durationMs":187,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/AuditFindings.t.sol:AuditFindingsTest\n[PASS] testAuditFindingHeaviestFirstCheckpointFitsStipend() (gas: 1205922)\n[PASS] testAuditFindingSharedBranchCanBeTakenWithoutVictimLoss() (gas: 1306341)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 2.05ms (2.31ms CPU time)\n\nRan 7 tests for test/Supplied.t.sol:ReviewTest\n[PASS] testConflictClearsLock() (gas: 784789)\n[PASS] testDeliveryRejected() (gas: 1245279)\n[PASS] testHappyPathAndCap() (gas: 647877)\n[PASS] testLongUriAnnounced() (gas: 1963523)\n[PASS] testSilentProposalExpires() (gas: 812205)\n[PASS] testSnapshotIsPersisted() (gas: 693219)\n[PASS] testZeroResultHashRejected() (gas: 438345)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 59.29ms (7.82ms CPU time)\n\nRan 50 tests for test/StepsBranch.t.sol:StepsBranchTest\n[PASS] testBodyCannotChangeAtFunding() (gas: 177788)\n[PASS] testBodySizeBounds() (gas: 149015)\n[PASS] testConstructorRehearsesWithStaticMainnetBindingsOnEmptyChain() (gas: 18512)\n[PASS] testDeadlineAloneDoesNotExpireOrDiscardSuccess() (gas: 258694)\n[PASS] testDuplicateOrConflictingLateOutcomesCannotOverwrite() (gas: 345236)\n[PASS] testDuplicateSuccessAndLateFailureRejected() (gas: 298539)\n[PASS] testExpiredOldParentCannotAffectIndependentFork() (gas: 1434081)\n[PASS] testExpiryBoundaryAndPermissionlessRelease() (gas: 274845)\n[PASS] testExpiryRejectsUnknownDraftAndSettled() (gas: 412406)\n[PASS] testFactoryDeploymentAssignsExplicitOwner() (gas: 321394)\n[PASS] testFalseApprovalRollsBackTokenTransfer() (gas: 537049)\n[PASS] testFalseTokenReturnDoesNotCreatePaidState() (gas: 420221)\n[PASS] testFirstRealisticUriCallbackFitsStipend() (gas: 282525)\n[PASS] testForkDoesNotMoveSourceHeadOrCreateSuccessfulCheckpoint() (gas: 895723)\n[PASS] testFuzzSiblingCompletionOrder(uint8,uint256) (runs: 256, μ: 2484949, ~: 2122757)\n[PASS] testHistoricalForkKeepsSourceRevision() (gas: 1382536)\n[PASS] testHttpFallbackUriFitsStipend() (gas: 213642)\n[PASS] testIntakeRejectionRollsBackPaymentAndBranchReservation() (gas: 432412)\n[PASS] testInvalidAndUnauthorizedLateCallbacksLeaveArchiveEmpty() (gas: 390348)\n[PASS] testInvalidFailureStatusCannotSettle() (gas: 92368)\n[PASS] testLargeLateUriOmittedButCommittedUnderStipend() (gas: 370345)\n[PASS] testLargeUriIsCommittedWithoutUnboundedEvent() (gas: 329523)\n[PASS] testLateFailureCannotBeReplacedBySuccess() (gas: 287137)\n[PASS] testLateFailureCannotClearNewPendingOrOverwriteReason() (gas: 785249)\n[PASS] testLateRefusalArchivedWithoutChangingExpiredStatus() (gas: 245294)\n[PASS] testLateSuccessAfterFailureCannotMoveHead() (gas: 721207)\n[PASS] testLateSuccessArchivedWithoutCheckpointOrHeadChange() (gas: 296044)\n[PASS] testLateSuccessCannotChangeAdvancedHeadOrAnotherPending() (gas: 1350272)\n[PASS] testLateSuccessCannotClearNewPendingRequest() (gas: 894259)\n[PASS] testMaxEventUriFitsStipend() (gas: 573982)\n[PASS] testMaximumLateEventUriFitsStipend() (gas: 289747)\n[PASS] testMaximumSnapshotIsPersistedAndReferenceRemainsEmpty() (gas: 1081798)\n[PASS] testNoResultUnlocksBranchWithoutChangingHead() (gas: 703945)\n[PASS] testOfficialTupleAbiAndNestedPayment() (gas: 48828)\n[PASS] testOwnershipUsesTwoStepsAndCannotChangeLineage() (gas: 158821)\n[PASS] testParallelForksCompleteInReverseOrderWithSeparateHeads() (gas: 1563409)\n[PASS] testPendingOriginCanBeForkedWithoutFakeSettlement() (gas: 668286)\n[PASS] testPriceChangeCannotExceedFunderLimit() (gas: 268420)\n[PASS] testReferenceForkNeedsNoRecorderOrFiles() (gas: 308827)\n[PASS] testRefusalUnlocksBranchWithoutCheckpoint() (gas: 721837)\n[PASS] testSecondPaidRequestOnSameBranchRejectedBeforeSpending() (gas: 190512)\n[PASS] testSnapshotCapDoesNotRestrictReferenceFork() (gas: 354883)\n[PASS] testSnapshotForkVerifiesExactResultBytes() (gas: 399193)\n[PASS] testStaleDraftCannotFundAfterHeadAdvances() (gas: 363683)\n[PASS] testSubsequentCallbackFitsStipendAndPinsParent() (gas: 760715)\n[PASS] testTokenCannotReenterBranchMutation() (gas: 833477)\n[PASS] testUnknownAnchorAndUnknownBranchRejected() (gas: 74322)\n[PASS] testUnknownAndForeignCallbacksRejected() (gas: 103012)\n[PASS] testZeroAndDuplicateRequestIdsRollBack() (gas: 667582)\n[PASS] testZeroInitialOwnerRejected() (gas: 8240)\nSuite result: ok. 50 passed; 0 failed; 0 skipped; finished in 59.50ms (81.25ms CPU time)\n\nRan 3 test suites in 60.20ms (120.84ms CPU time): 59 tests passed, 0 failed, 0 skipped (59 total tests)\n","passed":true},{"durationMs":33,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"MockToken.approve(address,uint256)\",\"MockToken.mint(address,uint256)\",\"MockToken.transferFrom(address,address,uint256)\",\"StepsBranch.acceptOwner()\",\"StepsBranch.expirePending(uint256)\",\"StepsBranch.fork(uint256)\",\"StepsBranch.forkFromOrigin()\",\"StepsBranch.forkWithSnapshot(uint256,bytes)\",\"StepsBranch.fundWithPayment(uint256,bytes,uint256)\",\"StepsBranch.onFailure(bytes32,uint8,bytes32,uint16,uint16,bytes)\",\"StepsBranch.onResult(bytes32,bytes32,string)\",\"StepsBranch.propose(uint64,bytes32,bytes)\",\"StepsBranch.setPrice(uint256)\",\"StepsBranch.transferOwner(address)\"],\"files\":{\".gitignore\":4,\"ADAPTATION.md\":98,\"README.md\":40,\"deployment/StepsBranch.abi.json\":1236,\"deployment/StepsBranch.artifact.json\":2633,\"evidence/LOCAL-INTEGRATION-REPRODUCED.json\":159,\"evidence/LOCAL-REPRODUCTION.json\":67,\"evidence/LOCAL-VALIDATION.json\":23,\"foundry.toml\":8,\"launch.json\":15,\"sdk/local-integration.mjs\":139,\"sdk/package.json\":12,\"sdk/steps-branch-v2.abi.json\":1236,\"sdk/steps-branch-v2.abi.mjs\":1237,\"sdk/steps-branch-v2.mjs\":175,\"sdk/steps-branch-v2.test.mjs\":112,\"src/MockToken.sol\":26,\"src/StepsBranch.sol\":375,\"test/AuditFindings.t.sol\":117,\"test/StepsBranch.t.sol\":439,\"test/Supplied.t.sol\":186},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"94cb9eee55eac8d7add7260abe1975cbe27c2833004954ae2062794f9950c850","verifiedTreeHash":"5a9e0efeb26d1bc7305ad271fdba017dbc9e573b","verifierVersion":"0.1.0+1d98f47e"},{"checks":[{"durationMs":1825,"exitCode":0,"name":"build","output":"Compiling 5 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.77s\nCompiler run successful!\n","passed":true},{"durationMs":142,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/AuditFindings.t.sol:AuditFindingsTest\n[PASS] testAuditFindingHeaviestFirstCheckpointFitsStipend() (gas: 1205922)\n[PASS] testAuditFindingSharedBranchCanBeTakenWithoutVictimLoss() (gas: 1306341)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 2.11ms (2.36ms CPU time)\n\nRan 7 tests for test/Supplied.t.sol:ReviewTest\n[PASS] testConflictClearsLock() (gas: 784789)\n[PASS] testDeliveryRejected() (gas: 1245279)\n[PASS] testHappyPathAndCap() (gas: 647877)\n[PASS] testLongUriAnnounced() (gas: 1963523)\n[PASS] testSilentProposalExpires() (gas: 812205)\n[PASS] testSnapshotIsPersisted() (gas: 693219)\n[PASS] testZeroResultHashRejected() (gas: 438345)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 51.94ms (7.21ms CPU time)\n\nRan 50 tests for test/StepsBranch.t.sol:StepsBranchTest\n[PASS] testBodyCannotChangeAtFunding() (gas: 177788)\n[PASS] testBodySizeBounds() (gas: 149015)\n[PASS] testConstructorRehearsesWithStaticMainnetBindingsOnEmptyChain() (gas: 18512)\n[PASS] testDeadlineAloneDoesNotExpireOrDiscardSuccess() (gas: 258694)\n[PASS] testDuplicateOrConflictingLateOutcomesCannotOverwrite() (gas: 345236)\n[PASS] testDuplicateSuccessAndLateFailureRejected() (gas: 298539)\n[PASS] testExpiredOldParentCannotAffectIndependentFork() (gas: 1434081)\n[PASS] testExpiryBoundaryAndPermissionlessRelease() (gas: 274845)\n[PASS] testExpiryRejectsUnknownDraftAndSettled() (gas: 412406)\n[PASS] testFactoryDeploymentAssignsExplicitOwner() (gas: 321394)\n[PASS] testFalseApprovalRollsBackTokenTransfer() (gas: 537049)\n[PASS] testFalseTokenReturnDoesNotCreatePaidState() (gas: 420221)\n[PASS] testFirstRealisticUriCallbackFitsStipend() (gas: 282525)\n[PASS] testForkDoesNotMoveSourceHeadOrCreateSuccessfulCheckpoint() (gas: 895723)\n[PASS] testFuzzSiblingCompletionOrder(uint8,uint256) (runs: 256, μ: 2484949, ~: 2122757)\n[PASS] testHistoricalForkKeepsSourceRevision() (gas: 1382536)\n[PASS] testHttpFallbackUriFitsStipend() (gas: 213642)\n[PASS] testIntakeRejectionRollsBackPaymentAndBranchReservation() (gas: 432412)\n[PASS] testInvalidAndUnauthorizedLateCallbacksLeaveArchiveEmpty() (gas: 390348)\n[PASS] testInvalidFailureStatusCannotSettle() (gas: 92368)\n[PASS] testLargeLateUriOmittedButCommittedUnderStipend() (gas: 370345)\n[PASS] testLargeUriIsCommittedWithoutUnboundedEvent() (gas: 329523)\n[PASS] testLateFailureCannotBeReplacedBySuccess() (gas: 287137)\n[PASS] testLateFailureCannotClearNewPendingOrOverwriteReason() (gas: 785249)\n[PASS] testLateRefusalArchivedWithoutChangingExpiredStatus() (gas: 245294)\n[PASS] testLateSuccessAfterFailureCannotMoveHead() (gas: 721207)\n[PASS] testLateSuccessArchivedWithoutCheckpointOrHeadChange() (gas: 296044)\n[PASS] testLateSuccessCannotChangeAdvancedHeadOrAnotherPending() (gas: 1350272)\n[PASS] testLateSuccessCannotClearNewPendingRequest() (gas: 894259)\n[PASS] testMaxEventUriFitsStipend() (gas: 573982)\n[PASS] testMaximumLateEventUriFitsStipend() (gas: 289747)\n[PASS] testMaximumSnapshotIsPersistedAndReferenceRemainsEmpty() (gas: 1081798)\n[PASS] testNoResultUnlocksBranchWithoutChangingHead() (gas: 703945)\n[PASS] testOfficialTupleAbiAndNestedPayment() (gas: 48828)\n[PASS] testOwnershipUsesTwoStepsAndCannotChangeLineage() (gas: 158821)\n[PASS] testParallelForksCompleteInReverseOrderWithSeparateHeads() (gas: 1563409)\n[PASS] testPendingOriginCanBeForkedWithoutFakeSettlement() (gas: 668286)\n[PASS] testPriceChangeCannotExceedFunderLimit() (gas: 268420)\n[PASS] testReferenceForkNeedsNoRecorderOrFiles() (gas: 308827)\n[PASS] testRefusalUnlocksBranchWithoutCheckpoint() (gas: 721837)\n[PASS] testSecondPaidRequestOnSameBranchRejectedBeforeSpending() (gas: 190512)\n[PASS] testSnapshotCapDoesNotRestrictReferenceFork() (gas: 354883)\n[PASS] testSnapshotForkVerifiesExactResultBytes() (gas: 399193)\n[PASS] testStaleDraftCannotFundAfterHeadAdvances() (gas: 363683)\n[PASS] testSubsequentCallbackFitsStipendAndPinsParent() (gas: 760715)\n[PASS] testTokenCannotReenterBranchMutation() (gas: 833477)\n[PASS] testUnknownAnchorAndUnknownBranchRejected() (gas: 74322)\n[PASS] testUnknownAndForeignCallbacksRejected() (gas: 103012)\n[PASS] testZeroAndDuplicateRequestIdsRollBack() (gas: 667582)\n[PASS] testZeroInitialOwnerRejected() (gas: 8240)\nSuite result: ok. 50 passed; 0 failed; 0 skipped; finished in 60.54ms (81.95ms CPU time)\n\nRan 3 test suites in 61.23ms (114.58ms CPU time): 59 tests passed, 0 failed, 0 skipped (59 total tests)\n","passed":true},{"durationMs":36,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"MockToken.approve(address,uint256)\",\"MockToken.mint(address,uint256)\",\"MockToken.transferFrom(address,address,uint256)\",\"StepsBranch.acceptOwner()\",\"StepsBranch.expirePending(uint256)\",\"StepsBranch.fork(uint256)\",\"StepsBranch.forkFromOrigin()\",\"StepsBranch.forkWithSnapshot(uint256,bytes)\",\"StepsBranch.fundWithPayment(uint256,bytes,uint256)\",\"StepsBranch.onFailure(bytes32,uint8,bytes32,uint16,uint16,bytes)\",\"StepsBranch.onResult(bytes32,bytes32,string)\",\"StepsBranch.propose(uint64,bytes32,bytes)\",\"StepsBranch.setPrice(uint256)\",\"StepsBranch.transferOwner(address)\"],\"files\":{\".gitignore\":4,\"ADAPTATION.md\":98,\"README.md\":40,\"deployment/StepsBranch.abi.json\":1236,\"deployment/StepsBranch.artifact.json\":2633,\"evidence/LOCAL-INTEGRATION-REPRODUCED.json\":159,\"evidence/LOCAL-REPRODUCTION.json\":67,\"evidence/LOCAL-VALIDATION.json\":23,\"foundry.toml\":8,\"launch.json\":15,\"sdk/local-integration.mjs\":139,\"sdk/package.json\":12,\"sdk/steps-branch-v2.abi.json\":1236,\"sdk/steps-branch-v2.abi.mjs\":1237,\"sdk/steps-branch-v2.mjs\":175,\"sdk/steps-branch-v2.test.mjs\":112,\"src/MockToken.sol\":26,\"src/StepsBranch.sol\":375,\"test/AuditFindings.t.sol\":117,\"test/StepsBranch.t.sol\":439,\"test/Supplied.t.sol\":186},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"9c28128d6d7c001b219788e1e6e3cc73c93abc989d9fb279840b5d2bc80edb73","verifiedTreeHash":"2d8ad63f4b251888dc322dda11d2b755f19e6915","verifierVersion":"0.1.0+1d98f47e"}]}