feat: implement full-platform STT API (v2.3 consensus plan)

Batch+realtime transcription API: faster-whisper engine w/ EngineOwner
(single GPU owner, OOM downgrade chain, persisted attempted_profiles),
hardware-adaptive device manager (T0-T3 VRAM tiers), Redis/in-proc job
queue w/ leases + crash recovery, postprocess (glossary/rules/LLM w/
egress guard), privacy-first result store (UUID keys, source deleted
after transcribe), retention sweeper, API-key auth (HMAC digests,
scopes, job ownership), WebSocket realtime lane (LocalAgreement),
CLI (detect/transcribe/bench/serve/key), Docker, benchmark runner.

127 mock-based tests pass; ruff clean. Includes verification checklist
and autoplan review notes.
This commit is contained in:
2026-08-12 16:01:21 +09:00
parent b7c30f8b71
commit 7327145d7a
82 changed files with 7351 additions and 0 deletions
+86
View File
@@ -0,0 +1,86 @@
"""KeyStore — 다이제스트 인증, 스코프, 키 생성 테스트."""
from __future__ import annotations
import json
import pytest
from luke_scribe.api.deps import KeyStore, Principal
from luke_scribe.config import Settings
from luke_scribe.errors import AuthError, ScopeDenied
@pytest.fixture
def settings(tmp_path) -> Settings:
return Settings(
_env_file=None,
api_keys="key-one,key-admin:admin,transcribe",
api_key_file=str(tmp_path / "api_keys.json"),
)
class TestKeyStoreAuth:
def test_plain_key_authenticates(self, settings: Settings):
ks = KeyStore(settings)
p = ks.authenticate("key-one")
assert p.key_id.startswith("k-key-one")
assert "transcribe" in p.scopes
def test_scoped_key(self, settings: Settings):
ks = KeyStore(settings)
p = ks.authenticate("key-admin")
assert {"admin", "transcribe"} <= p.scopes
def test_wrong_key_rejected(self, settings: Settings):
ks = KeyStore(settings)
with pytest.raises(AuthError):
ks.authenticate("key-wrong")
def test_empty_key_rejected(self, settings: Settings):
ks = KeyStore(settings)
with pytest.raises(AuthError):
ks.authenticate(None)
def test_digest_only_on_disk(self, settings: Settings, tmp_path):
"""평문 키는 저장하지 않고 다이제스트만 파일에 기록 (Eng P14)."""
created = KeyStore(settings).create_key(
scopes=["transcribe"], save_path=settings.api_key_file
)
raw = created["key"]
data = json.loads(tmp_path.joinpath("api_keys.json").read_text())
assert raw not in json.dumps(data)
entry = data["keys"][0]
assert entry["digest"] != raw
assert len(entry["digest"]) == 64 # sha256 hex
def test_file_keys_loaded(self, settings: Settings, tmp_path):
ks = KeyStore(settings)
created = ks.create_key(scopes=["transcribe"], save_path=settings.api_key_file)
# 새 인스턴스가 파일에서 로드
ks2 = KeyStore(settings)
p = ks2.authenticate(created["key"])
assert "transcribe" in p.scopes
class TestPrincipalScopes:
def test_require_scope_ok(self):
p = Principal(key_id="k-1", scopes={"transcribe"})
p.require_scope("transcribe") # no raise
def test_require_scope_denied(self):
p = Principal(key_id="k-1", scopes={"transcribe"})
with pytest.raises(ScopeDenied):
p.require_scope("admin")
def test_require_scope_empty(self):
p = Principal(key_id="k-1", scopes=set())
with pytest.raises(ScopeDenied):
p.require_scope("transcribe")
class TestDigest:
def test_same_key_same_digest(self, settings: Settings):
ks = KeyStore(settings)
assert ks._digest("abc") == ks._digest("abc")
assert ks._digest("abc") != ks._digest("abd")