Skip to content

feat(OMN-13942): runner-fleet-maintain workflow — Increment 1 (detection core, read-only) - #2206

Merged
jonahgabriel merged 4 commits into
devfrom
jonah/omn-13942-runner-fleet-maintain-increment1
Jul 5, 2026
Merged

jonahgabriel merged 4 commits into
devfrom
jonah/omn-13942-runner-fleet-maintain-increment1

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Jul 5, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Adds a canonical ONEX node workflow for GitHub Actions self-hosted runner-fleet health detection, per the operator directive to move runner-fleet maintenance from ad-hoc cron/bash into a node-based workflow. Strictly read-only — no path in this PR restarts a runner, cancels a run, force-recreates a container, or dequeues/re-enqueues a merge-queue entry. Increment 2 (recovery effects, FSM cooldown reducer, grant-gated mutation) is design-only per the task and intentionally not built here.

Closes the live OMN-13932 blind spot (buildx loss + codeload throttling degraded the fleet 2026-07-04 with zero automated detection).

Nodes (mirrors the same-repo node_merge_sweep_* trio + omnimarket's node_redeploy_orchestrator gate-then-effect-then-terminal-event topology)

  1. node_runner_fleet_maintain_orchestrator (ORCHESTRATOR_GENERIC, new) — coordinates one tick: start → snapshot-gather command → health-evaluate command → terminal runner-fleet-maintain-completed event. No I/O.
  2. node_runner_fleet_health_compute (COMPUTE_GENERIC, new) — pure/deterministic classifier. Input: a facts-only ModelRunnerFleetSnapshot. Output: ModelRunnerFleetHealthVerdict (per-runner EnumRunnerFleetHealthState, fleet aggregates, recommended_actions that are recorded/surfaced, never executed). Ports collector_runner_health.py::_classify_runner, OMN-13915's _diag heartbeat-freshness rule, and OMN-13109's SILENT-WEDGE heuristic. Adds two new checks closing OMN-13932: BUILDX_UNAVAILABLE, CODELOAD_THROTTLED.
  3. node_runner_health_snapshot_effect (EFFECT_GENERIC) — completed the OMN-6091 stub in place (same contract/topics declared since OMN-6082, not forked) with real gh-api + SSH/Docker gather logic: runner registrations, Docker RestartCount, _diag heartbeat age, queue/run-age facts, and the two new probes (buildx availability, codeload-throttle log-signature grep). Read-only.

Trigger: reuses OMN-13915's runner-fleet-canary 15-min GitHub-hosted schedule per the design — no third parallel schedule.

Gates fixed along the way (all genuine, none bypassed)

  • Registered both new nodes in [project.entry-points."onex.nodes"] and regenerated the dispatch-selection-parity oracle (tests/fixtures/dispatch_parity/baseline-selection-v2.json) and the topic enum (enum_omnibase_infra_topic.py) — both auto-generated, diff is additive-only.
  • Added runtime_profiles: [effects] to all three contracts (ValidatorRuntimeProfiles gate, OMN-12957) — no allowlist entries needed.
  • Allowlisted the 8 new infra-owned handler paths in scripts/ci/infra-node-allowlist.txt (runner fleet is infra's domain object per the task's explicit layering call, not a market business workflow).
  • Baselined the orchestrator's inherent 3-hop payload_type_match ARCH-004 (H2) finding in architecture-handshakes/imperative-orchestrator-baseline.yaml — the exact same accepted shape as 6 sibling orchestrators already baselined (thin decision handlers, no decorative FSM). Verified the changed-node ratchet (--check-changed --ratchet, the actual pre-commit-blocking gate) passes clean.
  • Exempted two validate.py patterns false positives in validation_exemptions.yaml: workflow_name (GitHub's own workflow display name, not an internal entity) and target_id (polymorphic runner-name-or-run-id string, not a UUID).
  • Allowlisted the external-trigger start topic in scripts/check_subscribe_wiring_health.py's _EXTERNAL_PUBLISHER_ALLOWLIST (triggered by the canary GHA schedule, not a Kafka publisher — same category as the existing build-loop-start/skill.merge-sweep entries).

Tests

tests/unit/nodes/node_runner_fleet_maintain/ (18 tests, all passing):

  • test_runner_fleet_health_compute.py — pure classification against fixture fleet states: healthy, saturated (mirrors the live 2026-07-04 incident: all-online zero-idle), crash-loop + listener-zombie, offline, zombie-run-candidate → CANCEL_RUN, buildx-unavailable, codeload-throttled, buildx-precedence-over-codeload, fleet-wide wedge, and a determinism check.
  • test_runner_fleet_snapshot_effect.py — gh-api/SSH/Docker gather fully mocked (no live calls): forward-pass reconciliation, reverse-pass stale-registration, index-bounds filtering, buildx-unavailable probe, partial-source-failure degradation.
  • test_runner_fleet_maintain_workflow_integration.py — full handler-chain wiring (start → gather → evaluate → completed), mirroring test_merge_sweep_canary's pattern.

No test in this PR restarts, cancels, or mutates anything real.

Verification

  • uv run ruff format/check, uv run mypy src/omnibase_infra — clean on all new/changed files.
  • onex spdx fix --check src — clean.
  • scripts/validate.py {architecture,contracts,patterns,unions,imports,any_types,declarative_nodes,migration_freeze} — clean.
  • scripts/check_contract_topic_parity.py, scripts/validation/check_topic_literals.py — clean.
  • bash scripts/ci/reject-node-implementations.sh --all — PASS.
  • Runtime-profiles validator (ValidatorRuntimeProfiles) — clean, no allowlist debt added.
  • scanner_imperative_orchestrator_ratchet --check-changed --ratchet (the actual blocking gate) — clean.
  • tests/integration/runtime/test_dispatch_selection_parity.py (10/10), tests/unit/scripts/test_check_subscribe_wiring_health.py (14/14), tests/unit/contracts/test_batch_validate_node_contracts.py, tests/ci/test_topic_config_contract_parity.py, tests/unit/observability/runner_health/, tests/integration/observability/, tests/unit/nodes/test_merge_sweep_canary/ — all passing.
  • Full suite uv run pytest tests/ -n auto -m "not slow": 24775 passed, 23 failed, 78 errors, 443 skipped — every failure/error reproduces on local-env gaps unrelated to this change (no live Postgres/Kafka/Docker/Infisical on this machine: test_db_handler_integration, test_dlq_tracking_integration, test_adapter_protocol_event_publisher_kafka (78 errors), test_provision_infisical, etc.), except tests/ci/test_runner_image_identity.py::test_recorded_identity_matches_recomputed_identity which is a pre-existing stale Docker-image-lock drift on origin/dev at this branch's base commit (e74105c8) — verified zero diff touches docker/runners/runner-image.lock.json or scripts/ci/runner_image_identity.py in this PR.
  • @pytest.mark.slow test_comprehensive_infra_scan (ARCH-004 full-repo scan, continue-on-error: true in CI, documented ~14min CI runtime) exceeds local wall-clock budget on this machine; not blocking (informational-only CI step).

No mutation

Confirmed by design and by test: HandlerRunnerFleetSnapshot performs read-only gh api/ssh/docker inspect calls only (no docker restart, no gh run cancel, no docker compose up --force-recreate, no queue dequeue/re-enqueue anywhere in the dependency graph of these three nodes). node_runner_fleet_remediate_effect and the FSM cooldown reducer are Increment 2, design-only, not built.

Evidence-Ticket: OMN-13942

Evidence-Source: OCC#3607
Evidence-Ticket: OMN-13942

…ion core, read-only)

Adds a canonical ONEX node workflow for runner-fleet health detection,
closing the OMN-13932 buildx/codeload blind spot. Strictly read-only:
no path in this PR restarts a runner, cancels a run, force-recreates a
container, or dequeues/re-enqueues a merge-queue entry.

New nodes (mirroring the same-repo node_merge_sweep_* trio and the
omnimarket node_redeploy_orchestrator gate-then-effect-then-terminal-event
topology):
- node_runner_fleet_maintain_orchestrator (ORCHESTRATOR_GENERIC, new):
  coordinates the tick — start -> snapshot-gather -> health-evaluate ->
  terminal completed event. No I/O.
- node_runner_fleet_health_compute (COMPUTE_GENERIC, new): pure/deterministic
  classifier. Ports collector_runner_health.py's crash-loop/offline logic,
  OMN-13915's _diag heartbeat-freshness rule, and OMN-13109's SILENT-WEDGE
  heuristic. Adds two NEW checks (BUILDX_UNAVAILABLE, CODELOAD_THROTTLED)
  that close the OMN-13932 gap. Emits recommended_actions that are recorded/
  surfaced only — nothing here executes them.
- node_runner_health_snapshot_effect (EFFECT_GENERIC): completed the OMN-6091
  stub in place (same contract/topics declared since OMN-6082) with real
  gh-api + SSH/Docker gather logic, incl. the two new probes.

Trigger: reuses OMN-13915's runner-fleet-canary 15-min GitHub-hosted
schedule per design — no new parallel schedule.

Also:
- Register both new nodes in [project.entry-points."onex.nodes"] and
  regenerate the dispatch-selection-parity oracle and topic enums.
- Add runtime_profiles: [effects] to all three new/completed contracts
  (ValidatorRuntimeProfiles gate).
- Allowlist the 8 new infra-owned handler paths in infra-node-allowlist.txt
  (runner fleet is infra's domain object, not a market business workflow).
- Baseline the orchestrator's inherent 3-hop payload_type_match ARCH-004
  finding (same accepted shape as 6 sibling orchestrators already baselined).
- Exempt two pattern-validator false positives (workflow_name, target_id —
  neither is an internal entity/UUID) with documented reasons.
- Allowlist the external-trigger start topic in the subscribe-wiring-health
  script (triggered by the canary schedule, not a Kafka publisher).

Increment 2 (recovery effects, FSM reducer, grant/cooldown gate) is
design-only per the task — not built here; a follow-up ticket will be filed
once this increment is reviewed.

Evidence-Ticket: OMN-13942
@coderabbitai

coderabbitai Bot commented Jul 5, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 44 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e81a67b3-2022-4565-8348-4c36d0fd9a52

📥 Commits

Reviewing files that changed from the base of the PR and between 61f6fbf and 38a38bd.

⛔ Files ignored due to path filters (1)
  • src/omnibase_infra/enums/generated/enum_omnibase_infra_topic.py is excluded by !**/generated/**
📒 Files selected for processing (46)
  • architecture-handshakes/imperative-orchestrator-baseline.yaml
  • docker/runners/runner-image.lock.json
  • pyproject.toml
  • scripts/check_subscribe_wiring_health.py
  • scripts/ci/infra-node-allowlist.txt
  • src/omnibase_infra/nodes/node_runner_fleet_health_compute/__init__.py
  • src/omnibase_infra/nodes/node_runner_fleet_health_compute/contract.yaml
  • src/omnibase_infra/nodes/node_runner_fleet_health_compute/handlers/__init__.py
  • src/omnibase_infra/nodes/node_runner_fleet_health_compute/handlers/handler_runner_fleet_health_evaluate.py
  • src/omnibase_infra/nodes/node_runner_fleet_health_compute/models/__init__.py
  • src/omnibase_infra/nodes/node_runner_fleet_health_compute/models/enum_recommended_action_type.py
  • src/omnibase_infra/nodes/node_runner_fleet_health_compute/models/enum_runner_fleet_health_state.py
  • src/omnibase_infra/nodes/node_runner_fleet_health_compute/models/model_recommended_action.py
  • src/omnibase_infra/nodes/node_runner_fleet_health_compute/models/model_runner_fleet_health_evaluate_command.py
  • src/omnibase_infra/nodes/node_runner_fleet_health_compute/models/model_runner_fleet_health_verdict.py
  • src/omnibase_infra/nodes/node_runner_fleet_health_compute/models/model_runner_health_assessment.py
  • src/omnibase_infra/nodes/node_runner_fleet_health_compute/node.py
  • src/omnibase_infra/nodes/node_runner_fleet_maintain_orchestrator/__init__.py
  • src/omnibase_infra/nodes/node_runner_fleet_maintain_orchestrator/contract.yaml
  • src/omnibase_infra/nodes/node_runner_fleet_maintain_orchestrator/handlers/__init__.py
  • src/omnibase_infra/nodes/node_runner_fleet_maintain_orchestrator/handlers/handler_runner_fleet_health_verdict_complete.py
  • src/omnibase_infra/nodes/node_runner_fleet_maintain_orchestrator/handlers/handler_runner_fleet_maintain_start.py
  • src/omnibase_infra/nodes/node_runner_fleet_maintain_orchestrator/handlers/handler_runner_fleet_snapshot_complete.py
  • src/omnibase_infra/nodes/node_runner_fleet_maintain_orchestrator/models/__init__.py
  • src/omnibase_infra/nodes/node_runner_fleet_maintain_orchestrator/models/model_runner_fleet_maintain_completed_event.py
  • src/omnibase_infra/nodes/node_runner_fleet_maintain_orchestrator/models/model_runner_fleet_maintain_start_command.py
  • src/omnibase_infra/nodes/node_runner_fleet_maintain_orchestrator/node.py
  • src/omnibase_infra/nodes/node_runner_health_snapshot_effect/__init__.py
  • src/omnibase_infra/nodes/node_runner_health_snapshot_effect/contract.yaml
  • src/omnibase_infra/nodes/node_runner_health_snapshot_effect/handlers/__init__.py
  • src/omnibase_infra/nodes/node_runner_health_snapshot_effect/handlers/handler_runner_fleet_snapshot.py
  • src/omnibase_infra/nodes/node_runner_health_snapshot_effect/models/__init__.py
  • src/omnibase_infra/nodes/node_runner_health_snapshot_effect/models/model_runner_fleet_runner_fact.py
  • src/omnibase_infra/nodes/node_runner_health_snapshot_effect/models/model_runner_fleet_snapshot.py
  • src/omnibase_infra/nodes/node_runner_health_snapshot_effect/models/model_runner_fleet_snapshot_gather_command.py
  • src/omnibase_infra/nodes/node_runner_health_snapshot_effect/models/model_zombie_run_candidate.py
  • src/omnibase_infra/nodes/node_runner_health_snapshot_effect/node.py
  • src/omnibase_infra/validation/validation_exemptions.yaml
  • tests/fixtures/dispatch_parity/baseline-selection-v2.json
  • tests/integration/docker/test_docker_integration.py
  • tests/integration/infra/test_omn_12765_release_backmerge_identity.py
  • tests/unit/nodes/node_architecture_validator/test_imperative_orchestrator_ratchet_baseline.py
  • tests/unit/nodes/node_runner_fleet_maintain/__init__.py
  • tests/unit/nodes/node_runner_fleet_maintain/test_runner_fleet_health_compute.py
  • tests/unit/nodes/node_runner_fleet_maintain/test_runner_fleet_maintain_workflow_integration.py
  • tests/unit/nodes/node_runner_fleet_maintain/test_runner_fleet_snapshot_effect.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch jonah/omn-13942-runner-fleet-maintain-increment1

Comment @coderabbitai help to get the list of available commands.

…er-image lock

- Update ARCH-004 imperative-orchestrator baseline frozen-list tests to accept
  the new node_runner_fleet_maintain_orchestrator entry (OMN-13942): add it to
  EXPECTED_OWNER_TICKETS and bump risk-2 count 6->7.
- Regenerate docker/runners/runner-image.lock.json: the two new node entry-points
  in pyproject.toml changed the manifest_digest (and shared_env_digest), so the
  bound runner-image identity was stale. Fixes Split 1/15 identity tests and
  runner-image-build-smoke.

Evidence-Source: OCC#3607
Evidence-Ticket: OMN-13942
@jonahgabriel
jonahgabriel enabled auto-merge July 5, 2026 03:31
test_release_backmerge_preserves_runner_identity_lock froze the pre-regen
identity/shared_env digests. The two new node entry-points in pyproject.toml
rebind the runner-image identity (binding-not-label by design), so update the
frozen values to the regenerated, build-proven (runner-image-build-smoke pass)
digests bc29a1.../d11587...

Evidence-Source: OCC#3607
Evidence-Ticket: OMN-13942
@jonahgabriel
jonahgabriel added this pull request to the merge queue Jul 5, 2026
@jonahgabriel
jonahgabriel removed this pull request from the merge queue due to a manual request Jul 5, 2026
@jonahgabriel
jonahgabriel added this pull request to the merge queue Jul 5, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Jul 5, 2026
@jonahgabriel
jonahgabriel enabled auto-merge July 5, 2026 04:51
@jonahgabriel
jonahgabriel disabled auto-merge July 5, 2026 05:13
@jonahgabriel
jonahgabriel enabled auto-merge July 5, 2026 05:13
@jonahgabriel
jonahgabriel added this pull request to the merge queue Jul 5, 2026
Merged via the queue into dev with commit 6efa8f9 Jul 5, 2026
170 of 176 checks passed
@jonahgabriel
jonahgabriel deleted the jonah/omn-13942-runner-fleet-maintain-increment1 branch July 5, 2026 06:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant