It's easier to understand if you post the plan.
I'm surprised that the subquery change didn't help since the subquery will get stored in a temp table, and the sequence scan over the temp table which calls permission_p shouldn't be that expensive since the temp table should only hold one row. Possibly the planner is doing something funny.
Just for grins try moving the permission_p(... 'read') call into the subqueries target list and alias it to read_p. Then modify the outer query where clause to test on read_p:
select * from (...) version where version.read_p = 't';