healthz: depend on Publisher to force bundle deep-load (perfsmoke gap)

v0.1.0 shipped two bugs that left /healthz returning 200 against a
half-loaded bundle: the path-repo symlink dangling at runtime in the
AppImage (vendor/php-qml/bridge → nonexistent), and the writable
cache-dir bug (Symfony couldn't create var/cache/prod). HealthController
returned a static {status:"ok"} without ever touching any BridgeBundle
service, so perfsmoke + the connection-state probe both passed even
when the bundle's autoload was broken — first sign of trouble was a
500 from /api/todos under real load.

Inject Publisher (the bundle's Mercure-publish wrapper) via constructor
and reference its FQN in the response body. Two effects:

  - Symfony's container resolves Publisher when the controller is
    instantiated; if the bundle's autoload is broken, the controller
    can't even construct, /healthz returns 500.
  - The response now includes `bundle: "PhpQml\Bridge\Publisher"` —
    proves to perfsmoke + dev console that the canary is live, not a
    cached static response.

Connection-state probe semantics unchanged: still 200 = Online,
non-200 = Reconnecting/Offline. Probe interval is 5s — Publisher's
construction is constant-time, no perf concern.

No new public API: /healthz response gained a `bundle` field
(additive, JSON parsers ignore unknown keys); 200 vs 500 boundary is
preserved. No existing consumer broken.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-03 13:19:58 +02:00
parent 3c027255c8
commit 7e734fec66

View File

@@ -4,18 +4,33 @@ declare(strict_types=1);
namespace PhpQml\Bridge\Controller; namespace PhpQml\Bridge\Controller;
use PhpQml\Bridge\Publisher;
use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Attribute\Route;
/** /**
* Readiness probe used by the Qt host to detect when the backend is up. * Readiness probe used by the Qt host to detect when the backend is up.
* See PLAN.md §3 (*Startup*, step 4). * See PLAN.md §3 (*Startup*, step 4).
*
* Publisher is injected purely as a deep-health canary: if the bridge
* bundle's autoload or container wiring is broken (e.g. a packaging build
* with a dangling vendor path-repo symlink), this controller can't even
* be constructed, so /healthz fails 500 instead of misleadingly returning
* 200 against a half-loaded bundle.
*/ */
final class HealthController final class HealthController
{ {
public function __construct(
private readonly Publisher $publisher,
) {
}
#[Route('/healthz', name: 'php_qml_bridge_healthz', methods: ['GET'])] #[Route('/healthz', name: 'php_qml_bridge_healthz', methods: ['GET'])]
public function __invoke(): JsonResponse public function __invoke(): JsonResponse
{ {
return new JsonResponse(['status' => 'ok']); return new JsonResponse([
'status' => 'ok',
'bundle' => $this->publisher::class,
]);
} }
} }