feat: extend daemon model with daemon status tracking and list support

- Add daemon status tracking with state and metrics (idle, running, error)
- Implement pfhub daemon list CLI command for viewing registered daemons
- Extend auth schema and service for daemon lifecycle management
- Add postgres repository queries for daemon state persistence
- Expand test coverage for daemon registry and status transitions
- Update rel-006 release document with daemon model verification results
- Support daemon graceful shutdown and error recovery patterns
This commit is contained in:
2026-05-14 06:29:52 +00:00
parent e6b6a8715b
commit 3bb3ff547d
9 changed files with 298 additions and 17 deletions
+46 -1
View File
@@ -67,7 +67,18 @@ class _DaemonRepo:
return token
def get_service_token_by_name(self, name: str) -> AuthToken | None:
return next((t for t in self._tokens.values() if t.name == name and t.token_type == "service"), None)
return next(
(
t
for t in sorted(
self._tokens.values(),
key=lambda token: token.created_at or datetime.min.replace(tzinfo=timezone.utc),
reverse=True,
)
if t.name == name and t.token_type == "service" and t.revoked_at is None
),
None,
)
def revoke_auth_token(self, user_id: str, token_id: str) -> None:
token = self._tokens.get(token_id)
@@ -142,6 +153,13 @@ class _DaemonRepo:
self._tokens[new_token.id] = new_token
return raw, new_token
def rebind_service_token(self, name: str, daemon_id: str) -> AuthToken:
existing = self.get_service_token_by_name(name)
if existing is None:
raise ValueError(f"서비스 토큰을 찾을 수 없습니다: {name}")
existing.daemon_id = daemon_id
return existing
def _repo_with_user(username: str = "alice") -> _DaemonRepo:
repo = _DaemonRepo()
@@ -284,3 +302,30 @@ def test_rotate_nonexistent_token_raises() -> None:
with pytest.raises(ValueError, match="nonexistent"):
service.rotate_service_token("nonexistent")
def test_rebind_service_token_updates_daemon_id_without_changing_runtime_id() -> None:
repo = _repo_with_user()
source_daemon = repo.create_daemon("user-1", daemon_slug="alpha-bot")
target_daemon = repo.create_daemon("user-1", daemon_slug="beta-bot")
service = _service(repo)
issued = service.issue_service_token("my-token", "alice", daemon_id=source_daemon.id)
previous_daemon_id, rebound = service.rebind_service_token("my-token", target_daemon.id)
assert previous_daemon_id == source_daemon.id
assert rebound.daemon_id == target_daemon.id
assert rebound.runtime_id == issued.auth_token.runtime_id
def test_rebind_service_token_rejects_daemon_from_other_user() -> None:
repo = _repo_with_user()
repo._users["user-2"] = User(id="user-2", username="bob", permission="read")
source_daemon = repo.create_daemon("user-1", daemon_slug="alpha-bot")
foreign_daemon = repo.create_daemon("user-2", daemon_slug="bob-bot")
service = _service(repo)
service.issue_service_token("my-token", "alice", daemon_id=source_daemon.id)
with pytest.raises(ValueError, match="해당 토큰 사용자에 속하지 않습니다"):
service.rebind_service_token("my-token", foreign_daemon.id)