Skip to content

Commit 0c3d659

Browse files
[cross-repo from workflow#135] GitHub #582: server + workflow: v2 architecture Phase 4: control-plane and execution-plane role split (#127)
1 parent e8be6a0 commit 0c3d659

3 files changed

Lines changed: 91 additions & 1 deletion

File tree

app/Support/ServerTopology.php

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public static function info(): array
5959
'scaling_boundaries' => self::scalingBoundaries(),
6060
'supported_topologies' => self::supportedTopologies(),
6161
'migration_path' => self::migrationPath(),
62+
'kernel_invariants' => self::kernelInvariants(),
6263
];
6364
}
6465

@@ -611,7 +612,8 @@ private static function supportedTopologies(): array
611612
/**
612613
* @return list<array{
613614
* step: string,
614-
* result: string
615+
* result: string,
616+
* reversible: bool
615617
* }>
616618
*/
617619
private static function migrationPath(): array
@@ -620,26 +622,75 @@ private static function migrationPath(): array
620622
[
621623
'step' => 'audit_role_boundaries',
622624
'result' => 'tooling flags cross-role writes before runtime shape changes',
625+
'reversible' => true,
623626
],
624627
[
625628
'step' => 'expose_role_bindings',
626629
'result' => 'container seams allow out-of-process adapters without patching the package',
630+
'reversible' => true,
627631
],
628632
[
629633
'step' => 'introduce_dedicated_matching_shape',
630634
'result' => 'matching can run as its own process class without changing the claim contract',
635+
'reversible' => true,
631636
],
632637
[
633638
'step' => 'split_history_projection',
634639
'result' => 'history and projections can move out of process without introducing a second writer',
640+
'reversible' => true,
635641
],
636642
[
637643
'step' => 'split_scheduler',
638644
'result' => 'schedule firing can move behind leader election while single-replica deployments stay legal',
645+
'reversible' => true,
639646
],
640647
[
641648
'step' => 'optional_execution_partitioning',
642649
'result' => 'workers can partition by namespace, connection, queue, and compatibility',
650+
'reversible' => true,
651+
],
652+
];
653+
}
654+
655+
/**
656+
* @return list<array{
657+
* id: string,
658+
* summary: string,
659+
* applies_to: list<string>
660+
* }>
661+
*/
662+
private static function kernelInvariants(): array
663+
{
664+
return [
665+
[
666+
'id' => 'single_persistence_engine',
667+
'summary' => 'one workflow database backs every topology shape; role split does not introduce a second persistence engine',
668+
'applies_to' => ['embedded', 'standalone_server', 'split_control_execution'],
669+
],
670+
[
671+
'id' => 'single_worker_protocol',
672+
'summary' => 'one HTTP worker protocol carries claim, complete, fail, and heartbeat traffic across every topology; role split does not fork the worker contract',
673+
'applies_to' => ['embedded', 'standalone_server', 'split_control_execution'],
674+
],
675+
[
676+
'id' => 'single_history_writer',
677+
'summary' => 'history_events has exactly one durable writer per logical event regardless of where the history/projection role runs',
678+
'applies_to' => ['embedded', 'standalone_server', 'split_control_execution'],
679+
],
680+
[
681+
'id' => 'single_control_authority_per_run',
682+
'summary' => 'every mutation of a given workflow run routes through one control-plane authority; per-run row locks serialise transitions across replicas',
683+
'applies_to' => ['embedded', 'standalone_server', 'split_control_execution'],
684+
],
685+
[
686+
'id' => 'embedded_topology_remains_supported',
687+
'summary' => 'the embedded shape where one process fills every role MUST stay legal; existing embedded hosts are never forced to migrate',
688+
'applies_to' => ['embedded', 'standalone_server', 'split_control_execution'],
689+
],
690+
[
691+
'id' => 'role_split_is_topology_only',
692+
'summary' => 'splitting roles is a topology change, not a product fork; collapsing the roles back onto a single process is always a legal topology',
693+
'applies_to' => ['embedded', 'standalone_server', 'split_control_execution'],
643694
],
644695
];
645696
}

tests/Feature/ClusterInfoCompatibilityTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ public function test_cluster_info_is_a_versionless_protocol_discovery_contract()
101101
'scaling_boundaries',
102102
'supported_topologies',
103103
'migration_path',
104+
'kernel_invariants',
104105
],
105106
'coordination_health' => [
106107
'schema',

tests/Feature/ClusterInfoTest.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,44 @@ public function test_it_publishes_role_topology_for_the_current_server_node(): v
256256
->assertJsonPath(
257257
'topology.migration_path.5.step',
258258
'optional_execution_partitioning',
259+
)
260+
->assertJsonPath('topology.migration_path.0.reversible', true)
261+
->assertJsonPath('topology.migration_path.5.reversible', true)
262+
->assertJsonPath(
263+
'topology.kernel_invariants.0.id',
264+
'single_persistence_engine',
265+
)
266+
->assertJsonPath(
267+
'topology.kernel_invariants.1.id',
268+
'single_worker_protocol',
269+
)
270+
->assertJsonPath(
271+
'topology.kernel_invariants.2.id',
272+
'single_history_writer',
273+
)
274+
->assertJsonPath(
275+
'topology.kernel_invariants.3.id',
276+
'single_control_authority_per_run',
277+
)
278+
->assertJsonPath(
279+
'topology.kernel_invariants.4.id',
280+
'embedded_topology_remains_supported',
281+
)
282+
->assertJsonPath(
283+
'topology.kernel_invariants.5.id',
284+
'role_split_is_topology_only',
285+
)
286+
->assertJsonPath(
287+
'topology.kernel_invariants.0.applies_to.0',
288+
'embedded',
289+
)
290+
->assertJsonPath(
291+
'topology.kernel_invariants.0.applies_to.1',
292+
'standalone_server',
293+
)
294+
->assertJsonPath(
295+
'topology.kernel_invariants.0.applies_to.2',
296+
'split_control_execution',
259297
);
260298
}
261299

0 commit comments

Comments
 (0)