From 9f29df4cc3a992efc7ac80e393e09bee498c4029 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Tue, 26 May 2026 17:31:27 +0200 Subject: [PATCH] fix(sentinel): accept empty container heartbeats Allow Sentinel pushes with an empty containers array so servers with no running containers still refresh their heartbeat and enqueue an update. --- app/Http/Controllers/Api/SentinelController.php | 2 +- tests/Feature/SentinelPushDeduplicationTest.php | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Api/SentinelController.php b/app/Http/Controllers/Api/SentinelController.php index ca47e9f9d..f76c58c0f 100644 --- a/app/Http/Controllers/Api/SentinelController.php +++ b/app/Http/Controllers/Api/SentinelController.php @@ -79,7 +79,7 @@ public function push(Request $request) return response()->json(['message' => 'Unauthorized'], 401); } $validator = Validator::make($request->all(), [ - 'containers' => ['required', 'array', 'min:1'], + 'containers' => ['present', 'array'], ]); if ($validator->fails()) { diff --git a/tests/Feature/SentinelPushDeduplicationTest.php b/tests/Feature/SentinelPushDeduplicationTest.php index 9d20851ed..b61e9933c 100644 --- a/tests/Feature/SentinelPushDeduplicationTest.php +++ b/tests/Feature/SentinelPushDeduplicationTest.php @@ -71,6 +71,15 @@ function sentinelPayload(array $containers, ?float $diskPercentage = 42.0): arra expect(Carbon::parse($this->server->fresh()->sentinel_updated_at)->diffInSeconds(now()))->toBeLessThan(5); }); +it('accepts an empty container list as a heartbeat when no containers are running', function () { + $this->server->update(['sentinel_updated_at' => now()->subHour()]); + + pushSentinel($this->token, sentinelPayload([]))->assertOk(); + + Queue::assertPushed(PushServerUpdateJob::class, 1); + expect(Carbon::parse($this->server->fresh()->sentinel_updated_at)->diffInSeconds(now()))->toBeLessThan(5); +}); + it('rejects malformed sentinel payloads before touching server state', function (array $payload) { $this->server->update(['sentinel_updated_at' => now()->subHour()]); $originalHeartbeat = $this->server->fresh()->sentinel_updated_at; @@ -87,7 +96,6 @@ function sentinelPayload(array $containers, ?float $diskPercentage = 42.0): arra })->with([ 'missing containers' => [[]], 'non-array containers' => [['containers' => 'not-an-array']], - 'empty containers' => [['containers' => []]], ]); it('guards the dedupe decision with a server scoped atomic cache lock', function () {