Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
23 changes: 23 additions & 0 deletions tests/test_hourly_review_repair_registry_retirement.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -50,6 +63,16 @@ 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 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 runner_directives == ["runs-on: ubuntu-slim"]


def test_retirement_names_every_legacy_identity_exactly_once() -> None:
"""No deleted hourly caller can remain an untracked active registry ID."""
text = _text()
Expand Down
Loading