Skip to content

Commit c577621

Browse files
Surface matching-role shape in v2 doctor
Surface matching-role shape in v2 doctor
1 parent 9430465 commit c577621

4 files changed

Lines changed: 93 additions & 30 deletions

File tree

src/Commands/V2DoctorCommand.php

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use JsonException;
99
use Symfony\Component\Console\Attribute\AsCommand;
1010
use Workflow\V2\Support\BackendCapabilities;
11+
use Workflow\V2\Support\MatchingRoleSnapshot;
1112

1213
#[AsCommand(name: 'workflow:v2:doctor')]
1314
class V2DoctorCommand extends Command
@@ -16,11 +17,12 @@ class V2DoctorCommand extends Command
1617
{--json : Output the capability snapshot as JSON}
1718
{--strict : Exit with failure when required capabilities are missing}';
1819

19-
protected $description = 'Inspect Workflow v2 backend capabilities for the configured database, queue, and cache stores';
20+
protected $description = 'Inspect Workflow v2 backend capabilities and the local matching-role deployment shape';
2021

2122
public function handle(): int
2223
{
2324
$snapshot = BackendCapabilities::snapshot();
25+
$snapshot['matching_role'] = MatchingRoleSnapshot::current();
2426

2527
if ((bool) $this->option('json')) {
2628
try {
@@ -52,6 +54,7 @@ private function renderHumanSnapshot(array $snapshot): void
5254
$this->componentLine('queue', $snapshot['queue'] ?? []);
5355
$this->componentLine('cache', $snapshot['cache'] ?? []);
5456
$this->componentLine('codec', $snapshot['codec'] ?? []);
57+
$this->matchingRoleLine($snapshot['matching_role'] ?? null);
5558

5659
$issues = $snapshot['issues'] ?? [];
5760

@@ -105,4 +108,26 @@ private function componentLine(string $name, mixed $component): void
105108

106109
$this->line(sprintf('[%s] %s: %s', $status, $name, $identity));
107110
}
111+
112+
private function matchingRoleLine(mixed $matchingRole): void
113+
{
114+
if (! is_array($matchingRole)) {
115+
$this->line('[INFO] matching_role: unavailable');
116+
117+
return;
118+
}
119+
120+
$shape = is_string($matchingRole['shape'] ?? null) ? $matchingRole['shape'] : 'unknown';
121+
$queueWakeEnabled = ($matchingRole['queue_wake_enabled'] ?? false) === true ? 'true' : 'false';
122+
$dispatchMode = is_string($matchingRole['task_dispatch_mode'] ?? null)
123+
? $matchingRole['task_dispatch_mode']
124+
: 'unknown';
125+
126+
$this->line(sprintf(
127+
'[INFO] matching_role: %s (queue_wake_enabled=%s, task_dispatch_mode=%s)',
128+
$shape,
129+
$queueWakeEnabled,
130+
$dispatchMode,
131+
));
132+
}
108133
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Workflow\V2\Support;
6+
7+
final class MatchingRoleSnapshot
8+
{
9+
/**
10+
* Process-local view of the matching-role deployment shape on this node.
11+
*
12+
* `queue_wake_enabled` reports `workflows.v2.matching_role.queue_wake_enabled`
13+
* exactly as the queue-worker Looping listener in WorkflowServiceProvider
14+
* consumes it. `shape` reports `in_worker` when the in-worker broad-poll
15+
* wake is active on this process and `dedicated` when the process has
16+
* opted out and the broad sweep is expected to run as
17+
* `php artisan workflow:v2:repair-pass` instead. `task_dispatch_mode`
18+
* reports the configured dispatch mode (`queue` or `poll`).
19+
*
20+
* @return array{queue_wake_enabled: bool, shape: string, task_dispatch_mode: string}
21+
*/
22+
public static function current(): array
23+
{
24+
$queueWakeEnabled = (bool) config('workflows.v2.matching_role.queue_wake_enabled', true);
25+
$dispatchModeConfig = config('workflows.v2.task_dispatch_mode', 'queue');
26+
$dispatchMode = is_string($dispatchModeConfig) && $dispatchModeConfig !== ''
27+
? $dispatchModeConfig
28+
: 'queue';
29+
30+
return [
31+
'queue_wake_enabled' => $queueWakeEnabled,
32+
'shape' => $queueWakeEnabled ? 'in_worker' : 'dedicated',
33+
'task_dispatch_mode' => $dispatchMode,
34+
];
35+
}
36+
}

src/V2/Support/OperatorMetrics.php

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -55,35 +55,7 @@ public static function snapshot(?CarbonInterface $now = null, ?string $namespace
5555
'structural_limits' => StructuralLimits::snapshot(),
5656
'update_wait' => UpdateWaitPolicy::snapshot(),
5757
'repair_policy' => TaskRepairPolicy::snapshot(),
58-
'matching_role' => self::matchingRoleSnapshot(),
59-
];
60-
}
61-
62-
/**
63-
* Process-local view of the matching-role deployment shape on this node.
64-
*
65-
* `queue_wake_enabled` reports `workflows.v2.matching_role.queue_wake_enabled`
66-
* exactly as the queue-worker Looping listener in WorkflowServiceProvider
67-
* consumes it. `shape` reports `in_worker` when the in-worker broad-poll
68-
* wake is active on this process and `dedicated` when the process has
69-
* opted out and the broad sweep is expected to run as
70-
* `php artisan workflow:v2:repair-pass` instead. `task_dispatch_mode`
71-
* reports the configured dispatch mode (`queue` or `poll`).
72-
*
73-
* @return array{queue_wake_enabled: bool, shape: string, task_dispatch_mode: string}
74-
*/
75-
private static function matchingRoleSnapshot(): array
76-
{
77-
$queueWakeEnabled = (bool) config('workflows.v2.matching_role.queue_wake_enabled', true);
78-
$dispatchModeConfig = config('workflows.v2.task_dispatch_mode', 'queue');
79-
$dispatchMode = is_string($dispatchModeConfig) && $dispatchModeConfig !== ''
80-
? $dispatchModeConfig
81-
: 'queue';
82-
83-
return [
84-
'queue_wake_enabled' => $queueWakeEnabled,
85-
'shape' => $queueWakeEnabled ? 'in_worker' : 'dedicated',
86-
'task_dispatch_mode' => $dispatchMode,
58+
'matching_role' => MatchingRoleSnapshot::current(),
8759
];
8860
}
8961

tests/Unit/Commands/V2DoctorCommandTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,36 @@ public function testJsonOutputSucceedsWithoutStrictMode(): void
3030
])->assertSuccessful();
3131
}
3232

