IR-20261112: chore(forgejo-runner): replicas 9 -> 8 (ordinal-8 volume fits no node under Longhorn 110%) #954

Merged
pi-landing merged 1 commit from plane/IR-20261112-runners8 into live 2026-11-12 05:16:16 +00:00
Collaborator

Returns the forgejo-runner StatefulSet to 8 replicas (was 9). The dind-gc fix
is kept, the affinity is untouched, and nothing lands from this PR — the rollout
is its own runner gap, below.

Why 8, not 9: the Longhorn 110% math

Ordinal 8's 30 Gi strict-local (longhorn-runner) docker volume fits on NO
runner node
under Longhorn's over-provisioning rule
scheduled + size <= (max - reserved) x 110%:

node scheduled + 30Gi (max − reserved) × 110% fits?
wk-02 458 + 30 = 488 (499 − 100) × 1.1 = 438.9 no
wk-05 351 + 30 = 381 (399 − 80) × 1.1 = 350.9 no
wk-06 325 + 30 = 355 (399 − 96) × 1.1 = 333.3 no

So the 9th pod could not schedule at all: ordinal 8 sat Pending with volume
insufficient storage while ordinals 0–7 ran the old revision at capacity 2.
#948 (capacity 2 → 1) and #949 (replicas 8 → 9, dind-gc follows) are already
landed on live; this PR removes the 9th slot that no node can host.

8 slots at capacity 1 is accepted until the pve RAM and disk arrive. Back at 8
replicas the capacity-1 revision can roll ordinals 0–7 cleanly, one slot each.

Follow-up recorded (not this PR): option (C) — shrink the runner docker volume to
~20 Gi
via a PVC migration, which buys the 9th slot back on today's nodes.

The dind-gc fix is kept, and it follows the count

The GC sweep width is not a constant: RUNNER_COUNT tracks
statefulset.yaml spec.replicas
, so it moves 9 → 8 here in both places it exists.

  • dind-gc-cronjob.yaml — env RUNNER_COUNT: "9" → "8" (comment updated: 8 = the replica count on this branch (was 9)).
  • dind-gc-script.yaml — default RUNNER_COUNT="${RUNNER_COUNT:-9}" → :-8.
  • The ordinal loop stays while [ "$ordinal" -lt "$RUNNER_COUNT" ] — never a
    hard-coded 0..N-1 range, which is the HOMELAB-2481 leak class.

CI pins all three together: scripts/manifests/tests/test_runner_dind_gc_tightening.py
A5 reads spec.replicas from the StatefulSet and asserts the CronJob env and the
script default both equal it. Run locally at this head:

python3 scripts/manifests/tests/test_runner_dind_gc_tightening.py            → OK, all assertions passed
python3 scripts/manifests/tests/test_runner_dind_gc_tightening.py --self-check → OK, 31 bad cases rejected
python3 scripts/manifests/tests/test_runner_ephemeral_ceilings.py            → ok (ceilings unchanged)
python3 scripts/manifests/tests/test_runner_ephemeral_ceilings.py --self-check → all 11 bad cases rejected

That test file carries the PR's only non-manifest change, and it is a mechanical
consequence of the count moving: its --self-check bad case "CronJob RUNNER_COUNT
left behind the StatefulSet replicas" carried the literal "8", which stopped
being a mismatch the moment replicas became 8 — the self-check then printed NOT REJECTED and failed CI while the assertion itself was fine. The stale value is now
derived from spec.replicas (str(int(_runner_count()) - 1)), which is the same
constant-bumped-alongside rot that module's docstring already warns about. No
assertion was weakened; the run above shows all 31 bad cases still rejected.

Affinity is unchanged

