std::is_constant_evaluated() returns true if and only if [meta.const.eval]:
evaluation of the call occurs within the evaluation of an expression or conversion that is manifestly constant-evaluated
This term, "manifestly constant-evaluated" (defined here), refers to contexts that have to be constant-evaluated. A call to a non-constexpr function (the nearest enclosing context here) is never constant evaluated, because it's non-constexpr, so this is straight-forwardly not "manifestly constant-evaluated."
Once we make it constexpr though, we're in this weird legacy quirk. Before C++11, which introduced constexpr, we could still do stuff like this:
template <int I> void f();
const int i = 42; // const, not constexpr
f<i>(); // ok
Basically, we have this carve out for specifically integral (and enumeration) types declared const that are initialized with a constant expression. Those still count as constant expressions.
So this:
const auto b = foo(0);
If foo(0) is an integral constant expression, then b is something that could be used as a compile time constant (and would be constant-initialized†, if it were at namespace scope). So what happens here is we do a two-step parse. We first try to evaluate foo(0) as if it were a constant expression and then, if that fails, fall back to not doing that.
In this first parse, with foo(0) evaluated as a constant, is_constant_evaluated() is (by definition) true, so we get 1. This parse succeeds, so we end up with b as a compile-time constant.
†For namespace-scope variables, constant-initialization is an important concept as well - to avoid the static initialization order fiasco. It leads to other gnarly examples (see P0595).
The important thing here is basically: is_constant_evaluated() should only be switched on to select a compile-time-safe algorithm vs a runtime algorithm, not to actually affect the semantics of the result.