Favorites and saved filters are exposed as projects under negative ids, but they
aggregate tasks that live in real projects and have no row of their own. Only
CanWrite knew that, and only for favorites, so every other permission method fell
through to GetProjectSimpleByID and errored on an id it can never resolve:
- IsAdmin ignored saved filters, so CanDelete - which delegates to it - answered
"project does not exist" for a pseudo id instead of denying.
- CanUpdate delegated saved filters to the filter's own check, but only after the
instance admin bypass, so an admin never reached the owner check.
- checkReadPermissionsForProjects took its admin branch ahead of the pseudo cases
and then found no row, so CanRead errored for instance admins.
- CanDelete short-circuited on instance admin ahead of IsAdmin, reintroducing the
same bypass.
Every one of them now returns a verdict rather than an error, and instance admins
get a real answer instead of a blanket yes. IsPseudoProjectID gives callers a name
for the id encoding they used to open-code.
CanCreate has the same shape for a negative parent_project_id, but changing it
would deny something that works today, so it is left alone.