33+
public function testJsonOutputIncludesTheMatchingRoleShape(): void
34+
{
35+
config()
36+
->set('workflows.v2.task_dispatch_mode', 'poll');
37+
config()
38+
->set('workflows.v2.matching_role.queue_wake_enabled', false);
39+
40+
$this->artisan('workflow:v2:doctor', [
41+
'--json' => true,
42+
])
43+
->expectsOutputToContain(
44+
'"matching_role":{"queue_wake_enabled":false,"shape":"dedicated","task_dispatch_mode":"poll"}'
45+
)
46+
->assertSuccessful();
47+
}
48+
49+
public function testHumanOutputIncludesTheMatchingRoleShape(): void
50+
{
51+
config()
52+
->set('workflows.v2.task_dispatch_mode', 'poll');
53+
config()
54+
->set('workflows.v2.matching_role.queue_wake_enabled', false);
55+
56+
$this->artisan('workflow:v2:doctor')
57+
->expectsOutputToContain(
58+
'[INFO] matching_role: dedicated (queue_wake_enabled=false, task_dispatch_mode=poll)'
59+
)
60+
->assertSuccessful();
61+
}
62+
3363
public function testPollModeSyncQueueIsReportedAsInfoNotWarning(): void
3464
{
3565
// TD-078: in poll mode the queue-driver diagnostic is informational,

0 commit comments

Comments
 (0)