Files
cleveragents-core/tools/_recent_push_cache.py
T
drew 0bc734c020 style: ruff format the controller-state-machine branch (288 files)
Applies `ruff format` to the accumulated formatting debt on this branch.
Formatting-only — no behavioral changes. Required for CI/lint's format
gate (`nox -s format -- --check`), which the branch was failing on 288
tracked files that drifted from ruff's canonical style.

In-progress WIP files are intentionally excluded so this commit stays a
clean formatting-only diff.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-20 00:09:17 -04:00

183 lines
5.5 KiB
Python

"""Per-PR record of the dispatcher's most recent push event.
C4 (2026-05-13): a PR that's still in failing-CI state may be picked
up by the implementer again on the next cycle. If a prior cycle just
pushed a fix (head_sha_advanced=True), the new cycle's worker should
know that — otherwise it re-does the same compliance work and emits
``resolved`` thinking it just fixed the same problem (PR #28 cycle 2
2026-05-13 failure mode).
Storage: per-PR JSON file under ``/tmp/cleveragents-recent-push-cache/``.
Each record carries:
- ``pushed_at`` — ISO timestamp of the push
- ``pushed_head_sha`` — the SHA the dispatcher pushed
- ``cycle_id`` — the producing cycle's id (for cross-reference with
telemetry rows)
- ``terminal_state`` — terminal_state from that cycle
- ``outcome`` — outcome string from that cycle
The dispatcher's prefetch reads this record and surfaces it to the
worker via the sentinel's ``recent_implementer_push`` field when the
record's ``pushed_head_sha`` matches the PR's current head_sha AND
the push was within the staleness window (default 1 hour).
Stale records are discarded — a push from 25h ago shouldn't bias the
current cycle's decision-making.
"""
from __future__ import annotations
import datetime as _dt
import json
import logging
import os
from pathlib import Path
from typing import Any
_logger = logging.getLogger("recent_push_cache")
SCHEMA_VERSION = 1
_DEFAULT_CACHE_DIR = Path("/tmp/cleveragents-recent-push-cache")
_CACHE_DIR_ENV = "IMPLEMENTER_DISPATCHER_RECENT_PUSH_CACHE_DIR"
# 1 hour: if the cached push is older than this, the worker
# probably shouldn't be biased by it — either CI has had time to
# react (in which case the new cycle's CI state is the signal) or
# something else pushed in between.
_DEFAULT_MAX_AGE_S = 3600
_MAX_AGE_ENV = "IMPLEMENTER_DISPATCHER_RECENT_PUSH_MAX_AGE_S"
def cache_dir() -> Path:
return Path(os.environ.get(_CACHE_DIR_ENV) or str(_DEFAULT_CACHE_DIR))
def cache_path(pr_number: int) -> Path:
return cache_dir() / f"pr-{int(pr_number)}.json"
def _max_age_s() -> int:
raw = os.environ.get(_MAX_AGE_ENV)
if raw:
try:
return max(0, int(raw))
except ValueError:
pass
return _DEFAULT_MAX_AGE_S
def _now() -> str:
return _dt.datetime.now(_dt.timezone.utc).isoformat()
def record_push(
pr_number: int,
pushed_head_sha: str,
*,
cycle_id: str = "",
terminal_state: str = "",
outcome: str = "",
) -> None:
"""Write a per-PR record of the push event. Called from the
dispatcher's post-session action when head_sha_advanced=True."""
if not pushed_head_sha:
return
target = cache_path(int(pr_number))
tmp = target.with_suffix(target.suffix + ".tmp")
payload = {
"schema_version": SCHEMA_VERSION,
"pr_number": int(pr_number),
"pushed_at": _now(),
"pushed_head_sha": pushed_head_sha,
"cycle_id": cycle_id,
"terminal_state": terminal_state,
"outcome": outcome,
}
try:
target.parent.mkdir(parents=True, exist_ok=True)
tmp.write_text(
json.dumps(payload, indent=2),
encoding="utf-8",
)
tmp.replace(target)
except (OSError, TypeError, ValueError) as exc:
_logger.warning(
"recent-push cache write failed for PR #%s: %s",
pr_number,
exc,
)
try:
tmp.unlink(missing_ok=True)
except OSError:
pass
def lookup_recent_push(
pr_number: int,
current_head_sha: str,
) -> dict[str, Any] | None:
"""Return the cached push record IF it matches the current head
AND is within the staleness window. Otherwise ``None``.
Matching on head_sha is what makes this useful: the cached push
is only relevant if the PR's HEAD is still the SHA we pushed.
A subsequent human force-push or merge invalidates the record.
"""
target = cache_path(int(pr_number))
if not target.exists():
return None
try:
text = target.read_text(encoding="utf-8")
payload = json.loads(text)
except (OSError, ValueError) as exc:
_logger.warning(
"recent-push cache read failed for PR #%s: %s",
pr_number,
exc,
)
return None
if not isinstance(payload, dict):
return None
if payload.get("schema_version") != SCHEMA_VERSION:
return None
if payload.get("pushed_head_sha") != current_head_sha:
# The PR's HEAD has moved since we pushed; the record is
# no longer relevant.
return None
try:
pushed_at = _dt.datetime.fromisoformat(payload.get("pushed_at", ""))
age_s = (_dt.datetime.now(_dt.timezone.utc) - pushed_at).total_seconds()
if age_s > _max_age_s():
return None
except (ValueError, TypeError):
return None
return payload
def invalidate(pr_number: int) -> None:
"""Remove the per-PR record. Called when a cycle ends with a
new push (which will write a fresh record) or when the
dispatcher knows the record is stale (e.g. on cleanup).
Idempotent."""
target = cache_path(int(pr_number))
try:
target.unlink(missing_ok=True)
except OSError as exc:
_logger.warning(
"recent-push cache invalidate failed for PR #%s: %s",
pr_number,
exc,
)
__all__ = (
"SCHEMA_VERSION",
"cache_dir",
"cache_path",
"record_push",
"lookup_recent_push",
"invalidate",
)