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
10 changes: 5 additions & 5 deletions autopilot/config_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import copy
import os
from typing import Any, Optional
from typing import Any

DEFAULT_CONFIG_PATH = os.environ.get("AUTOPILOT_CONFIG_PATH", "autopilot/config.yaml")

Expand Down Expand Up @@ -42,7 +42,7 @@
},
}

_cache: Optional[dict[str, Any]] = None
_cache: dict[str, Any] | None = None


def _deep_merge(base: dict[str, Any], over: dict[str, Any]) -> dict[str, Any]:
Expand All @@ -54,7 +54,7 @@ def _deep_merge(base: dict[str, Any], over: dict[str, Any]) -> dict[str, Any]:
return base


def load_config(path: Optional[str] = None) -> dict[str, Any]:
def load_config(path: str | None = None) -> dict[str, Any]:
"""Load and cache the merged config. Falls back to DEFAULTS if PyYAML
is unavailable or the file is missing.

Expand All @@ -73,15 +73,15 @@ def load_config(path: Optional[str] = None) -> dict[str, Any]:
pass
else:
if os.path.exists(path):
with open(path, "r", encoding="utf-8") as f:
with open(path, encoding="utf-8") as f:
loaded = yaml.safe_load(f) or {}
if isinstance(loaded, dict):
_deep_merge(cfg, loaded)
_cache = cfg
return cfg


def get_debounce_hours(status: str) -> Optional[int]:
def get_debounce_hours(status: str) -> int | None:
"""Hours to suppress a re-raise, or None for never-repost."""
policy = load_config()["recommendation_debounce"]["repost_policy"].get(status, {})
if policy.get("never_repost"):
Expand Down
25 changes: 0 additions & 25 deletions autopilot/decisions/ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,6 @@ def _load(path: str = DEFAULT_LEDGER_PATH) -> list[dict]:
continue
return out


def _next_seq(path: str = DEFAULT_LEDGER_PATH) -> int:
"""Monotonic append counter — breaks sub-second timestamp ties so the
latest-written entry wins in latest_match()."""
return len(_load(path))


def _lock(fd):
"""Exclusive lock the ledger file handle (POSIX). No-op elsewhere."""
if _HAS_FCNTL:
Expand All @@ -76,24 +69,6 @@ def _unlock(fd):
fcntl.flock(fd.fileno(), fcntl.LOCK_UN)


def _append(entry: dict, path: str = DEFAULT_LEDGER_PATH) -> None:
"""Append a JSON line under an exclusive file lock.

Holds the lock across _next_seq + write when called via _append_locked
so concurrent writers can't collide on sequence numbers or interleave
large entries.
"""
os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
with open(path, "a", encoding="utf-8") as f:
_lock(f)
try:
f.write(json.dumps(entry, ensure_ascii=False) + "\n")
f.flush()
os.fsync(f.fileno())
finally:
_unlock(f)


def _append_with_seq(entry: dict, path: str = DEFAULT_LEDGER_PATH) -> dict:
"""Atomic: assign seq under lock, then append. Prevents _next_seq races."""
Comment on lines 72 to 73
os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
Expand Down
1 change: 1 addition & 0 deletions autopilot/tests/test_recommendation_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ def test_transition_rejects_unknown_status():
def test_concurrent_writers_do_not_lose_entries():
"""Parallel record() calls must all land — file locking prevents races."""
import threading

from decisions.ledger import record

ledger = tempfile.NamedTemporaryFile(suffix=".jsonl", delete=False)
Expand Down
Loading