feat(memory): the morning seed carries the process start and the code freshness
This commit is contained in:
1 parent
9c3262cba8
commit
27c4e68b6e
2 files changed
+139
-3
No files matched your search
@@ -2,6 +2,9 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from beaver_gateway.frontends.turn_record import slugify
|
||||
@@ -15,12 +18,11 @@ from beaver_agent.vault import (
|
||||
PORTRAIT,
|
||||
STATE,
|
||||
VAULT,
|
||||
ZONE,
|
||||
read,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
from beaver_gateway.conversations.distill import DistillContext
|
||||
from beaver_gateway.conversations.rotation import HandoutContext
|
||||
from beaver_gateway.conversations.texts import NewDayContext, SeedContext
|
||||
@@ -32,8 +34,67 @@ def latest_handout() -> Path | None:
|
||||
return files[-1] if files else None
|
||||
|
||||
|
||||
PROC_INIT = Path("/proc/1")
|
||||
"""mtime PID 1 - момент старта контейнера, проверено в бою."""
|
||||
PACKAGE = Path(__file__).resolve().parent.parent
|
||||
"""Корень пакета: свежесть кода меряем от себя, а не от пути в контейнере."""
|
||||
|
||||
DEPLOY_MARK = "Выкат: процесс поднят {start} ({ago}), свежесть кода {code} - {verdict}."
|
||||
CODE_OLDER = "код старше старта, значит крутится то, что выкатывали"
|
||||
CODE_NEWER = "код НОВЕЕ старта: правка на диске есть, рестарта после неё не было"
|
||||
SAME_MINUTE = "выкат и правка в одну минуту"
|
||||
HOUR = 3600
|
||||
|
||||
|
||||
def _ago(seconds: float) -> str:
|
||||
"""Целыми часами, а меньше часа - минутами."""
|
||||
if seconds < HOUR:
|
||||
return f"{int(seconds // 60)} мин назад"
|
||||
return f"{int(seconds // HOUR)} ч назад"
|
||||
|
||||
|
||||
def _clock(stamp: float) -> str:
|
||||
return f"{datetime.fromtimestamp(stamp, ZONE):%Y-%m-%d %H:%M}"
|
||||
|
||||
|
||||
def deploy_mark(proc: Path = PROC_INIT, package: Path = PACKAGE) -> str | None:
|
||||
"""Когда подняли процесс и насколько свеж код, который в нём крутится.
|
||||
|
||||
Две цифры рядом: mtime PID 1 - старт контейнера, последний изменённый
|
||||
`*.py` пакета - свежесть исходников. Порознь каждая врёт: код бывает
|
||||
новее старта, и тогда правка на диске есть, а рестарта после неё не было.
|
||||
Любая осечка (нет `/proc`, нет прав, пустой пакет) - None: сид не имеет
|
||||
права упасть из-за этой отметки.
|
||||
"""
|
||||
try:
|
||||
started = proc.stat().st_mtime
|
||||
sources = [
|
||||
path.stat().st_mtime
|
||||
for path in package.rglob("*.py")
|
||||
if "__pycache__" not in path.parts
|
||||
]
|
||||
if not sources:
|
||||
return None
|
||||
code = max(sources)
|
||||
gap = code - started
|
||||
if abs(gap) < 60:
|
||||
verdict = SAME_MINUTE
|
||||
elif gap > 0:
|
||||
verdict = CODE_NEWER
|
||||
else:
|
||||
verdict = CODE_OLDER
|
||||
return DEPLOY_MARK.format(
|
||||
start=_clock(started),
|
||||
ago=_ago(time.time() - started),
|
||||
code=_clock(code),
|
||||
verdict=verdict,
|
||||
)
|
||||
except (OSError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def seed_body(ctx: SeedContext) -> str | None:
|
||||
"""Утренний сид: последний хендаут, шапка состояния, портрет Бобра."""
|
||||
"""Утренний сид: хендаут, шапка состояния, портрет Бобра, отметка о выкате."""
|
||||
if ctx.seed != "morning":
|
||||
return None
|
||||
handout = latest_handout()
|
||||
@@ -54,6 +115,9 @@ def seed_body(ctx: SeedContext) -> str | None:
|
||||
"Портрет Бобра (шапка "
|
||||
f"`{PORTRAIT.relative_to(VAULT)}`, лента - там же):\n{portrait}"
|
||||
)
|
||||
mark = deploy_mark()
|
||||
if mark:
|
||||
parts.append(mark)
|
||||
return "\n\n".join(parts)
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user