mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-11 13:17:07 +00:00
fix(variants): apply a variant pin only while its carrier is in the round
Pins of every role applied at every round index, so a role the round did not deploy could pull a partner into a heavier variant (web-app-discourse#1 measured 35.46 GB against the 32 GB reservation cap). resolve_round now starts every role on its clamped index and recomputes the include and the pins in force until the assignment settles, raising VariantPinError on a cycle. The planner resolves each round once (39.1 s to 16.0 s for all rounds), affected judges every (role, index) with the round the planner deploys for it, and the resource budget lint measures the pin-aware rounds. The stale web-app-matomo pin above the primary-only docs entry is dropped, and test_variant_pin_fixed_point.py guards every planned round against unsettled pins and pins above a disabled entry. docs/contributing/artefact/files/role/variants.md explains the settle loop with diagrams. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8127d61018
commit
76fc6992c4
10 files changed
+935
-173
No files matched your search
@@ -21,11 +21,6 @@ class TestPlanDevInventoryMatrix(unittest.TestCase):
|
||||
"cli.administration.deploy.development.inventory.legacy_resolver._resolve_round_include",
|
||||
autospec=True,
|
||||
)
|
||||
@patch(
|
||||
"cli.administration.deploy.development.inventory.legacy_resolver._build_services_overrides_for_round",
|
||||
autospec=True,
|
||||
return_value={},
|
||||
)
|
||||
@patch(
|
||||
"cli.administration.deploy.development.inventory.planner.get_variants",
|
||||
autospec=True,
|
||||
@@ -33,7 +28,6 @@ class TestPlanDevInventoryMatrix(unittest.TestCase):
|
||||
def test_single_variant_apps_share_one_unsuffixed_folder(
|
||||
self,
|
||||
get_variants_mock: MagicMock,
|
||||
_overrides_mock: MagicMock,
|
||||
resolve_include_mock: MagicMock,
|
||||
) -> None:
|
||||
get_variants_mock.return_value = {
|
||||
@@ -63,11 +57,6 @@ class TestPlanDevInventoryMatrix(unittest.TestCase):
|
||||
"cli.administration.deploy.development.inventory.legacy_resolver._resolve_round_include",
|
||||
autospec=True,
|
||||
)
|
||||
@patch(
|
||||
"cli.administration.deploy.development.inventory.legacy_resolver._build_services_overrides_for_round",
|
||||
autospec=True,
|
||||
return_value={},
|
||||
)
|
||||
@patch(
|
||||
"cli.administration.deploy.development.inventory.planner.get_variants",
|
||||
autospec=True,
|
||||
@@ -75,7 +64,6 @@ class TestPlanDevInventoryMatrix(unittest.TestCase):
|
||||
def test_two_variant_primary_drives_two_suffixed_folders(
|
||||
self,
|
||||
get_variants_mock: MagicMock,
|
||||
_overrides_mock: MagicMock,
|
||||
resolve_include_mock: MagicMock,
|
||||
) -> None:
|
||||
get_variants_mock.return_value = {
|
||||
@@ -115,11 +103,6 @@ class TestPlanDevInventoryMatrix(unittest.TestCase):
|
||||
"cli.administration.deploy.development.inventory.legacy_resolver._resolve_round_include",
|
||||
autospec=True,
|
||||
)
|
||||
@patch(
|
||||
"cli.administration.deploy.development.inventory.legacy_resolver._build_services_overrides_for_round",
|
||||
autospec=True,
|
||||
return_value={},
|
||||
)
|
||||
@patch(
|
||||
"cli.administration.deploy.development.inventory.planner.get_variants",
|
||||
autospec=True,
|
||||
@@ -127,7 +110,6 @@ class TestPlanDevInventoryMatrix(unittest.TestCase):
|
||||
def test_round_count_driven_by_primary_max_variants(
|
||||
self,
|
||||
get_variants_mock: MagicMock,
|
||||
_overrides_mock: MagicMock,
|
||||
resolve_include_mock: MagicMock,
|
||||
) -> None:
|
||||
get_variants_mock.return_value = {
|
||||
@@ -175,11 +157,6 @@ class TestPlanDevInventoryMatrix(unittest.TestCase):
|
||||
"cli.administration.deploy.development.inventory.legacy_resolver._resolve_round_include",
|
||||
autospec=True,
|
||||
)
|
||||
@patch(
|
||||
"cli.administration.deploy.development.inventory.legacy_resolver._build_services_overrides_for_round",
|
||||
autospec=True,
|
||||
return_value={},
|
||||
)
|
||||
@patch(
|
||||
"cli.administration.deploy.development.inventory.planner.get_variants",
|
||||
autospec=True,
|
||||
@@ -187,7 +164,6 @@ class TestPlanDevInventoryMatrix(unittest.TestCase):
|
||||
def test_purge_set_is_union_when_variants_differ(
|
||||
self,
|
||||
get_variants_mock: MagicMock,
|
||||
_overrides_mock: MagicMock,
|
||||
resolve_include_mock: MagicMock,
|
||||
) -> None:
|
||||
get_variants_mock.return_value = {
|
||||
@@ -266,11 +242,6 @@ class TestPlanBakesPinnedVariant(unittest.TestCase):
|
||||
autospec=True,
|
||||
return_value=("web-app-bar", "web-app-foo"),
|
||||
)
|
||||
@patch(
|
||||
"cli.administration.deploy.development.inventory.legacy_resolver._build_services_overrides_for_round",
|
||||
autospec=True,
|
||||
return_value={},
|
||||
)
|
||||
@patch(
|
||||
"cli.administration.deploy.development.inventory.planner.get_variants",
|
||||
autospec=True,
|
||||
@@ -278,7 +249,6 @@ class TestPlanBakesPinnedVariant(unittest.TestCase):
|
||||
def test_round_variants_carry_the_pinned_index(
|
||||
self,
|
||||
get_variants_mock: MagicMock,
|
||||
_overrides_mock: MagicMock,
|
||||
_include_mock: MagicMock,
|
||||
_pins_mock: MagicMock,
|
||||
) -> None:
|
||||
|
||||
@@ -163,6 +163,61 @@ class TestAffected(unittest.TestCase):
|
||||
got = affected_main.affected_selection(["web-app-keycloak"])
|
||||
self.assertIn("web-app-consumer#0", got)
|
||||
|
||||
def test_each_variant_is_judged_with_the_pins_its_own_round_runs(self) -> None:
|
||||
"""Variant 1 reaches the seed only through its own pin moving the
|
||||
partner, and an unrelated role's pin would move the subject itself;
|
||||
only the round the planner deploys for each variant keeps both."""
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
root = Path(td)
|
||||
for role in (
|
||||
"web-app-keycloak",
|
||||
"web-app-matomo",
|
||||
"web-app-consumer",
|
||||
"web-app-other",
|
||||
):
|
||||
_mk_app_role(root, role, role)
|
||||
off = {"enabled": False, "shared": False}
|
||||
on = {"enabled": True, "shared": True}
|
||||
dump_yaml(
|
||||
root / "roles" / "web-app-matomo" / ROLE_FILE_META_SERVICES,
|
||||
{"matomo": {}, "sso": off},
|
||||
)
|
||||
dump_yaml(
|
||||
root / "roles" / "web-app-matomo" / ROLE_FILE_META_VARIANTS,
|
||||
[{}, {}, {"services": {"sso": on}}],
|
||||
)
|
||||
dump_yaml(
|
||||
root / "roles" / "web-app-consumer" / ROLE_FILE_META_SERVICES,
|
||||
{"consumer": {}, "sso": on, "matomo": on},
|
||||
)
|
||||
(root / "roles" / "web-app-consumer" / ROLE_FILE_META_VARIANTS).write_text(
|
||||
"- services:\n"
|
||||
" matomo: {enabled: false, shared: false}\n"
|
||||
"- services:\n"
|
||||
" sso: {enabled: false, shared: false}\n"
|
||||
" # variant-pin: web-app-matomo#2\n"
|
||||
" matomo: {enabled: true, shared: true}\n"
|
||||
"- services:\n"
|
||||
" sso: {enabled: false, shared: false}\n"
|
||||
" matomo: {enabled: false, shared: false}\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
dump_yaml(
|
||||
root / "roles" / "web-app-other" / ROLE_FILE_META_SERVICES,
|
||||
{"other": {}, "matomo": off},
|
||||
)
|
||||
(root / "roles" / "web-app-other" / ROLE_FILE_META_VARIANTS).write_text(
|
||||
"- services:\n"
|
||||
" # variant-pin: web-app-consumer#2\n"
|
||||
" matomo: {enabled: false, shared: false}\n"
|
||||
"- {}\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
with patch.object(repo_paths, "PROJECT_ROOT", root):
|
||||
got = affected_main.affected_selection(["web-app-keycloak"])
|
||||
self.assertIn("web-app-consumer#0,1", got)
|
||||
|
||||
def test_a_seed_keeps_every_variant_of_its_own_role(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
root = Path(td)
|
||||
|
||||
Reference in new issue
Block a user