feat(worker-pool): Registry-1B Pull 활성화 (auth + worker_jobs + 5 endpoint)

worker-pool-policy §B 1B 영역 완료. 1A scaffold (mig 270~274 + 503 stub) 위에:
- mig 275/276: worker_jobs (status CHECK + user_id=owner) + pending partial index
- create_laptop_worker_bot_token + require_worker_user dependency (voice-memo 동형)
- /internal/worker/{register,heartbeat,claim,result,drain} 5 endpoint 실 구현
- /claim FOR UPDATE SKIP LOCKED + 204 body 0
- /result 소유권 검증 (worker_id 매칭, 404) + failed 재시도 (attempts/max)
- explicit failure 시 request.result 무시 (DB result NULL 유지)
- 테스트 22 항목 7 파일

policy §B.2 5 invariant 보존: voice-memo wrapper 변경 0, drain advisory,
result raw JSONB, ProcessingQueue 무변경, 운영 자동 분기 변경 0.

활용처 (recap context + /jobs/recap + payload 100KB guard) = Registry-1C 영역.
stale recovery / 노트북 client / canonical promote = Notebook-Pilot-1 영역.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-05-19 08:53:57 +09:00
parent acd29b963e
commit f60d6e52fc
17 changed files with 1562 additions and 64 deletions
+29
View File
@@ -0,0 +1,29 @@
-- 2026-05-20 PR-Worker-Pool-Registry-1B: worker_jobs 테이블 신규.
-- worker-pool-policy §8 방향 (b) — ProcessingQueue 무변경, worker 전용 큐 분리.
-- §8 결정: document_id 컬럼 의도적 부재. memo/event recap + future code_review/long_summary
-- /repo_analysis 모두 document 단위 아님. context 는 payload JSONB 안에.
-- user_id 의미 = job owner user_id (실 사용자). worker bot 의 user_id 가 아님.
-- 1C recap context 가 user_id 기준 memos/events JOIN. worker 인증은 worker_id + JWT 별도.
-- §B.2 invariant 3: result = raw JSONB only (canonical promote 0, Notebook-Pilot-1 영역).
-- §B.2 invariant 4: ProcessingQueue 무변경 — 이 테이블은 document FK 없음.
-- claim 방식 = SELECT FOR UPDATE SKIP LOCKED (queue_consumer 의 단순 UPDATE 와 다름).
-- status CHECK constraint = 신규 table 부터 enum 문자열 drift 차단.
-- explicit /result failed 재시도 (attempts < max_attempts → pending 복귀) = 1B 영역.
-- stale recovery (timeout 기반) = Notebook-Pilot-1 영역.
CREATE TABLE IF NOT EXISTS worker_jobs (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
job_type TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending', 'processing', 'completed', 'failed')),
worker_id TEXT REFERENCES worker_capabilities(worker_id) ON DELETE SET NULL,
payload JSONB NOT NULL DEFAULT '{}'::jsonb,
result JSONB,
error_message TEXT,
attempts SMALLINT NOT NULL DEFAULT 0,
max_attempts SMALLINT NOT NULL DEFAULT 3,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
claimed_at TIMESTAMPTZ,
completed_at TIMESTAMPTZ
);
+7
View File
@@ -0,0 +1,7 @@
-- 2026-05-20 PR-Worker-Pool-Registry-1B: worker_jobs claim 핫패스 partial index.
-- claim 쿼리: WHERE status='pending' AND job_type=$1 ORDER BY created_at LIMIT 1 FOR UPDATE SKIP LOCKED.
-- partial index = pending row 만 색인 (테이블 성장과 무관 — completed/failed row 누적해도 인덱스 크기 일정).
CREATE INDEX IF NOT EXISTS idx_worker_jobs_pending_type
ON worker_jobs (job_type, created_at)
WHERE status = 'pending';