Skip to content

Commit b2c3755

Browse files
Publish server role topology through cluster discovery
1 parent 11d35b7 commit b2c3755

5 files changed

Lines changed: 118 additions & 1 deletion

File tree

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ workflow-task command payload.
493493
### System
494494
- `GET /api/health` — Health check
495495
- `GET /api/ready` — Readiness check for migrations, default namespace, cache, and auth config
496-
- `GET /api/cluster/info` — Server capabilities and version
496+
- `GET /api/cluster/info` — Server capabilities, role topology, and version
497497
- `GET /api/system/metrics` — Server metrics including bounded stuck workflow-task diagnostics
498498
- `GET /api/system/operator-metrics` — Full operator metrics snapshot (runs, tasks, backlog, repair, workers/fleet, backend, structural limits) for rollout-safety coordination health
499499
- `GET /api/system/repair` — Task repair diagnostics
@@ -652,6 +652,16 @@ Server-owned cache keys and metric label sets are governed by the bounded-growth
652652
policy in `config/dw-bounded-growth.php`; the human-readable inventory lives in
653653
`docs/bounded-growth.md`.
654654

655+
Cluster discovery also publishes a `topology` manifest. It freezes the server's
656+
role vocabulary (`api_ingress`, `control_plane`, `matching`,
657+
`history_projection`, `scheduler`, `execution_plane`), the product's supported
658+
deployment shapes (`embedded`, `standalone_server`,
659+
`split_control_execution`), the roles currently hosted by the HTTP node, and
660+
the current execution mode. `execution_mode` is `remote_worker_protocol` in the
661+
default service-mode deployment and switches to `local_queue_worker` when
662+
`DW_MODE=embedded` routes workflow and activity task execution through local
663+
Laravel queue workers.
664+
655665
The activity-grade external execution surface is published from
656666
`GET /api/cluster/info` at
657667
`worker_protocol.external_execution_surface_contract`. That manifest is the

app/Http/Controllers/Api/HealthController.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use App\Support\BridgeAdapterOutcomeContract;
88
use App\Support\ClientCompatibility;
99
use App\Support\ControlPlaneProtocol;
10+
use App\Support\ServerTopology;
1011
use App\Support\ServerReadiness;
1112
use App\Support\WorkerProtocol;
1213
use Illuminate\Http\JsonResponse;
@@ -117,6 +118,7 @@ public function clusterInfo(Request $request): JsonResponse
117118
'max_pending_children' => (int) config('server.limits.max_pending_children', 2000),
118119
],
119120
'structural_limits' => StructuralLimits::snapshot(),
121+
'topology' => ServerTopology::info(),
120122
'client_compatibility' => ClientCompatibility::info(),
121123
'auth_composition_contract' => AuthCompositionContract::manifest(),
122124
'control_plane' => ControlPlaneProtocol::info(),

app/Support/ServerTopology.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?php
2+
3+
namespace App\Support;
4+
5+
final class ServerTopology
6+
{
7+
public const SCHEMA = 'durable-workflow.v2.role-topology';
8+
9+
public const VERSION = 1;
10+
11+
private const SUPPORTED_SHAPES = [
12+
'embedded',
13+
'standalone_server',
14+
'split_control_execution',
15+
];
16+
17+
private const ROLE_VOCABULARY = [
18+
'api_ingress',
19+
'control_plane',
20+
'matching',
21+
'history_projection',
22+
'scheduler',
23+
'execution_plane',
24+
];
25+
26+
private const CURRENT_SERVER_NODE_ROLES = [
27+
'api_ingress',
28+
'control_plane',
29+
'matching',
30+
'history_projection',
31+
];
32+
33+
/**
34+
* @return array{
35+
* schema: string,
36+
* version: int,
37+
* supported_shapes: array<int, string>,
38+
* role_vocabulary: array<int, string>,
39+
* current_shape: string,
40+
* current_roles: array<int, string>,
41+
* execution_mode: string
42+
* }
43+
*/
44+
public static function info(): array
45+
{
46+
return [
47+
'schema' => self::SCHEMA,
48+
'version' => self::VERSION,
49+
'supported_shapes' => self::SUPPORTED_SHAPES,
50+
'role_vocabulary' => self::ROLE_VOCABULARY,
51+
'current_shape' => 'standalone_server',
52+
'current_roles' => self::CURRENT_SERVER_NODE_ROLES,
53+
'execution_mode' => self::executionMode(),
54+
];
55+
}
56+
57+
private static function executionMode(): string
58+
{
59+
return config('server.mode') === 'embedded'
60+
? 'local_queue_worker'
61+
: 'remote_worker_protocol';
62+
}
63+
}

