feat(worker-pool): Registry-1A scaffold — worker_capabilities/heartbeats + /internal/worker/* 5 endpoint 503 stub

PR-Worker-Pool-Registry-1A (scaffold only, no runtime activation).

신규:
- migrations/270~274 (1 statement/1 file 강제): worker_capabilities + 2 idx + worker_heartbeats + 1 idx
- app/models/worker_pool.py: WorkerCapability + WorkerHeartbeat ORM (queue.py 패턴)
- app/api/internal_worker.py: 5 endpoint 모두 _stub_503() — register/heartbeat/claim/result/drain
- tests/test_internal_worker_stub.py: 503 응답 smoke (inline ASGI client, DB 의존 0)

수정:
- app/main.py: import + include_router 각 1줄 (prefix=/internal/worker, internal_study 일관)

scaffold-first + phase-gate-material-first 강제 (worker-pool-policy §1, §12):
- 인증 dependency 0 (1B 에서 JWT + require_worker_user)
- ProcessingQueue 변경 0 (방향 b: worker_jobs 별 table = 1B)
- LLM 호출 0 / canonical DB 변경 0 / 운영 자동 분기 0

회귀 0 (1주 안전망 = app/main.py.pre-registry-1a.20260518).

plan: ~/.claude/plans/floofy-exploring-mitten.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-05-18 20:24:59 +09:00
parent 406b810e28
commit bbd92a840a
9 changed files with 183 additions and 0 deletions
@@ -0,0 +1,19 @@
-- 2026-05-18 PR-Worker-Pool-Registry-1A: worker_capabilities 테이블 신규.
-- 노트북 등 worker 가 register 시 advertise 한 device/tier/capability 1 row UPSERT.
-- worker-pool-policy §1 invariant: 노트북 = optional session worker (24/7 X, extraction backbone X).
-- 1A 단계: schema only (라우트 5개 모두 503 stub). register 활성화 = 1B (laptop-worker-bot user).
-- enum 컬럼 (worker_class, tier) 도 TEXT — policy §3,§5 가 capability bundle 을 docs 결정으로 못박음.
-- user_id NOT NULL: 1A 에서는 503 stub 이라 runtime INSERT 없음. 첫 INSERT = 1B JWT 활성화와 동시점.
CREATE TABLE IF NOT EXISTS worker_capabilities (
worker_id TEXT PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
device_label TEXT NOT NULL,
worker_class TEXT NOT NULL,
tier TEXT NOT NULL,
capabilities JSONB NOT NULL DEFAULT '[]'::jsonb,
models_loaded JSONB NOT NULL DEFAULT '[]'::jsonb,
endpoint TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
last_registered_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
@@ -0,0 +1,4 @@
-- 2026-05-18 PR-Worker-Pool-Registry-1A: worker_capabilities tier 인덱스.
-- claim 쿼리 (1B) 가 capability + tier 로 필터링 — 인덱스로 가속.
CREATE INDEX IF NOT EXISTS idx_worker_capabilities_tier
ON worker_capabilities (tier);
@@ -0,0 +1,4 @@
-- 2026-05-18 PR-Worker-Pool-Registry-1A: worker_capabilities worker_class 인덱스.
-- worker_class ('laptop'/'macmini'/'cloud') 로 필터링 시 가속.
CREATE INDEX IF NOT EXISTS idx_worker_capabilities_class
ON worker_capabilities (worker_class);
@@ -0,0 +1,17 @@
-- 2026-05-18 PR-Worker-Pool-Registry-1A: worker_heartbeats 테이블 신규.
-- worker 가 30~60s 주기로 status 발신 (worker-pool-policy §7).
-- append-only. 운영 alive 판정 = (worker_id 별 최신 row).heartbeat_at > now() - interval '10 min'.
-- retention 정책은 별 PR (PR-LLM-Router-Observability-1 또는 동등).
-- 1A 단계: schema only (heartbeat endpoint 503 stub). 활성화 = 1B.
-- current_job_id 는 1A 단계에서 plain BIGINT (FK 없음). 1B 의 worker_jobs 마이그레이션 후 FK 추가 검토.
CREATE TABLE IF NOT EXISTS worker_heartbeats (
id BIGSERIAL PRIMARY KEY,
worker_id TEXT NOT NULL REFERENCES worker_capabilities(worker_id) ON DELETE CASCADE,
heartbeat_at TIMESTAMPTZ NOT NULL DEFAULT now(),
status TEXT NOT NULL,
current_job_id BIGINT,
battery TEXT,
thermal TEXT,
raw_payload JSONB NOT NULL DEFAULT '{}'::jsonb
);
+4
View File
@@ -0,0 +1,4 @@
-- 2026-05-18 PR-Worker-Pool-Registry-1A: worker_heartbeats latest-row 조회 인덱스.
-- alive 판정 SQL: SELECT ... ORDER BY heartbeat_at DESC LIMIT 1 per worker_id.
CREATE INDEX IF NOT EXISTS idx_worker_heartbeats_worker_at
ON worker_heartbeats (worker_id, heartbeat_at DESC);