@@ -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 }
0 commit comments