nodeAffinity (wk-02 / wk-05 / wk-06, wk-01 removed in #949) and the
topologySpreadConstraints are byte-for-byte identical in this diff. Only
spec.replicas and comments move. The 2026-11-11 headroom paragraph is kept as
history with its number corrected (the CEILING is 9 and not 10 — unchanged), and a
new dated 2026-11-12 block records the 110% math above as the reason for 8.

At rollout: ordinal 8's pod goes, its PVCs stay as orphans

Scaling a StatefulSet down never deletes PVCs, so
docker-data-forgejo-runner-8 and runner-data-forgejo-runner-8 are left behind
as orphans
— deliberately, not "cleaned up" by this change. They are for a
named cleanup later (an operator-gated PVC deletion, not part of this PR).

Rollout is NOT part of this PR

Ordering matters because the pool is the CI that would otherwise be rolling itself.
The rollout happens in a second runner gap: an idle check that includes Sendrast,
then START/END notices to a9 and 3c. Nothing is merged or rolled by the
builder that opened this PR.

Scope

Four files, one commit, no force-push:

  • core/manifests/apps/forgejo-runner/statefulset.yaml — spec.replicas: 9 → 8;
    comments corrected / the 2026-11-12 dated block added. Affinity untouched.
  • core/manifests/apps/forgejo-runner/dind-gc-cronjob.yaml — RUNNER_COUNT env
    "9" → "8", two comments.
  • core/manifests/apps/forgejo-runner/dind-gc-script.yaml — RUNNER_COUNT default
    9 → 8, two comments.
  • scripts/manifests/tests/test_runner_dind_gc_tightening.py — the self-check bad
    case derives its stale value instead of hard-coding 8 (above).

Pushed as pi-landing (repo activity feed act_user = pi-landing on the commit);
opened through the mint.sh staged-header route with GET /user asserted pi-landing
in-process before the POST.

Returns the forgejo-runner StatefulSet to **8 replicas** (was 9). The dind-gc fix is kept, the affinity is untouched, and **nothing lands from this PR** — the rollout is its own runner gap, below. ## Why 8, not 9: the Longhorn 110% math Ordinal 8's **30 Gi strict-local (`longhorn-runner`) docker volume fits on NO runner node** under Longhorn's over-provisioning rule `scheduled + size <= (max - reserved) x 110%`: | node | scheduled + 30Gi | (max − reserved) × 110% | fits? | |---|---|---|---| | **wk-02** | 458 + 30 = 488 | (499 − 100) × 1.1 = **438.9** | no | | **wk-05** | 351 + 30 = 381 | (399 − 80) × 1.1 = **350.9** | no | | **wk-06** | 325 + 30 = 355 | (399 − 96) × 1.1 = **333.3** | no | So the 9th pod could not schedule at all: ordinal 8 sat **Pending** with volume `insufficient storage` while ordinals 0–7 ran the old revision at capacity 2. #948 (capacity 2 → 1) and #949 (replicas 8 → 9, dind-gc follows) are already landed on `live`; this PR removes the 9th slot that no node can host. **8 slots at capacity 1 is accepted until the pve RAM and disk arrive.** Back at 8 replicas the capacity-1 revision can roll ordinals 0–7 cleanly, one slot each. Follow-up recorded (not this PR): **option (C) — shrink the runner docker volume to ~20 Gi** via a PVC migration, which buys the 9th slot back on today's nodes. ## The dind-gc fix is kept, and it follows the count The GC sweep width is not a constant: `RUNNER_COUNT` **tracks `statefulset.yaml spec.replicas`**, so it moves 9 → 8 here in both places it exists. * `dind-gc-cronjob.yaml` — env `RUNNER_COUNT: "9"` → `"8"` (comment updated: `8 = the replica count on this branch (was 9)`). * `dind-gc-script.yaml` — default `RUNNER_COUNT="${RUNNER_COUNT:-9}"` → `:-8`. * The ordinal loop stays **`while [ "$ordinal" -lt "$RUNNER_COUNT" ]`** — never a hard-coded `0..N-1` range, which is the HOMELAB-2481 leak class. CI pins all three together: `scripts/manifests/tests/test_runner_dind_gc_tightening.py` A5 reads `spec.replicas` from the StatefulSet and asserts the CronJob env and the script default both equal it. Run locally at this head: ``` python3 scripts/manifests/tests/test_runner_dind_gc_tightening.py → OK, all assertions passed python3 scripts/manifests/tests/test_runner_dind_gc_tightening.py --self-check → OK, 31 bad cases rejected python3 scripts/manifests/tests/test_runner_ephemeral_ceilings.py → ok (ceilings unchanged) python3 scripts/manifests/tests/test_runner_ephemeral_ceilings.py --self-check → all 11 bad cases rejected ``` That test file carries the PR's only non-manifest change, and it is a mechanical consequence of the count moving: its `--self-check` bad case "CronJob RUNNER_COUNT left behind the StatefulSet replicas" carried the **literal `"8"`**, which stopped being a mismatch the moment replicas became 8 — the self-check then printed `NOT REJECTED` and failed CI while the assertion itself was fine. The stale value is now **derived** from `spec.replicas` (`str(int(_runner_count()) - 1)`), which is the same constant-bumped-alongside rot that module's docstring already warns about. No assertion was weakened; the run above shows all 31 bad cases still rejected. ## Affinity is unchanged `nodeAffinity` (wk-02 / wk-05 / wk-06, wk-01 removed in #949) and the `topologySpreadConstraints` are **byte-for-byte identical** in this diff. Only `spec.replicas` and comments move. The 2026-11-11 headroom paragraph is kept as history with its number corrected (`the CEILING is 9 and not 10` — unchanged), and a new dated 2026-11-12 block records the 110% math above as the reason for 8. ## At rollout: ordinal 8's pod goes, its PVCs stay as orphans Scaling a StatefulSet down never deletes PVCs, so `docker-data-forgejo-runner-8` and `runner-data-forgejo-runner-8` are **left behind as orphans** — deliberately, not "cleaned up" by this change. They are for a **named cleanup later** (an operator-gated PVC deletion, not part of this PR). ## Rollout is NOT part of this PR Ordering matters because the pool is the CI that would otherwise be rolling itself. The rollout happens in a **second runner gap**: an idle check that includes Sendrast, then START/END notices to **a9** and **3c**. Nothing is merged or rolled by the builder that opened this PR. ## Scope Four files, one commit, no force-push: * `core/manifests/apps/forgejo-runner/statefulset.yaml` — `spec.replicas: 9` → `8`; comments corrected / the 2026-11-12 dated block added. Affinity untouched. * `core/manifests/apps/forgejo-runner/dind-gc-cronjob.yaml` — `RUNNER_COUNT` env `"9"` → `"8"`, two comments. * `core/manifests/apps/forgejo-runner/dind-gc-script.yaml` — `RUNNER_COUNT` default `9` → `8`, two comments. * `scripts/manifests/tests/test_runner_dind_gc_tightening.py` — the self-check bad case derives its stale value instead of hard-coding `8` (above). Pushed as `pi-landing` (repo activity feed `act_user` = pi-landing on the commit); opened through the mint.sh staged-header route with `GET /user` asserted `pi-landing` in-process before the POST.
IR-20261112: chore(forgejo-runner): replicas 9 -> 8 (ordinal-8 volume fits no node under Longhorn 110%)
All checks were successful
CI Review / helm-validate (pull_request) Successful in 10s
CI Review / runner-manifest-test (pull_request) Successful in 10s
CI Review / receiver-test (pull_request) Successful in 4s
Lint & Validate / shellcheck (pull_request) Successful in 6s
CI Review / alert-receiver-test (pull_request) Successful in 23s
Lint & Validate / yaml-lint (pull_request) Successful in 25s
CI Review / record-sql-test (pull_request) Successful in 54s
Lint & Validate / terraform-validate (pull_request) Successful in 1m53s
CI Review / cockpit-test (pull_request) Successful in 2m34s
CI Review / cockpit-e2e (pull_request) Successful in 2m40s
CI Review / dispatcher-test (pull_request) Successful in 3m32s
3e05d8a67c
Ordinal 8's 30Gi strict-local docker volume fits on NO runner node under
Longhorn's over-provisioning rule (scheduled + size <= (max - reserved) x 110%):
wk-02 458+30 > (499-100)x1.1 = 438.9; wk-05 351+30 > (399-80)x1.1 = 350.9;
wk-06 325+30 > (399-96)x1.1 = 333.3. The 9th pod sat Pending with volume
"insufficient storage" and never bought a slot.

Back at 8 replicas the capacity 2 -> 1 revision can roll ordinals 0..7 cleanly
at one slot each: 8 slots at capacity 1 is accepted until the pve RAM and disk
arrive. The dind-gc fix is KEPT and follows the replica count to 8 - the cronjob
RUNNER_COUNT env and the script default both move with spec.replicas, which CI's
test_runner_dind_gc_tightening.py A5 pins (the ordinal loop stays
RUNNER_COUNT-driven, never a hard-coded range). Node affinity and the
topologySpread rules are untouched.

That test's self-check also had to move: its "RUNNER_COUNT left behind" bad case
carried the literal "8", which stopped being a mismatch the moment replicas
became 8, so the case now derives its stale value from the StatefulSet instead
of a constant.

Nothing runs on merge: the rollout is a second runner gap (idle check, START/END
to a9 + 3c) and at rollout ordinal 8's pod goes while its docker-data and
runner-data PVCs stay as orphans for a named cleanup. Recorded follow-up: option
(C), shrink the runner docker volume to ~20Gi (a PVC migration) to get the slot
back.

Lab-Run: IR-20261112-runners8
Collaborator

APPROVE — ld-reviewer — head 3e05d8a67c

Diff reviewed against base live 8f490930554f40d8adf1d0f2a89af614eb9acfd8; PR head confirmed 3e05d8a6… via the PR object. Diff is exactly 4 files (3 manifests + 1 test), one commit.

  1. replicas 9 -> 8 — statefulset.yaml: - replicas: 9 / + replicas: 8 (raw head file line 125 is spec.replicas: 8). ✔

  2. dind-gc fix kept and following the count — dind-gc-cronjob.yaml: env RUNNER_COUNT value: "9" -> value: "8". dind-gc-script.yaml: default RUNNER_COUNT="${RUNNER_COUNT:-9}" -> :-8. Ordinal loop is still RUNNER_COUNT-driven and untouched (while [ "$ordinal" -lt "$RUNNER_COUNT" ]; do, head line 285); grepped the script for hard-coded ranges — no 0..N, no seq 0, no literal for i in 0 1 2 …; the only 0..7 mention is the comment explaining why a fixed range is forbidden (HOMELAB-2481 class). Comment edits are prose only (9 -> 8 runner arithmetic, and the "LAST ordinal / N-1 of N" rewording). ✔

  3. Affinity/spread byte-identical — the complete statefulset.yaml base->head diff contains only the two comment blocks and the single replicas: line; nodeAffinity and topologySpreadConstraints do not appear in the diff at all (the block tail hashes identical in base and head). ✔

  4. Only other change is the test self-check — the whole-file base->head diff of scripts/manifests/tests/test_runner_dind_gc_tightening.py is one hunk: mutate_env(..., "RUNNER_COUNT", "8") becomes stale_count = str(int(_runner_count()) - 1) + mutate_env(..., stale_count), plus explanatory comment and an f-string case name. _runner_count() really reads statefulset.yaml spec.replicas (validates positive int, returns str(replicas)), so at 8 replicas the bad case now mutates to "7" — still a genuine mismatch, so the case is not weakened back into a tautology. No assertion removed or loosened: A5's three checks are intact and unchanged from base — env.get("RUNNER_COUNT") != replicas (line 283), defaults.get("RUNNER_COUNT") != replicas (line 290), and the re.search(r"RUNNER_COUNT", _ordinal_loop_block(...)) check that rejects a hard-coded ordinal range (line 295). No case was added or dropped, so the self-check still enumerates the same set. (Tests not executed here by rule — this is a by-inspection check; the hunk is mechanical.) ✔

  5. PR body math — body carries the Longhorn over-provisioning rule scheduled + size <= (max - reserved) x 110% with the per-node table (wk-02 458+30=488 vs 438.9; wk-05 351+30=381 vs 350.9; wk-06 325+30=355 vs 333.3, all "no"), explains ordinal 8 sat Pending on insufficient storage, and states verbatim "8 slots at capacity 1 is accepted until the pve RAM and disk arrive", with the same math mirrored in the new dated 2026-11-12 comment block in the StatefulSet and in the commit message. ✔

  6. Trailer — head commit 3e05d8a67c5888ea1a6e40210143aea00f819661 message's last non-empty trailer line is Lab-Run: IR-20261112-runners8. ✔

No blocking findings: scope is minimal, the orphaned docker-data-forgejo-runner-8 / runner-data-forgejo-runner-8 PVCs are explicitly declared as deliberate leftovers for a named cleanup, and the rollout is correctly deferred out of this PR.

model: omniroute/deepseek-flash
session: IR-20261112-runners8-seatB-20261112T0435Z

**APPROVE — ld-reviewer — head 3e05d8a67c5888ea1a6e40210143aea00f819661** Diff reviewed against base live `8f490930554f40d8adf1d0f2a89af614eb9acfd8`; PR head confirmed `3e05d8a6…` via the PR object. Diff is exactly 4 files (3 manifests + 1 test), one commit. 1. **replicas 9 -> 8** — `statefulset.yaml`: `- replicas: 9` / `+ replicas: 8` (raw head file line 125 is `spec.replicas: 8`). ✔ 2. **dind-gc fix kept and following the count** — `dind-gc-cronjob.yaml`: env `RUNNER_COUNT` `value: "9"` -> `value: "8"`. `dind-gc-script.yaml`: default `RUNNER_COUNT="${RUNNER_COUNT:-9}"` -> `:-8`. Ordinal loop is still RUNNER_COUNT-driven and untouched (`while [ "$ordinal" -lt "$RUNNER_COUNT" ]; do`, head line 285); grepped the script for hard-coded ranges — no `0..N`, no `seq 0`, no literal `for i in 0 1 2 …`; the only `0..7` mention is the comment explaining why a fixed range is forbidden (HOMELAB-2481 class). Comment edits are prose only (9 -> 8 runner arithmetic, and the "LAST ordinal / N-1 of N" rewording). ✔ 3. **Affinity/spread byte-identical** — the complete `statefulset.yaml` base->head diff contains only the two comment blocks and the single `replicas:` line; `nodeAffinity` and `topologySpreadConstraints` do not appear in the diff at all (the block tail hashes identical in base and head). ✔ 4. **Only other change is the test self-check** — the whole-file base->head diff of `scripts/manifests/tests/test_runner_dind_gc_tightening.py` is one hunk: `mutate_env(..., "RUNNER_COUNT", "8")` becomes `stale_count = str(int(_runner_count()) - 1)` + `mutate_env(..., stale_count)`, plus explanatory comment and an f-string case name. `_runner_count()` really reads `statefulset.yaml spec.replicas` (validates positive int, returns `str(replicas)`), so at 8 replicas the bad case now mutates to `"7"` — still a genuine mismatch, so the case is not weakened back into a tautology. No assertion removed or loosened: A5's three checks are intact and unchanged from base — `env.get("RUNNER_COUNT") != replicas` (line 283), `defaults.get("RUNNER_COUNT") != replicas` (line 290), and the `re.search(r"RUNNER_COUNT", _ordinal_loop_block(...))` check that rejects a hard-coded ordinal range (line 295). No case was added or dropped, so the self-check still enumerates the same set. (Tests not executed here by rule — this is a by-inspection check; the hunk is mechanical.) ✔ 5. **PR body math** — body carries the Longhorn over-provisioning rule `scheduled + size <= (max - reserved) x 110%` with the per-node table (wk-02 458+30=488 vs 438.9; wk-05 351+30=381 vs 350.9; wk-06 325+30=355 vs 333.3, all "no"), explains ordinal 8 sat Pending on `insufficient storage`, and states verbatim **"8 slots at capacity 1 is accepted until the pve RAM and disk arrive"**, with the same math mirrored in the new dated 2026-11-12 comment block in the StatefulSet and in the commit message. ✔ 6. **Trailer** — head commit `3e05d8a67c5888ea1a6e40210143aea00f819661` message's last non-empty trailer line is `Lab-Run: IR-20261112-runners8`. ✔ No blocking findings: scope is minimal, the orphaned `docker-data-forgejo-runner-8` / `runner-data-forgejo-runner-8` PVCs are explicitly declared as deliberate leftovers for a named cleanup, and the rollout is correctly deferred out of this PR. model: omniroute/deepseek-flash session: IR-20261112-runners8-seatB-20261112T0435Z
Collaborator

APPROVE — ld-reviewer — head 3e05d8a67c

Reviewed as a diff against base live (8f49093055), 4 files, +44/−16. I also diffed the base and head blobs of all three manifests directly to confirm the published diff is complete (no hidden hunks) and byte-compared the scheduler block.

  1. replicas 9 → 8 — core/manifests/apps/forgejo-runner/statefulset.yaml: the only non-comment hunk is - replicas: 9 / + replicas: 8 (head line 125), with the surrounding serviceName / podManagementPolicy: Parallel / updateStrategy context unchanged.

  2. dind-gc fix kept and count-derived — dind-gc-cronjob.yaml: env RUNNER_COUNT - value: "9" / + value: "8" (head line 145). dind-gc-script.yaml: default - RUNNER_COUNT="${RUNNER_COUNT:-9}" / + RUNNER_COUNT="${RUNNER_COUNT:-8}" (head line 93). The loop is untouched and still while [ "$ordinal" -lt "$RUNNER_COUNT" ]; do (head line 285) — no hard-coded 0..N-1 anywhere in the script; the case "$RUNNER_COUNT" non-negative-integer guard is also unchanged. Comment edits only restate the same invariant.

  3. Affinity / spread untouched — sed-range comparison of the scheduler block (base 122–175 vs head 143–196, nodeAffinity → requiredDuringSchedulingIgnoredDuringExecution → nodeSelectorTerms → matchExpressions … through topologySpreadConstraints / whenUnsatisfiable: ScheduleAnyway / labelSelector) is byte-identical: sha256 eb1d03e82b02ceb57fcc388bbe3a165e68fc01d3eb6aabeb569d0b480ec0ea40 on both sides. The only statefulset hunks are the header comments (2026-11-11 paragraph reworded to keep its history, "the CEILING is 9 and not 10", a new dated 2026-11-12 110% block, and the rollout-scope note) and the one replicas line.

  4. Test file is the only other change, and it is not weakened — scripts/manifests/tests/test_runner_dind_gc_tightening.py has exactly one hunk: mutate_env(..., "RUNNER_COUNT", "8") becomes stale_count = str(int(_runner_count()) - 1) with the label interpolating it. Checked at head: _runner_count() (line 99) reads spec.replicas from statefulset.yaml and fails loudly on a non-positive-integer, so at head the derived stale value is "7" against a live "8" — the bad case is still a genuine mismatch and still routed to assert_tier1_safety_contract (line 672). No assertion removed, none loosened, no other cases.append touched; the sibling ordinal loop hard-codes 0..7 case that this value must not collide with is intact and now correctly targets "8".

  5. PR body math — the body carries the Longhorn over-provisioning table (scheduled + size <= (max − reserved) × 110%; wk-02 488 > 438.9, wk-05 381 > 350.9, wk-06 355 > 333.3) and states verbatim that 8 slots at capacity 1 is accepted until the pve RAM and disk arrive, with option (C) ~20Gi recorded as the follow-up. The same math appears in the statefulset's dated comment, consistent with the body.

  6. Trailer — commit 3e05d8a67c5888ea1a6e40210143aea00f819661 message ends, after the last body paragraph and a blank line, with Lab-Run: IR-20261112-runners8 as the final trailer. Head SHA in the PR object matches the SHA under review exactly.

Non-blocking observation only (not a request for change): at exactly 8 replicas a literal 0..7 loop would coincidentally be correct, which is why the rewritten script comment says "at any count above 8" — the A5 derivation test is what keeps that honest, and it does. The ordinal-8 PVC orphaning is disclosed and correctly left out of scope.

model: omniroute/deepseek-flash
session: IR-20261112-runners8-seatA-20261112T0435Z

**APPROVE — ld-reviewer — head 3e05d8a67c5888ea1a6e40210143aea00f819661** Reviewed as a diff against base `live` (8f490930554f40d8adf1d0f2a89af614eb9acfd8), 4 files, +44/−16. I also diffed the base and head blobs of all three manifests directly to confirm the published diff is complete (no hidden hunks) and byte-compared the scheduler block. 1. **replicas 9 → 8** — `core/manifests/apps/forgejo-runner/statefulset.yaml`: the only non-comment hunk is `- replicas: 9` / `+ replicas: 8` (head line 125), with the surrounding `serviceName` / `podManagementPolicy: Parallel` / `updateStrategy` context unchanged. 2. **dind-gc fix kept and count-derived** — `dind-gc-cronjob.yaml`: env `RUNNER_COUNT` `- value: "9"` / `+ value: "8"` (head line 145). `dind-gc-script.yaml`: default `- RUNNER_COUNT="${RUNNER_COUNT:-9}"` / `+ RUNNER_COUNT="${RUNNER_COUNT:-8}"` (head line 93). The loop is untouched and still `while [ "$ordinal" -lt "$RUNNER_COUNT" ]; do` (head line 285) — no hard-coded `0..N-1` anywhere in the script; the `case "$RUNNER_COUNT"` non-negative-integer guard is also unchanged. Comment edits only restate the same invariant. 3. **Affinity / spread untouched** — `sed`-range comparison of the scheduler block (base 122–175 vs head 143–196, `nodeAffinity` → `requiredDuringSchedulingIgnoredDuringExecution` → `nodeSelectorTerms` → `matchExpressions` … through `topologySpreadConstraints` / `whenUnsatisfiable: ScheduleAnyway` / `labelSelector`) is byte-identical: `sha256 eb1d03e82b02ceb57fcc388bbe3a165e68fc01d3eb6aabeb569d0b480ec0ea40` on both sides. The only statefulset hunks are the header comments (2026-11-11 paragraph reworded to keep its history, "the CEILING is 9 and not 10", a new dated 2026-11-12 110% block, and the rollout-scope note) and the one replicas line. 4. **Test file is the only other change, and it is not weakened** — `scripts/manifests/tests/test_runner_dind_gc_tightening.py` has exactly one hunk: `mutate_env(..., "RUNNER_COUNT", "8")` becomes `stale_count = str(int(_runner_count()) - 1)` with the label interpolating it. Checked at head: `_runner_count()` (line 99) reads `spec.replicas` from `statefulset.yaml` and fails loudly on a non-positive-integer, so at head the derived stale value is `"7"` against a live `"8"` — the bad case is still a genuine mismatch and still routed to `assert_tier1_safety_contract` (line 672). No assertion removed, none loosened, no other `cases.append` touched; the sibling `ordinal loop hard-codes 0..7` case that this value must not collide with is intact and now correctly targets `"8"`. 5. **PR body math** — the body carries the Longhorn over-provisioning table (`scheduled + size <= (max − reserved) × 110%`; wk-02 488 > 438.9, wk-05 381 > 350.9, wk-06 355 > 333.3) and states verbatim that **8 slots at capacity 1 is accepted until the pve RAM and disk arrive**, with option (C) ~20Gi recorded as the follow-up. The same math appears in the statefulset's dated comment, consistent with the body. 6. **Trailer** — commit `3e05d8a67c5888ea1a6e40210143aea00f819661` message ends, after the last body paragraph and a blank line, with `Lab-Run: IR-20261112-runners8` as the final trailer. Head SHA in the PR object matches the SHA under review exactly. Non-blocking observation only (not a request for change): at exactly 8 replicas a literal `0..7` loop would coincidentally be correct, which is why the rewritten script comment says "at any count **above** 8" — the A5 derivation test is what keeps that honest, and it does. The ordinal-8 PVC orphaning is disclosed and correctly left out of scope. model: omniroute/deepseek-flash session: IR-20261112-runners8-seatA-20261112T0435Z
Sign in to join this conversation.
No reviewers
No labels
operator-review
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Homelab/infra-core!954
No description provided.