MEDIUM severity: Manual/Instant `fulfillRedeemRequest` doesn’t reserve liquidity. **Description:** `AccountableAsyncRedeemVault` account for reserved liquidity only when processing the queue through (`AccountableWithdrawalQueue::processUpToShares` / `AccountableWithdrawalQueue::processUpToRequestId`).
However, the manual fulfillment paths (`fulfillRedeemRequest`) and the...

Vulnerable code:
sum(claimable assets across users)  >  vault.totalAssets() - reservedLiquidity

function test_manualFulfill_vsQueuedFulfill_mismatch() public {
    // Setup: price = 1e36, deposits for Alice & Bob
    _setupInitialDeposits(1e36, DEPOSIT_AMOUNT);

    uint256 aliceHalf = vault.balanceOf(alice) / 2;
    uint256 bobHalf   = vault.balanceOf(bob)   / 2;

    // === (A) Queue Bob first and reserve via processor ===
    vm.prank(bob);
    uint256 bobReqId = vault.requestRedeem(bobHalf, bob, bob);
    assertEq(bobReqId, 1, "Bob should be the head of the queue");

    // Processor path reserves liquidity for Bob
    uint256 price = strategy.sharePrice(address(vault)); // 1e36
    uint256 expectedBobAssets = (bobHalf * price) / 1e36;
    uint256 used = vault.processUpToShares(bobHalf);
    assertEq(used, expectedBobAssets, "queued fulfill reserves exact assets for Bob");

    // Sanity: reservedLiquidity == Bob's claimable assets
    uint256 reservedBefore = vault.reservedLiquidity();
    assertEq(reservedBefore, expectedBobAssets, "only Bob's queued path bumped reservedLiquidity");

    // === (B) Now manually fulfill Alice (no reservation bump) ===
    vm.prank(alice);
    uint256 aliceReqId = vault.requestRedeem(aliceHalf, alice, alice);
    assertEq(aliceReqId, 2, "Alice should be behind Bob in the queue");

    // Manual fulfill creates claimables but doesn't increase reservedLiquidity
    strategy.fulfillRedeemRequest(0, address(vault), alice, aliceHalf);

    // Compute claimables in assets
    uint256 aliceClaimableShares = vault.claimableRedeemRequest(0, alice);
    uint256 bobClaimableShares   = vault.claimableRedeemRequest(0, bob);
    assertEq(aliceClaimableShares, aliceHalf, "Alice claimable shares set by manual fulfill");
    assertEq(bobClaimableShares,   bobHalf,   "Bob claimable shares set by queued processor");

    uint256 aliceClaimableAssets = (aliceClaimableShares * price) / 1e36;
    uint256 bobClaimableAssets   = (bobClaimableShares   * price) / 1e36;
    uint256 totalClaimables      = aliceClaimableAssets + bobClaimableAssets;

    // Mismatch: claimables exceed reservedLiquidity because Alice's path didn't reserve
    assertGt(totalClaimables, reservedBefore, "claimables > reservedLiquidity (oversubscription)");

    // === (C) Bob withdraws his reserved claim → consumes all reservation ===
    uint256 bobMax = vault.maxWithdraw(bob);
    assertEq(bobMax, bobClaimableAssets, "Bob can withdraw exactly his reserved amount");

    uint256 vaultAssetsBefore = vault.totalAssets();
    vm.prank(bob);
    vault.withdraw(bobMax, bob, bob);

    // After paying Bob, reservation is zero, but Alice still has claimables (unreserved)
    uint256 reservedAfter = vault.reservedLiquidity();
    assertEq(reservedAfter, 0, "all reserved liquidity consumed by Bob's withdrawal");

    uint256 aliceClaimableShares2 = vault.claimableRedeemRequest(0, alice);
    uint256 aliceClaimableAssets2 = (aliceClaimableShares2 * price) / 1e36;
    assertEq(aliceClaimableShares2, aliceHalf, "Alice still has claimables (manual path)");
    assertGt(aliceClaimableAssets2, reservedAfter, "manual claimables remain with zero reservation");

    // Optional sanity: vault asset balance decreased by Bob's withdrawal only
    uint256 vaultAssetsAfter = vault.totalAssets();
    assertEq(vaultAssetsBefore - vaultAssetsAfter, bobMax, "vault paid only the reserved portion");
}