0bc734c020
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>
183 lines
5.5 KiB
Python
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",
|
|
)
|