tests/Feature/ClusterInfoCompatibilityTest.php

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
use App\Support\ClientCompatibility;
77
use App\Support\ControlPlaneProtocol;
88
use App\Support\ControlPlaneRequestContract;
9+
use App\Support\ServerTopology;
910
use App\Support\WorkerProtocol;
1011
use Illuminate\Foundation\Testing\RefreshDatabase;
1112
use Tests\TestCase;
@@ -70,12 +71,23 @@ public function test_cluster_info_is_a_versionless_protocol_discovery_contract()
7071
'max_pending_children',
7172
],
7273
'structural_limits',
74+
'topology' => [
75+
'schema',
76+
'version',
77+
'supported_shapes',
78+
'role_vocabulary',
79+
'current_shape',
80+
'current_roles',
81+
'execution_mode',
82+
],
7383
'client_compatibility',
7484
'auth_composition_contract',
7585
'control_plane',
7686
'worker_protocol',
7787
'bridge_adapter_outcome_contract',
7888
])
89+
->assertJsonPath('topology.schema', ServerTopology::SCHEMA)
90+
->assertJsonPath('topology.version', ServerTopology::VERSION)
7991
->assertJsonPath('control_plane.version', ControlPlaneProtocol::VERSION)
8092
->assertJsonPath('worker_protocol.version', WorkerProtocol::VERSION)
8193
->assertJsonPath('client_compatibility.authority', 'protocol_manifests');

tests/Feature/ClusterInfoTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Tests\Feature;
44

5+
use App\Support\ServerTopology;
56
use Illuminate\Foundation\Testing\RefreshDatabase;
67
use Tests\TestCase;
78

@@ -135,6 +136,35 @@ public function test_it_publishes_external_task_input_contract_manifest(): void
135136
);
136137
}
137138

139+
public function test_it_publishes_role_topology_for_the_current_server_node(): void
140+
{
141+
$this->getJson('/api/cluster/info')
142+
->assertOk()
143+
->assertJsonPath('topology.schema', ServerTopology::SCHEMA)
144+
->assertJsonPath('topology.version', ServerTopology::VERSION)
145+
->assertJsonPath('topology.current_shape', 'standalone_server')
146+
->assertJsonPath('topology.execution_mode', 'remote_worker_protocol')
147+
->assertJsonPath('topology.current_roles.0', 'api_ingress')
148+
->assertJsonPath('topology.current_roles.1', 'control_plane')
149+
->assertJsonPath('topology.current_roles.2', 'matching')
150+
->assertJsonPath('topology.current_roles.3', 'history_projection')
151+
->assertJsonPath('topology.supported_shapes.0', 'embedded')
152+
->assertJsonPath('topology.supported_shapes.1', 'standalone_server')
153+
->assertJsonPath('topology.supported_shapes.2', 'split_control_execution')
154+
->assertJsonPath('topology.role_vocabulary.4', 'scheduler')
155+
->assertJsonPath('topology.role_vocabulary.5', 'execution_plane');
156+
}
157+
158+
public function test_it_switches_cluster_topology_execution_mode_when_embedded_dispatch_is_enabled(): void
159+
{
160+
config(['server.mode' => 'embedded']);
161+
162+
$this->getJson('/api/cluster/info')
163+
->assertOk()
164+
->assertJsonPath('topology.current_shape', 'standalone_server')
165+
->assertJsonPath('topology.execution_mode', 'local_queue_worker');
166+
}
167+
138168
public function test_it_publishes_external_execution_surface_contract_manifest(): void
139169
{
140170
$this->getJson('/api/cluster/info')

0 commit comments

Comments
 (0)