feat(ci): add a fourth deploy chunk so a short priority chunk strands no budget

A sweep may deploy 210 rows (available) in chunks of 80. Three declared
blocks hold exactly that when there is no priority line, but priority
rows get a chunk of their own that is never topped up with regular rows.
Nine priority rows therefore occupied a whole block, left the regular
rows 2 x 80 = 160, and 42 rows of budget went unused.

Adding a block to the YAML alone would not have changed that:
matrix.chunks_of handed the split slots.chunk_count(), which is
min(ceil(available / chunk size), INFINITO_CI_MAX_CHUNKS) and ignores
the priority seam, so the plan still stopped at three blocks.

call-orchestrator.yml now declares test-deploy-chunk-3, gated on chunks
0 to 2 like every chunk gates on all before it, and report-main-failures
and done need it. INFINITO_CI_MAX_CHUNKS is 4, one above what the budget
fills, and slots.chunk_blocks() returns it. chunks_of hands the split
every declared block; available still caps the rows, and a chunk whose
slice comes back empty skips its deploy. chunk_count() stays the "chunks
filled" figure of the budget report.

Tests: the split spends 9 + 80 + 80 + 41 = 210 rows with a spare block;
chunks_of plans three chunks from chunk_blocks=3 while chunk_count=2
(the previous wiring yields [1, 10]); an integration guard requires the
declared chunk jobs to equal INFINITO_CI_MAX_CHUNKS and to exceed
ceil(available / chunk size) by one, and fails on the stale value 3.

A shell that inherited INFINITO_CI_MAX_CHUNKS=3 keeps failing that guard
until it is restarted; make dotenv-force rewrites .env.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
kevinveenbirkenbachandClaude Opus 5 committed 2026-09-11 23:46:48 +02:00
1 parent fbe07d7fb0
commit c32bf320ff
9 files changed
+125 -12

No files matched your search

@@ -53,6 +53,12 @@ class TestPlan(unittest.TestCase):
self.assertEqual([len(c) for c in plan], [5, 80, 80])
self.assertEqual(plan[0], list(range(5)))
def test_a_spare_block_spends_what_a_short_priority_chunk_leaves(self) -> None:
plan = chunks.plan(
list(range(9)), list(range(100, 400)), size=80, blocks=4, budget=210
)
self.assertEqual([len(c) for c in plan], [9, 80, 80, 41])
def test_the_seam_chunk_is_never_topped_up_with_regular_rows(self) -> None:
plan = chunks.plan(
list(range(5)),
+22 -2
View File
@@ -31,7 +31,7 @@ class TestChunksOf(unittest.TestCase):
def _chunks(self, entries: list[dict], *, size: int) -> list[list[dict]]:
with (
mock.patch.object(matrix.slots, "chunk_size", return_value=size),
mock.patch.object(matrix.slots, "chunk_count", return_value=4),
mock.patch.object(matrix.slots, "chunk_blocks", return_value=4),
mock.patch.object(matrix.slots, "available", return_value=99),
):
return matrix.chunks_of(entries, 0)
@@ -58,6 +58,26 @@ class TestChunksOf(unittest.TestCase):
[chunk[0]["apps"] for chunk in chunks], ["web-app-z", "web-app-a"]
)
def test_a_short_priority_chunk_leaves_the_regular_rows_a_spare_block(
self,
) -> None:
entries = [_entry("web-app-p", "0", "compose", priority=True)] + [
_entry(f"web-app-{index:02d}", "0", "compose") for index in range(20)
]
with (
mock.patch.object(matrix.slots, "chunk_size", return_value=10),
mock.patch.object(matrix.slots, "chunk_count", return_value=2),
mock.patch.object(matrix.slots, "chunk_blocks", return_value=3),
mock.patch.object(matrix.slots, "available", return_value=21),
):
chunks = matrix.chunks_of(entries, 0)
self.assertEqual(
[len(chunk) for chunk in chunks],
[1, 10, 10],
"the split must get every declared block, not only the ones the "
"budget fills, or the rows a short priority chunk leaves go unplanned",
)
def test_priority_chunks_are_sorted_on_their_own(self) -> None:
entries = [
_entry("web-app-z", "0", "compose", priority=True),
@@ -80,7 +100,7 @@ class TestRedundant(unittest.TestCase):
def _chunked(self, entries: list[dict]) -> list[dict]:
with (
mock.patch.object(matrix.slots, "chunk_size", return_value=10),
mock.patch.object(matrix.slots, "chunk_count", return_value=4),
mock.patch.object(matrix.slots, "chunk_blocks", return_value=4),
mock.patch.object(matrix.slots, "available", return_value=99),
):
return [row for chunk in matrix.chunks_of(entries, 0) for row in chunk]