Skip to content

Commit 2f0eac5

Browse files
committed
Publish embedded role-topology diagnostics for workflow doctor
Publish embedded role topology in doctor output
1 parent b1fc3bd commit 2f0eac5

5 files changed

Lines changed: 669 additions & 3 deletions

File tree

‎docs/architecture/control-plane-split.md‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -590,9 +590,14 @@ are degraded without reading model tables directly:
590590
- The standalone server exposes
591591
`GET /api/cluster/info`, backed by
592592
`App\Support\ServerTopology`, so operators can read the current
593-
role split (`current_shape`, `current_roles`, `matching_role`,
594-
and `shape_assignments`) without inferring it from deployment
595-
notes.
593+
role split (`current_shape`, `current_process_class`,
594+
`current_roles`, `matching_role`, and `shape_assignments`)
595+
without inferring it from deployment notes.
596+
- Embedded or package-local installs expose the same
597+
`durable-workflow.v2.role-topology` manifest through
598+
`php artisan workflow:v2:doctor --json` under the `topology`
599+
key, so operators can read the current application-process
600+
role bundle without standing up the standalone server first.
596601
- `Workflow\V2\Support\OperatorMetrics::snapshot()` reports per-role
597602
health signals: `tasks.ready`, `tasks.leased`,
598603
`tasks.claim_failed`, `repair.missing_task_candidates`,

‎src/Commands/V2DoctorCommand.php‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Symfony\Component\Console\Attribute\AsCommand;
1010
use Workflow\V2\Support\BackendCapabilities;
1111
use Workflow\V2\Support\MatchingRoleSnapshot;
12+
use Workflow\V2\Support\RoleTopologySnapshot;
1213

1314
#[AsCommand(name: 'workflow:v2:doctor')]
1415
class V2DoctorCommand extends Command
@@ -23,6 +24,7 @@ public function handle(): int
2324
{
2425
$snapshot = BackendCapabilities::snapshot();
2526
$snapshot['matching_role'] = MatchingRoleSnapshot::current();
27+
$snapshot['topology'] = RoleTopologySnapshot::current();
2628

2729
if ((bool) $this->option('json')) {
2830
try {
@@ -54,6 +56,7 @@ private function renderHumanSnapshot(array $snapshot): void
5456
$this->componentLine('queue', $snapshot['queue'] ?? []);
5557
$this->componentLine('cache', $snapshot['cache'] ?? []);
5658
$this->componentLine('codec', $snapshot['codec'] ?? []);
59+
$this->topologyLine($snapshot['topology'] ?? null);
5760
$this->matchingRoleLine($snapshot['matching_role'] ?? null);
5861

5962
$issues = $snapshot['issues'] ?? [];
@@ -132,4 +135,33 @@ private function matchingRoleLine(mixed $matchingRole): void
132135
$dispatchMode,
133136
));
134137
}
138+
139+
private function topologyLine(mixed $topology): void
140+
{
141+
if (! is_array($topology)) {
142+
$this->line('[INFO] topology: unavailable');
143+
144+
return;
145+
}
146+
147+
$shape = is_string($topology['current_shape'] ?? null) ? $topology['current_shape'] : 'unknown';
148+
$processClass = is_string($topology['current_process_class'] ?? null)
149+
? $topology['current_process_class']
150+
: 'unknown';
151+
$executionMode = is_string($topology['execution_mode'] ?? null)
152+
? $topology['execution_mode']
153+
: 'unknown';
154+
$roles = array_values(array_filter(
155+
is_array($topology['current_roles'] ?? null) ? $topology['current_roles'] : [],
156+
static fn (mixed $role): bool => is_string($role) && $role !== '',
157+
));
158+
159+
$this->line(sprintf(
160+
'[INFO] topology: %s/%s (execution_mode=%s, roles=%s)',
161+
$shape,
162+
$processClass,
163+
$executionMode,
164+
$roles === [] ? 'none' : implode(',', $roles),
165+
));
166+
}
135167
}

0 commit comments

Comments
 (0)