feat(http): complete the kernel with a Response pipeline and terminate()
Stage 3b of the HTTP kernel migration.
MiddlewareInterface::handle() now returns a Response instead of mixed, across
all 15 implementations. To make that true at runtime and not just in the
signature, normalization moved inside the pipeline: the handler closure passed
to MiddlewareDispatcher::dispatch() calls ResponseNormalizer::normalize()
itself, and the dispatcher is typed on Response throughout. The transitional
controller contract (Response|string|null) is unchanged, it is just normalized
one step earlier.
The legacy status seam is gone with it. handleRaw() no longer reads
http_response_code() to build the status, and handle() no longer resets it to
200: after stage 2c no controller sets the status that way, the only remaining
callers being GlobalErrorHandler and the pre-kernel BanIP.
Kernel implements TerminableInterface. UserStat and the mail queue moved into
terminate(): both are post-response side effects the response does not depend
on. The mail queue condition is kept 1:1, including the isSuccessful() check.
public/index.php is down to handle() -> send() -> terminate().
send() is now the full one, with fastcgi_finish_request(). That is only safe
because handle() closes the session once the response is built (guarded by
PHP_SESSION_ACTIVE and non-console mode): PHP otherwise holds the session file
locked until shutdown, so the next request from the same visitor would queue
behind the mail batch running in terminate().
Refs: .claude/http-kernel-migration-plan.md stage 3b