From ecba627cecce40bd542488e803e42947c09f4737 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 14:39:02 +0900 Subject: [PATCH 1/4] fix(actions): move registry retirement off saturated runner lane --- .github/workflows/hourly-review-repair-registry-retirement.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/hourly-review-repair-registry-retirement.yml b/.github/workflows/hourly-review-repair-registry-retirement.yml index 13871bd8f8..bf552f55a7 100644 --- a/.github/workflows/hourly-review-repair-registry-retirement.yml +++ b/.github/workflows/hourly-review-repair-registry-retirement.yml @@ -26,7 +26,7 @@ jobs: github.repository == 'ContextualWisdomLab/.github' && github.event_name == 'push' && github.ref == 'refs/heads/main' - runs-on: ubuntu-24.04 + runs-on: ubuntu-slim timeout-minutes: 10 env: GH_TOKEN: ${{ github.token }} From 8262a0ecf1d427176b2281d549080f3e525e004e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 14:39:17 +0900 Subject: [PATCH 2/4] test(actions): pin registry retirement capacity contract --- tests/test_hourly_review_repair_registry_retirement.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_hourly_review_repair_registry_retirement.py b/tests/test_hourly_review_repair_registry_retirement.py index fe253fde51..7c2ada0f1c 100644 --- a/tests/test_hourly_review_repair_registry_retirement.py +++ b/tests/test_hourly_review_repair_registry_retirement.py @@ -50,6 +50,15 @@ def test_retirement_is_protected_main_push_only_and_not_scheduled() -> None: assert "id-token: write" not in text +def test_retirement_uses_capacity_available_short_lived_runner() -> None: + """The one-shot retirement must not wait behind the saturated standard queue it repairs.""" + text = _text() + + assert "runs-on: ubuntu-slim" in text + assert "runs-on: ubuntu-24.04" not in text + assert "runs-on: ubuntu-latest" not in text + + def test_retirement_names_every_legacy_identity_exactly_once() -> None: """No deleted hourly caller can remain an untracked active registry ID.""" text = _text() From b66ce13ab2de0b31e233a8477137689003feb344 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 14:39:36 +0900 Subject: [PATCH 3/4] docs(actions): record registry retirement capacity repair --- .../doctoring/hourly-review-repair-registry-retirement.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/doctoring/hourly-review-repair-registry-retirement.md b/docs/doctoring/hourly-review-repair-registry-retirement.md index 978a04bd46..1a16bddf16 100644 --- a/docs/doctoring/hourly-review-repair-registry-retirement.md +++ b/docs/doctoring/hourly-review-repair-registry-retirement.md @@ -24,13 +24,19 @@ PR #1673 adds the one-shot compatibility workflow `.github/workflows/hourly-revi The migration has repository `actions: write` plus `contents: read`, no checkout, no model/reviewer secrets, no OIDC grant, no repository-content mutation, no schedule, and no arbitrary-branch manual dispatch. It fails closed on missing, duplicate, unresolved, or unexpected registry states. A transient hosted-run failure is retried through GitHub's run/job retry controls against the same reviewed protected-main source rather than by dispatching a feature branch. The permanent consolidated scheduler retains its narrower read/OIDC dispatch permissions and does not inherit registry-mutation authority. +## 2026-09-02 Actions-capacity reconciliation + +The first protected-main migration run remained queued on `ubuntu-24.04` while the central Actions control plane was already carrying a large standard-runner backlog. Because the purpose of this one-shot is itself to retire 18 obsolete workflow identities that contribute unnecessary Actions scheduling pressure, leaving the mutation on the saturated runner lane creates an avoidable operability dependency. The retirement job therefore uses `ubuntu-slim`, which is sufficient for the shell-only `gh`/`jq` registry transaction and does not require checkout, language toolchains, containers, or privileged build tooling. This changes only runner admission; the protected-main event boundary, `actions: write` scope, replacement-active proof, exact identity enumeration, read-after-write verification, fail-closed state handling, and self-disable-last ordering are unchanged. + +`tests/test_hourly_review_repair_registry_retirement.py` pins that runner choice so this one-shot cannot silently regress onto `ubuntu-24.04` or `ubuntu-latest` while it remains needed. Once hosted evidence proves all 18 legacy identities plus this migration identity are disabled and the replacement remains active, the source workflow and this capacity-specific test assertion should be removed together in the normal post-migration cleanup. + ## Cleanup and evidence The migration source must remain in protected `main` until a hosted run proves all 18 legacy identities and the migration identity itself are `disabled_manually` while `.github/workflows/hourly-review-repair.yml` remains active. After that evidence exists, remove the migration YAML in a normal protected-branch PR. Deleting it only after self-disable leaves its historical registry identity disabled rather than creating another enabled orphan. Do not claim the migration complete from PR checks alone; PR checks validate source contracts, while the registry mutation can occur only after the replacement is active on protected `main`. ## Regression contract -`tests/test_hourly_review_repair_registry_retirement.py` requires the one-shot workflow to have neither a schedule nor `workflow_dispatch`, to bind execution to protected-main push context, to name all 18 legacy paths exactly once, to prove the replacement active before the first disable request, to re-read and verify every disabled state, to disable itself last, and to avoid reviewer/model/provider credentials. The focused `Contextual Orchestrator Review Repair Quality CI` watches the migration workflow, this doctoring record, and the retirement contract test so a future change cannot bypass that regression. This complements `tests/test_hourly_review_repair_callers.py`, which continues to verify the 18-repository schedule/target/concurrency mapping in the single active scheduler file. +`tests/test_hourly_review_repair_registry_retirement.py` requires the one-shot workflow to have neither a schedule nor `workflow_dispatch`, to bind execution to protected-main push context, to name all 18 legacy paths exactly once, to prove the replacement active before the first disable request, to re-read and verify every disabled state, to disable itself last, to stay on the capacity-available `ubuntu-slim` lane while migration remains pending, and to avoid reviewer/model/provider credentials. The focused `Contextual Orchestrator Review Repair Quality CI` watches the migration workflow, this doctoring record, and the retirement contract test so a future change cannot bypass that regression. This complements `tests/test_hourly_review_repair_callers.py`, which continues to verify the 18-repository schedule/target/concurrency mapping in the single active scheduler file. ## References From 42612f437a6d55c59f80b726daeae5ede5bcd7b0 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 2 Sep 2026 14:44:03 +0900 Subject: [PATCH 4/4] test(actions): bind retirement runner assertion to exact job --- ...ourly_review_repair_registry_retirement.py | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/tests/test_hourly_review_repair_registry_retirement.py b/tests/test_hourly_review_repair_registry_retirement.py index 7c2ada0f1c..2a54729500 100644 --- a/tests/test_hourly_review_repair_registry_retirement.py +++ b/tests/test_hourly_review_repair_registry_retirement.py @@ -34,6 +34,19 @@ def _text() -> str: return _WORKFLOW.read_text(encoding="utf-8") +def _job_block(text: str, job_name: str) -> str: + """Return only one top-level job block, excluding comments and sibling jobs.""" + anchor = f" {job_name}:\n" + assert text.count(anchor) == 1 + remainder = text.split(anchor, 1)[1] + lines: list[str] = [] + for line in remainder.splitlines(): + if line.startswith(" ") and not line.startswith(" ") and line.strip(): + break + lines.append(line) + return "\n".join(lines) + + def test_retirement_is_protected_main_push_only_and_not_scheduled() -> None: """Privileged registry mutation cannot run from an arbitrary branch or cadence.""" text = _text() @@ -51,12 +64,13 @@ def test_retirement_is_protected_main_push_only_and_not_scheduled() -> None: def test_retirement_uses_capacity_available_short_lived_runner() -> None: - """The one-shot retirement must not wait behind the saturated standard queue it repairs.""" - text = _text() + """The retirement job itself must use the capacity-available short-lived runner.""" + job = _job_block(_text(), "retire-legacy-identities") + runner_directives = [ + line.strip() for line in job.splitlines() if line.startswith(" runs-on:") + ] - assert "runs-on: ubuntu-slim" in text - assert "runs-on: ubuntu-24.04" not in text - assert "runs-on: ubuntu-latest" not in text + assert runner_directives == ["runs-on: ubuntu-slim"] def test_retirement_names_every_legacy_identity_exactly_once() -> None: