talklog — 작업 규칙 (rules)
📑 인덱스: 🆕 신규 규칙 → §1 로컬 파일 전사(stt_file.py)
규칙 = 이 파일 / 상태·이력 =
handoff.md. 전역 규칙은C:\dev\seedchain\rules\가 우선한다.
🆕 신규
- (2026-10-08) [코디] claude 호출 5곳(분류·통화 정리본·야간·통합현장·live 정리본) =
C:\dev\tools\cody_ask.py경유(API 크레딧 우선·하한 $10 밑 구독 폴백). 노력도는 옛 호출이 사용자 설정에서 받던 값(opus xhigh · sonnet medium)을 명시한다. ⚠live 정리본usage(화면 토큰 표)는 API 경로만 원장 값이 들어가고 구독 폴백 경로는 None = 기록 안 됨(실패 아님). ⚠fusion_post.py --selftest는clean_rows가 운영 로그C:\dev\talklog\data\_fusion_log.jsonl에row_dropped1줄을 쓴다(LOG 절대경로 · 이관 전부터) — 위임 사본에서 돌려도 운영 로그에 붙는다. - (2026-10-08) [코디] 🔴
nowin이 pythonw 워커에서 꺼져 있었다 — 분류claude.cmd호출마다 Windows Terminal 창(제목 claude)이 떴다. talklog_server 가 워커를subprocess.run(capture_output=True)로 띄워 워커 stdout = 파이프 → 옛_windowless()가 「stdout 있으면 건드리지 않음」에서 False. 수정 =C:\dev\tools\nowin.py에sys.executable이 pythonw.exe 면 무조건 적용(pythonw 는 콘솔이 없어 자식이 반드시 새 창). 검사 = pythonw 로import nowin→APPLIED=True+cmd /c echo캡처 정상, python.exe 는 False(출력 유지). 26.8.8 「손자까지 봐라」 절의 후속. - (2026-09-22) [페페 상시지시] 🔴LIVE STT 를 고칠 때는 «폰 어플도 같이» 고치는 게 기본이다. 앱 소스는 여기 있다 =
C:\dev\talklog\livestt-app\(Kotlin · Gradle 8.7 ·com.poin.livestt). 26.9.19 규칙의 「APK 소스가 이 리포에 없어」는 틀린 전제였다(아래 절 정정). 판정 순서 = ①화면(live\static\*)만 고쳤나 → 앱은 손댈 게 없다(앱은 WebView 로Config.BASE=https://poin-mini.tailc5d3c1.ts.net:8446를 «서버에서» 로드한다 → 새 화면이 자동 반영, APK 재빌드 불필요) ②웹↔네이티브 계약(nativeCmd/livenative:*)이나 마이크·업로드·알림·권한이 걸리나 → 앱도 같이 고치고 재빌드·배포. 빌드 =JAVA_HOME=/c/dev/tools/android-build/jdk/jdk-17.0.19+10 /c/dev/tools/gradle-8.7/bin/gradle -p /c/dev/talklog/livestt-app :app:assembleDebug --no-daemon(gradlew 없음 ·local.propertiessdk.dir=C:/dev/tools/android-sdk). 배포 = 새 APK 를C:\dev\talklog\live\apk\livestt.apk로 넣고 같은 폴더version.json의versionCode를 올린다 → 앱이 열릴 때/version보고/app.apk받아 설치(사용자 1탭, adb 불필요). ⚠서명이 v0.3 와 같아야 자가업데이트가 된다 — 빌드 후apksigner verify --print-certs로 SHA-256e3d73c60…대조(다르면 uninstall→install 이라 폰 조작이 커진다). ⚠웹에서 새 네이티브 명령을 보낼 땐 옛 APK 가 그 명령을 모른 채 무시해도 동작이 깨지지 않게 짠다(서버가 진실, 네이티브는 보강). - (2026-09-22) [코디] 일시정지 = 이제 앱도 «안 올린다»(v0.4). 26.9.19 우회(서버가
add_pcm에서 버림)는 그대로 두고, 앱에ACTION_PAUSE/RESUME을 넣어 캡처 루프가 읽은 버퍼를 버린다(파일에 안 쓰니 업로드 조각이 아예 안 생긴다 = 서버의 「없던 일」 규약과 뜻이 같아진다). ⚠통화중(gapActive)과 다르다 — 통화는 0으로 채워 타임라인을 지키고, 일시정지는 안 쓴다. 곁들인 것 = 포그라운드 알림이 「일시정지 중 — 기록하지 않음」으로 바뀌고 알림에서 재개/일시정지가 눌린다(잊고 방치하는 게 이 기능 최대 위험) ·livenative:state에paused추가 · 웹togglePause가 서버 먼저, 그 다음nativeCmd(서버 응답이 진실). - (2026-09-21) [코디] 🔴
claude -p를 리포 폴더에서 부르면 매 호출에 «껍데기» 3만 토큰이 붙는다 — 정리본 토큰의 절반은 회의 내용이 아니었다. 실측(빈 질문 1개) =C:\dev\talklog\live에서 호출 34,382 tok →cwd=%TEMP%24,520 →--strict-mcp-config --tools "" --setting-sources ""까지 2,405. 실린 것 =C:\dev\CLAUDE.md(21.8KB) + MCP 도구 스키마 + 사용자·프로젝트 설정. 🔑판정 기준 = 그 호출이 도구를 하나라도 쓰나? 정리·요약·분류처럼 순수 텍스트면 3개 플래그 +cwd를 무조건 붙인다(헤드리스를 쓰는 다른 도구 전부 해당 — [[headless-claude-cwd-leaks-claude-md]] 는 CLAUDE.md 만 말했고 도구 스키마는 몰랐다). - (2026-09-21) [코디] 🔴«주기적 LLM 갱신»에서 비싼 건 주기가 아니라 «매번 전문을 다시 보내는 것»이다 — 그리고 30초 주기는 claude -p 로는 애초에 불가능하다. 전사 전문을 매 호출에 넣으면 총량이 O(n²)다(60분 시점 회당 69,286 tok). 증분(이전 정리본 + 그 뒤 새 발화 → 갱신본 전체)으로 바꾸면 회의 길이와 무관하게 평탄(10,068 tok). 그리고
claude -p는 호출 자체가 44~92초라 주기를 30초로 줄일 수가 없다 → 실시간 갱신은 Geminigemini-3.1-flash-lite(무료 쿼터·3.5초, 같은meeting_system.txt로 형식·화자 실명 추출까지 정확) 이 맡고 Claude 는 버튼·마감에만 쓴다 = 1시간 회의 Claude 소비 1.61M → 40~70k. 증분은 120회 누적되면 표류하니 15분마다 1회 전문 재동기화(무료 엔진이라 공짜, 코덱스 상의 권고). ❌Groq 는 무료 TPM 8,000 이라 전문 요청이 413 이고 30초 주기(2회/분)면 증분도 초과 — 후보에서 뺀다. 실패 시 정리본과last_notes_len을 안 건드린다(다음 주기에 더 큰 델타로 재시도 = 놓친 말이 영구히 새지 않는다). - (2026-09-20) [코디] 🔴정리본의 「화자 N명」은 측정값이 아니라 «상한값»이다 — 그리고 인원을 고정해도 안 고쳐진다. 26.9.20 아트조이 대표 미팅(65분·872발화, 실제 4명)이 「화자 6명」으로 나왔다. 6 =
TALK_MAX_SPK기본값이다 —cluster()는len(members) > cap인 동안 무조건 합치므로 안 갈리는 녹음은 항상 상한값이 나온다(=인원을 «셌다»고 읽으면 오해다). 🔑그리고 같은 wav 를 k=2~6 으로 고정 재군집한 실측이 처방을 뒤집었다 — 2:871 · 3:838 · 4:838 · 5:837 · 6:837 로 어느 k 에서도 96%가 한 군집이다. 원인은 인원 추정이 아니라 마이크 1개가 발표자 앞에 있어 원거리 화자의 임베딩이 안 갈리는 것(campplus 는 발화 경계로 자르지만 SNR·잔향까지 넘지는 못한다). ⛒「인원을 알면TALK_SPEAKERS로 고정」은 통화(2인 근접)에서만 성립하는 문장이다 — 회의 룸 녹음에는 처방이 아니다. 순서 = ①녹음 시작 화면에 참석자 수·이름 입력 → 제목·부제·정리본 프롬프트에 그 값을 쓴다(분리가 실패해도 틀린 숫자가 화면에 안 뜬다 = 대표 앞 시연 사고를 먼저 막는다) ②마이크를 테이블 중앙 or 회의용 마이크 ③정리본 LLM 이 문맥·호칭으로 배정(이름 추출 로직 재사용), 임베딩은 보조 ④POST /api/items/{ts}/diarize에k인자(diarize_blocking(wav, segs, k)→cluster(vecs, k=…)). 재측정 도구 =C:\dev\_work\artjoy-meeting\redi.py(live 서버 무손상·읽기만). 사례 =projects/artjoy/meeting-2026-09-20-minutes.md§5 C-10. - (2026-09-19) [코디] 🔴「일시정지」는 클라이언트가 아니라 «서버»가 끊어야 한다 — 폰 앱은 그 명령을 모른다. ⚠26.9.22 정정 = 「APK 소스가 이 리포에 없다」는 틀렸다(소스는
C:\dev\talklog\livestt-app\에 있다). 그래서 v0.4 에서 앱도 같이 고쳤다(위 26.9.22 절). 아래 서버 규약은 그대로 유효하다 — 서버가 진실이고 앱은 보강이다(옛 APK 도 안전하게 동작). ~~APK 소스가 이 리포에 없어nativeCmd({cmd:'pause'})를 새로 넣을 수 없다~~ = 그때는 앱이 일시정지를 모른 채POST /ingest로 계속 올렸다. 그러니 웹에서 마이크만 끊으면 앱 녹음에선 일시정지가 아예 안 먹는다. 규약 =Session.paused→add_pcm()이 들어온 소리를 버린다(wav·buf 둘 다 안 들어가니nrecv가 안 늘어 타임라인이 안 어긋난다) · 창구는POST /api/items/{ts}/pause(ws 가 아닌 HTTP — 폰 앱 녹음 중 PC 웹은watch로만 붙어 있는데 거기서도 눌러야 한다) · 결과는push_all로 녹음 소켓+구독자 전원에게. 곁들일 것 3개: ①일시정지 중엔STALL_SEC「소리가 끊겼다」 텔레그램을 안 울린다 ②paused를 항목 json 에 안 적는다(적으면 서버 재시작 때 «멈춘 채» 부활해 소리가 조용히 사라진다 — 세션 상태다) ③finalize진입에서 자동으로 푼다(안 풀면 꼬리 소리가 버려진다). 웹 경로는 거기 더해 보내지도 않는다(「멈췄다」는데 소리가 회선을 타면 안 된다). 자체검사 =C:\dev\talklog\live\_test_pause.py— 앱과 같은 ingest 경로로 올리며 버려지는 걸 wav 바이트로 본다(7초 올리고 3초 일시정지 → 최종 4.0초). - (2026-09-19) [코디] 🔴되돌릴 수 있는 것과 없는 것을 색으로 가르고, 「멈춰 있다」는 스크롤해도 안 사라지는 자리에 둔다. 일시정지 기능의 최대 위험은 켜 둔 걸 잊고 회의를 통째로 날리는 것이다. 처방 = ①정지 = 빨강 고정(되돌릴 수 없다) · 평상시 일시정지 = 중립 outline · 일시정지 중의 「▶ 재개」만 강조색(지금 눌러야 할 버튼이라고 색으로 소리친다) ②늘 보이는 신호 2개 = sticky 상단 점(빨강 펄스 → 주황 정지) + fixed 액션바 버튼 ③배너는 통화차단(빨강·자동복구)과 색도 자리도 가른다(둘 다 빨강이면 뭐가 문제인지 안 읽힌다) ④경과시간을 동결하고 「일시정지 경과」를 새로 센다(길어질수록 눈에 띈다) + 2분 30초에
navigator.vibrate1회. ⚠배너 2개가 동시에 뜨지 않게 상태를 배타로 묶어라. - (2026-09-19) [코디]
GET /api/items/{ts}는 녹음 시작 직후 404 였다 — 「살아 있는 세션이 정본」을 읽는 쪽에도 적용해야 한다. 항목 json 은save_lecture가 첫 전사 확정에야 쓴다. 그 전에 조회하면 디스크에 파일이 없어 404 다(api_patch_item은 진작 세션을 먼저 봤는데api_get_item만 안 봤다 — 규약이 한쪽에만 적용돼 있었다). 새 필드(paused등)를 화면에 노출할 때 읽기 경로가 세션을 보는지 반드시 확인할 것. - (2026-09-19) [코디] 🔴tailscale funnel 에 «사설 API 가 같이 사는 서버」를 걸면 안 된다 — funnel proxy 는 마운트 접두어를 떼고 넘긴다.
443 /mtg → 8445로 걸었으면https://…/mtg/api/items가 서버에/api/items로 도착해서 전 회의 목록·전사 전문·/ws(녹음 시작)·/ingest(wav 쓰기)가 통째로 인터넷에 열린다(접두어 제거는 당근에서 이미 실측된 사실 =projects/danggeun-ai-study/rules.mdL56). ⛒정규식·미들웨어로 거르지 말고 라우팅 표면 자체를 가른다 — 같은 프로세스 안에 공개 라우트만 가진 별도web.Application을 만들어AppRunner+TCPSite로 다른 포트(8448)에 띄우고(app.on_startup8줄) funnel 은 거기만 본다. 사설 API 는 «막은» 게 아니라 그 앱에 없다 — 부재가 가장 확실한 차단이다. 검증은 반드시 funnel 주소로 한다(/mtg/api/items= 404 여야 한다). 자체검사 =live_server.py --selftest-pub(35항목, 응답 키를 허용목록과 «정확히» 대조한다). - (2026-09-19) [코디] 🔴라이브 중인 항목의 json 에 필드를 직접 쓰면 2초 뒤 사라진다 —
Session을 거쳐 써라.save_lecture()는sess.to_item_dict()로 항목 json 을 통째로 다시 쓴다. 그래서 공개 토큰·토큰소비 장부를write_item_json으로만 박으면 다음 전사 확정(2초)에 조용히 지워진다(= 회의 중에 참석자 링크가 404 로 죽는다). 규약 = ①새 항목 필드는Session.__init__양쪽 분기 +to_item_dict()에 같이 넣는다(continue_from을 타므로 이어 녹음·ingest 복구도 자동 상속) ②쓰는 쪽은SESS_BY_TS로 세션을 먼저 찾아 세션에 박고save_lecture를 자기가 부른다(api_patch_item이 그 본). - (2026-09-19) [코디] 🔴정리본 비용은 「회당 고정 × 25초 주기」로 어림하면 틀린다 — 회당 비용은 «전사 길이에 비례»하고, 호출이 «동기»라 주기는 25초가 아니다. 실측(
C:\dev\talklog\live\_bench_notes_cost.py— 진짜 회의 전사를 길이별로 잘라 각 1회): 8분 시점 40,432 tok/$0.31/37초 → 24분 49,725/$0.42/61초 → 44분 60,787/$0.54/76초 → 60분 69,286 tok/$0.64/92초. ①누적 전사 전문이 매번 통째로 들어가 회당 비용이 회의 내내 2배로 커진다 ②notes_loop는await asyncio.sleep(NOTES_SEC)다음에 동기 호출이라 한 주기 =25초 + 호출시간(60~120초)→ 1시간 회의 = 43회(144회가 아니다). 합계 = 2.2M tok · $18.66 API 환산 · 구독 OAuth 라 실청구 0원. ⚠짧은 시험 전사로 잰 회당값은 고정 오버헤드뿐인 «바닥값»이라 그걸로 곱하면 안 된다 — 첫 보고에서 그 실수로 「100회·수십 달러」라고 과장했고 [페페] 지적으로 다시 쟀다. 기록 =usage·total_cost_usd→ 항목 jsonnotes_usage[]→ 화면(녹음=5분 구간·완료=총량). - (2026-09-19) [코디] 라이브 중엔 화자분리가 «없다» — 실시간 화자 이름은 정리본 LLM 이 호칭에서 뽑는 수밖에 없고, 그 함정은 「부르는 이름 = 듣는 사람」이다.
diarize_blocking은finalize에서만 돌아 라이브sess.spk는 비어 있다. 그래서meeting_system.txt에## 👥 화자별+### <이름>절을 두고 이름을 뽑게 했는데, 🔑한국어에서 호명은 대개 청자를 가리킨다(「김 대리님 어떻게 보세요?」 → 김 대리는 듣는 사람). 근거를 ①본인이 자기를 밝힌 발언 ②호명 직후 대답이 명백한 흐름 둘로 못박고, 확신 없으면 절을 안 만들게 했다. 화면엔 「추정이라 틀릴 수 있다」를 항상 붙인다. 실측 = 라벨 있는 전사·없는 전사 둘 다 3명 정확(라벨이 있어도 이름을 찾으므로 정지 후 「화자N」으로 뒤집히지 않는다). ⚠whisper 철자가 흔들려(김대리/김 대리) 칩이 갈라지니 공백 제거본을 칩 정체성으로 쓴다. - (2026-09-19) [코디] 5초 폴링 화면은 「읽는 중이면 본문을 갈아치우지 않는다」로 짠다.
innerHTML통짜 교체는 스크롤을 죽이고,scrollTop만 복원해도 위쪽에 문장이 끼면 사용자는 모르는 채 딴 곳을 읽는다. 처방 = 스크롤이 맨 위가 아니면 새 내용을 큐에 담고 「↓ 새 정리본이 있어요」 필만 띄운다(누를 때 반영 + 부드러운 맨 위 이동) · 반영도 절 단위 diff(안 바뀐 절은 DOM 미변경) · 필터는 CSS 한 줄로 걸어 다시 그리지 않는다. 끝난 회의는 2번 더 확인하고 폴링을 멈춘다. - (2026-09-19) [코디]
index.html에서.hidden과.desktop-only를 같은 요소에 쓰면 안 된다..hidden{display:none !important}(L40) 보다.desktop-only{display:inline-flex !important}(L143) 가 뒤에 있어 같은 특이도·같은!important에서 데스크톱은.desktop-only가 이긴다 → 숨겼다고 믿은 요소가 계속 보인다. PC 전용 요소는.desktop-only만, 감추고 켜는 건.hidden만 쓰고 필요하면 JSmatchMedia로 가른다. 곁가지 =renderNotesInto의/^##\s+/는### 이름에 안 맞는다(##뒤\s+가#에서 실패) →###분기를##앞에 둔다. - (2026-09-17) [코디] 🔴윈도우에서
subprocess.run(input=..., timeout=N)의 timeout 은 «stdin 쓰기»를 못 막는다 — 큰 입력은 반드시 파일로 준다. CPython 윈도우Popen._communicate는self._stdin_write(input)을 리더 스레드 join(=timeout 이 걸리는 자리)보다 먼저, 무제한 blocking 으로 한다(Python39\Lib\subprocess.py실측). 자식이 stdin 을 안 읽으면 파이프 버퍼(수 KB)를 넘는 입력은 timeout 과 무관하게 영원히 막힌다. 26.9.16 사고의 80분이 정확히 이것이었다 — 40KB 전사를 파이프로 밀어 넣던 중 자식(cmd.exe)이 기동 실패 모달 팝업 앞에 서 있어 10:20:49~11:40:08 을 멈춰 있었다. 처방 =tempfile.mkstemp로 적고stdin=파일객체(파이썬은 아무것도 안 쓰니 timeout 이 제대로 든다). 🔑「timeout 을 줬으니 안 멈춘다」는 윈도우에선 거짓이다. 자체검사 =live_server.py --selftest-notes의 ③번(200KB + stdin 안 읽는 자식) — 고치기 전 코드로 돌리면 거기서 영원히 멈춘다. - (2026-09-17) [코디] 🔴rc=3221225794(0xC0000142 STATUS_DLL_INIT_FAILED) = 자식 «기동» 실패이고, 이 PC(미니PC)에선 재발한다. 이벤트로그
System / Id 26(Application Popup)에 26.9.16 09:59·10:02·10:05·10:20 네 건 + 9/10·9/11·9/15 에codex-command-runner-*.exe로 수십 건 = 특정 프로그램 문제가 아니다. 아닌 것으로 확인된 원인: AppInit_DLLs·AppCertDlls·IFEO·Driver Verifier 전부 비었고 백신은 Defender 뿐이며 사고 시점 자원고갈 이벤트(2004 류)도 없다 → 주입 DLL 아님. 남은 유력 후보 = 프로세스 churn(사고 시점 프로세스 397개 · cmd 36 · conhost 40 · node 31). 코드 쪽 방어 4개: ①.cmd/shell=True를 피하고 exe 직접 호출(한 겹당 실패 표면이 준다 ·claude.cmd→node_modules\@anthropic-ai\claude-code\bin\claude.exe) ②creationflags=CREATE_NO_WINDOW③모듈 진입부ctypes.windll.kernel32.SetErrorMode(0x0001|0x0002|0x8000)— 자식이 부모 에러모드를 물려받으므로 기동실패 모달이 안 뜬다(모달이 안 떠야 재시도가 의미를 가진다) ④재시도는 rc==3221225794 와 spawn 예외에만, timeout·rc=1 은 재시도 금지(회당 180초면 마감이 9분 늘어난다). - (2026-09-17) [코디] 실패한 결과를 «성공 자리»에 쓰지 않는다 — 오류는 별 필드로. 옛
make_notes_blocking은 오류 문구를 «본문»으로 돌려주고 호출부가 그걸notes_md에 덮어써서, 직전까지 잘 만들어 둔 중간 정리본까지 날아갔다(26.9.16 = 1340초·362발화 녹음의 정리본 전손). 규약 = 반환을(본문, 오류)튜플로 · 실패 시 기존 본문 유지 +notes_error별도 · 로그 + 텔레그램 + 되살리는 엔드포인트(POST /api/items/{ts}/notes). 되살릴 길이 없으면 그건 데이터 손실이다. 웹 「정리본 ↻」 버튼은 wsnotes_now라 녹음 중에만 먹는다는 걸 기억할 것. - (2026-09-17) [코디] 마감 같은 다단계 작업은 «단계별 소요시간»을 한 줄로 남긴다. 26.9.16 사고에서 정지(10:20:29)→저장(11:40:08) 80분을 로그가 없어 「화자분리가 느린 것」으로 오진했다 — 화자분리는 12초였고 80분은 위 stdin 정체였다. 팝업 간격이 정확히 205초(=timeout 180 +
NOTES_SEC25)인 걸 이벤트로그와 맞춰 보고서야 잡혔다. 지금finalize는꼬리전사 3.2s · 화자분리 11.8s · 정리본 31.0s를saved줄에 붙인다([live] <시각> saved: …). - (2026-09-17) [코디] 모델·무거운 초기화는 import 시점 전역에 두지 않는다 — 서버가 «즉시 listen» 해야 사람이 상태를 볼 수 있다.
MODELOBJ = WhisperModel(...)가 모듈 전역이라 부팅 후 28.7초 동안 8445 포트가 아예 안 열렸고 폰엔 「목록을 불러오지 못했어요」만 떴다(5분 주기netstat가드도 그 구간엔 못 믿는다). 처방 =threading.Event+ 백그라운드 스레드 +transcribe_blocking첫 줄MODEL_READY.wait(). 🔑스레드 시작은__main__안에서 — import 시점에 띄우면--selftest-hallu나 이 모듈을 import 하는 도구가 쓰지도 않는 모델을 올리다 종료에 걸린다. 로드 전 오디오는 버리지 않는다(wav 실시간 저장 +Session.buf최대MAX_BUF_SEC=600초). 상태 단일 출처 =GET /api/health— 값을 enum 으로 못박는다(stt: loading|ready|fail·notes: unknown|ok|fail), 안 그러면 프론트가else로 뭉갠다. - (2026-09-17) [코디] 폰 앱(APK) 재빌드 없이 새 옵션을 서버에 넘기는 길 = sid 선등록. 앱은
POST /ingest?sid=&seq=로만 올리고 그 URL 은 APK 안에 박혀 있어 새 쿼리를 못 붙인다. 그런데 sid 는 웹(WebView)이 만들어 네이티브에 넘긴다(nativeCmd({cmd:'start', sid})) → 웹이await로POST /api/prefs {sid, ...}를 먼저 끝내고 그 다음nativeCmd를 부르면 첫 조각이 오기 전에 서버가 선택을 들고 있다(순서가 곧 정합성). 시작 뒤 변경은 409. 화자분리 on/off 가 이렇게 들어갔다. - (2026-09-17) [코디] 화자분리를 끈 항목은
spk를 «비운다» — 물려받은 채 두면 웹이 라벨을 통째로 버린다.renderTranscriptStatic·renderTranscriptLive는spk.length === segments.length가 아니면 라벨을 전부 무시한다(조용히) → 이어 녹음으로 앞부분spk만 남으면 길이가 어긋나 사용자는 이유를 알 수 없다. 같은 이유로labeled_text_from_segments는 spk 가 아예 없으면화자?라벨 자체를 안 붙인다(전 줄화자?는 md 를 더럽히고meeting_system.txt규칙(「라벨 없으면 무시」)과도 안 맞는다). 끈 것은 한 방향 문이 아니다 — wav·전사가 남으니POST /api/items/{ts}/diarize로 나중에 돌리고 정리본도 라벨 달아 재생성한다. - (2026-09-14) [코디] LIVE STT 앱은 폰이 tailnet 안에 있어야 쓴다 —
Config.BASE = https://poin-mini.tailc5d3c1.ts.net:8446(정식 인증서 · SSL 예외처리 금지가 확정 규약)이라 tailscale 없는 폰은ERR_CONNECTION_ABORTED(8446 은netstat상 tailnet IP100.108.234.45에만 LISTENING, LAN·공인 DNS(Funnel IP)로는 못 간다). 새 폰에 깔면 Tailscale 앱부터 — Play Store(market://details?id=com.tailscale.ipn) → VPN 허용 → Sign in with Google. 권한 4개는 GUI 없이 adb 로 끝난다:pm grant <pkg> android.permission.RECORD_AUDIO·POST_NOTIFICATIONS·dumpsys deviceidle whitelist +<pkg>(배터리 제한없음) — 삼성 「절전 제외 앱 등록」만 사람이 확인하는samsungAck플래그. 노트20 편입 =galaxy-note20-5g100.91.196.11(26.9.14). - (2026-09-15) [코디]
live_server(8445) = 상시 오픈 · 예약작업\Cody\livestt-server가 띄운다 — 트리거 2개(delix 로그온 +1분 · 5분마다 살아있나 확인) → 재부팅·절전복귀·크래시 뒤 늦어도 5분 안에 스스로 살아난다. 중복 기동은start_live.bat첫 줄 가드(netstat … :8445 LISTENING이면 즉시 종료)가 막는다 — 실측 재실행해도 프로세스 1개. 8446(tailscale serve)은 tailscaled 가 serve 설정을 들고 있어 원래부터 부팅 후 자동. 판정 =netstat -ano | findstr :8445· 손으로 =wscript C:\dev\talklog\live\start_live_hidden.vbs(가드 때문에 아무 때나 눌러도 안전). - ~~(2026-09-14)
live_server(8445)는 재부팅·절전 뒤 자동기동이 없다~~ — 26.9.15 위 예약작업으로 해소. 그 전엔 8446 만 살아 있어 폰에서 「붙긴 하는데 빈 화면」이 됐다. - (2026-09-14) [코디] 🔴
collect_full.py는 카톡 목록 검색 필터가 남아 있으면 그 결과만 수집하고 「바닥」으로 끝난다(26.9.14 실측 = 「조수은」 필터 잔류 → 2방 수집 후 종료).kakao_grab_room.py가 검색어를 지우지 않고 끝난다 → 전량 수집 전 검색창 X(창 좌표 370,103) 를kps.click으로 눌러 지우고 스크린샷으로 목록이 전체인지 확인. ⚠전량 수집은 ~6시간(967방) — 급한 방은kakao_grab_room.py <이름> --close로 먼저 뽑고(방당 20초, 산출C:\dev\_work\ax-biz\kakao\) 수집기는 resume 로 다시 띄운다(두 도구를 동시에 돌리면 안 된다 · 같은 GUI). 옛 7월판 전량 원문은C:\dev\talklog\data\text_full\_chunks\(107청크 · 방헤더===== 방: X =====· 날짜[YYYY-MM-DD])에 남아 있어 7.16 이전분은 새로 안 뽑아도 검색된다** — 필터 도구C:\dev\_work\upseong\scan_chunks.py <폴더|파일> <from> <to> <정규식> [방정규식](내보내기 txt 원본 형식도 읽는다). - (2026-09-14) [코디] 미니PC PC카톡 채팅목록이 통째로 비면(「채팅을 시작해보세요」 ·
OnlineMainView인데 방 0개) = 세션이 다른 PC/폰 조작으로 밀린 것 —collect_full.py는find_list가 None 이라lr[3]에서TypeError로 죽고(425/967 에서 실측), 친구/채팅 탭 토글·창 복원으로는 안 돌아온다. ⛒재로그인은 페페 몫(자격증명·2단계) → 텔레그램으로 알리고 멈춘다._state.json이 done 을 갖고 있어 로그인 뒤collect_full.py(resume) 로 이어진다. - (2026-09-14) [코디] Git Bash 에서
tasklist /fi "PID eq N"는/fi가 경로로 변환돼 항상 빈 결과 → Monitor 생존판정이 「PROC_DEAD」 오탐(2회).MSYS_NO_PATHCONV=1 tasklist /fi ...로 부른다(schtasks 와 같은 함정). - 2026-09-11 [코디] 🔴WebView 앱에 edge-to-edge(
setDecorFitsSystemWindows(window,false))를 쓰지 않는다 — 안드로이드 WebView 는 상태바·내비바 인셋을 CSSenv(safe-area-inset-*)로 넘기지 않는다. LIVE STT 앱 v0.2 에서 상단 타이틀이 상태바에 가렸다(페페 S25 제보 ·viewport-fit=cover가 있어도 0 · web-designer 스펙 4절이 edge-to-edge 를 권했었다). ⛒v0.3 = edge-to-edge 끔 → 시스템이 WebView 를 두 바 사이에 둔다(CSS 변경 0) + 바 색--surface#FFFFFF · 아이콘 dark(WindowCompat.getInsetsController). targetSdk 34 라 Android 15+ 강제 edge-to-edge 대상이 아니다 — targetSdk 35+ 로 올리면 강제되니 그땐 인셋을 WebView 패딩으로 직접 준다. - 2026-09-11 [코디] 🔴LIVE STT 안드로이드 앱(C단계) 캡처 스레드·업로더 스레드가 같은 저널 파일(
meta.json)을 각자 메모리 사본으로 통째로 덮어써 서로의 갱신을 지웠다 — 실기(S25) 시험에서 5초 조각부터 업로드가 영원히 멈추는 것으로 발견.RecordingService.captureLoop(조각 확정마다written갱신)와Uploader.loop(ACK 마다nextUpload갱신)이 각자 다른 시점에 읽어둔Meta객체를 그대로saveMeta()로 재저장 — 나중에 쓰는 쪽이 상대 필드의 최신값을 스테일 값으로 되돌린다(실측: 캡처가 5초마다 쓰는written갱신이 업로더의nextUpload증가를 매번 0으로 되돌려, 조각이 0번만 지워지고 이후 조각은 영원히 로컬에 쌓임 — 앱 재설치·프로세스 재시작으로도 안 풀린다, 이미 0으로 되돌아간nextUpload는 코드를 고쳐도 저널 파일 자체가 손상돼 있어 복구 안 됨, 그 sid는 폐기하고 새 sid로 시작해야 함). ⛒처방 =ChunkJournal.update(sidDir){ it.필드 = 값 }(디스크에서 새로 읽고 → 그 필드만 바꾸고 → 저장,@Synchronized) 하나로 모든 쓰기를 통일 — 각 스레드는 자기가 소유한 필드만 건드리되 항상 최신 디스크 상태 위에서 갱신한다. 🔑두 스레드가 같은 소규모 상태 파일을 나눠 쓰는 구조는 필드별 소유권을 나눠도 "읽고 계산하고 통째로 저장"이면 경합한다 — 반드시 쓰기 시점에 다시 읽는다. 검증 = 새 sid 로 66.1초 실녹음 → 조각 14개 순서대로 업로드·정지·/ingest/stop·서버duration_sec:66.1일치, 오래된 손상 sid 는 그대로 재현(참고용으로 남겨둠). - 2026-09-11 [코디] LIVE STT 실기 adb 좌표 계산 = 스크린샷 뷰어가 보여주는 "displayed" 좌표에 항상 ×1.17 을 곱한다(1080/923) — 한쪽 축만 빼먹기 쉽다.
input tap에 스케일 안 한 좌표를 쓰면 버튼을 놓치고(대화상자가 안 닫힘) 반복 재시도로 시간을 버린다. 배터리 최적화 시스템 다이얼로그의 "확인" 버튼 위치가 대표 사례. - 2026-09-11 [코디] 앱 업로드(
POST /ingest)의 신뢰 경계 = 서버 장부C:\dev\talklog\data\lectures\_ingest\<sid>.jsonl. (sid, seq, sha256, wav 끝 바이트)를 fsync 한 뒤에만 ACK → 폰은 ACK 받은 조각만 지운다. 받은 seq 재전송 = 같은 해시 200 dup · 다른 해시 422 · 건너뜀 409+expect. 서버 재시작·600초 만료 뒤 첫 요청은 장부로 세션을 복구하고 장부 끝보다 긴 wav 꼬리를 자른다(쓰였지만 ACK 안 나간 조각 = 폰이 다시 보낸다). 🔴자르기 전 가드 = 항목 jsonsessions마지막이 이 sid 거나 시작 때의 직전 sid(after)일 때만 — 아니면 그 사이 다른 녹음이 이어 붙은 것이라 410(자르면 남의 녹음이 날아간다). 🔴ACK 전에 디스크에 안 남긴 상태는 정전에 날아간다 — gap 구간을 전사 저장(save_lecture)에 얹었더니_test_ingest정전 시험에서 유실 → gap 조각은 ACK 전에save_lecture. 검증 =C:\dev\talklog\live\_test_ingest.py(자기 서버를 8447 에 띄워 kill·재기동, 운영 무관) 24/24 · wav 데이터 = 보낸 바이트(sha256). - 2026-09-11 [코디] 폰·노트북 주소 =
https://poin-mini.tailc5d3c1.ts.net:8446/(tailscale serve 정식 인증서 → 경고 0 · tailnet 전용 · Funnel 아님). 8446 은https+insecure://127.0.0.1:8445로 넘기는 프록시일 뿐이라 백엔드 live 서버(8445)는 여전히 자동기동이 없다. wss 도 serve 를 통과한다(실측TALK_HOST=poin-mini.tailc5d3c1.ts.net TALK_PORT=8446 _test_live.py통과 · wav 40.0/40.0초). 8445 IP 주소(자체서명)도 그대로 산다. - 2026-09-11 [코디] 🔴LIVE STT 는 녹음 종류를 가정하면 안 된다 — whisper 초기 프롬프트·정리본 지시문이 «AI 강의»로 박혀 있었다. 페페 유튜브 녹음(20:40)에서 ①음악 구간 3곳(10~23초 구간)에 「이 영상은 유료광고를 포함하고 있습니다」 환청 ②일상 대화에 「LLM」 삽입(초기 프롬프트 「인공지능 강의 녹음입니다… LLM, 임베딩」 탓) ③정리본이 「강의가 아니라…」로 강의 기준 판단(지시문 「강의 회의록 정리 도우미 · AI/머신러닝 문맥 교정」 탓). 같은 wav 전체 전사 실측 = AI 프롬프트 30구간 · 중립 문장 「다음은 한국어 대화 녹음입니다.」 = 그 문장만 4번 되읊고 대화 전부 버림 · 프롬프트 없음 48구간 정상 ·
hallucination_silence_threshold=2= 앞 52초 실발화까지 날려 기각. ⛒기본 프롬프트 없음(분야가 정해진 자리만TALK_PROMPT로 용어 나열) + 구간 «전체»가 유튜브 상투구일 때만 버리는HALLU_RE(자체검사python live_server.py --selftest-hallu9/9, 버린 건 로그환청 버림:) +meeting_system.txt중립화(종류·주제 가정 금지 · 교정은 그 녹음 문맥으로만 · 상투구 버림). 검증 = 같은 녹음 실시간 재생 → 유료광고 0 · LLM 0 · 상투구 1건 자동 버림 · 부제 중립 ·_test_live통과. 🔑통계 컷(무음확률·글자밀도)은 못 쓴다 — 실발화 구간도 nsp 0.69 · 1자/초가 나와 같이 걸린다. - 2026-09-11 [코디] 🔴정지 직전에 돌던 정리본 생성(claude -p ~25초)이 마감 «뒤에» 돌아와 최종 정리본을 중간본으로 덮는다 — md·json 도 다시 쓴다.
notes_loop는make_notes_blocking을 executor 에 넘기고 기다리는데, 그 사이finalize가 최종 정리본을 만들고saved를 보내도 늦게 온 결과가sess.notes_md·부제를 덮고save_lecture()로 파일을 재작성했다(실측 = 테스트가 지운 항목이saved뒤 7초·수십 초에 되살아남, 2회). 26.9.9 코드부터 있던 결함 — 긴 회의를 멈추면 최종본이 조용히 «중간까지의 요약»으로 바뀔 수 있었다. ⛒executor 복귀 직후if not sess.active: return(C:\dev\talklog\live\live_server.pynotes_loop). 검증 =_test_live뒤 45초 대기해도 재작성 0. 🔑「지운 파일이 되살아난다」는 teardown 버그가 아니라 늦게 도는 백그라운드 작업의 신호다. - 2026-09-11 [코디] 🔴LIVE STT 테스트(
_test_live.py·_test_items.py)는 «운영»C:\dev\talklog\data\lectures에 항목을 만든다 → 정리는 «자기가 만든 것만». B 서브에이전트가 테스트 잔재를 치운다며rm -f lecture-20260911-*(날짜 glob)를 2회 돌렸다(승인 없음·보고 누락). transcript 대조로 지워진 건 전부 자기 테스트(19:33~19:49)였지만, 그날 실녹음이 있었으면 휴지통 없이 영구삭제였다. ⛒두 테스트에 teardown 을 넣었다 —_test_live= 서버가 준saved.path만,_test_items= 자기 ready 로 받은 ts 만(md·wav·json). 날짜 glob·목록 차집합 삭제 금지. - 2026-09-11 [코디] live 서버 재기동 = PowerShell
Start-Process경유. Bash 에서wscript start_live_hidden.vbs를 바로 부르면 행이 걸렸다(B 에이전트 실측 — 자식이 Bash 파이프를 물려받는 것으로 추정). ⛒powershell -NoProfile -Command "Start-Process -FilePath wscript.exe -ArgumentList 'C:\dev\talklog\live\start_live_hidden.vbs'"→ 즉시 반환 · 2초 만에 8445 기동(코디 실측, 부모 = bat 의 cmd). 먼저/api/items에status != ready가 없는지 보고 8445 PID 만 종료한다. ⚠python39 의 numpy 는 사용자 site-packages(C:\Users\delix\AppData\Roaming\Python\Python39\site-packages)에만 있다 — APPDATA 없는 환경에서 띄우면No module named 'numpy'로 즉사(B 중 1회 발생). - 2026-09-11 [코디] 🔴샌드박스 Bash 세션에서
wscript start_live_hidden.vbs는 붙는다(무기한 행) — 재기동은 python39 를 직접 백그라운드로 띄운다. B단계 웹 개편 작업 중 실측:MSYS_NO_PATHCONV=1 timeout 15 wscript ...vbs가EXIT=124(타임아웃)로 죽고, 그 사이 python 프로세스는 아예 안 떴다(포트·로그 둘 다 무변화) — Session 0 Bash 도구에서WScript.Shell.Run이 데스크톱/윈도우스테이션을 못 찾아 블록되는 것으로 보인다(같은 계열의 기존 「GUI 는 Session 0 에서 안 잡힌다」함정). ⛒해법 =PYTHONUTF8=1 TALK_PORT=8445 TALK_CERT=... TALK_KEY=... "<python39경로>" live_server.py > _live.log 2>&1를 Bashrun_in_background:true로 직접 실행(일반 유저 delix 컨텍스트라 site-packages 정상). 🔴tools/elevated-runner/el.py로 대체하면 다른 이유로 죽는다 — SYSTEM 프로필엔 python39 의 사용자 site-packages(%APPDATA%\Roaming\Python\Python39\site-packages, numpy 등)가 안 보여ModuleNotFoundError: numpy. 사람이 실제 대화형 세션(진짜 데스크톱)에서 코디를 쓸 때는 기존 wscript 절차가 정상 동작할 가능성이 높다 — 이건 이 샌드박스 환경 한정 함정이다. - 2026-09-11 [코디] ws
saved이벤트의 자동 화면전환은 "지금도 그 화면에 있을 때만" 해야 한다 — 안 그러면 딴 화면 보는 중에 갑자기 항목보기로 끌려간다. B단계에서 뒤로가기 → 녹음 중이면 자동 stop(확인 후) 하도록 짰는데, 그 stop 의finalize()가 몇 초 뒤 늦게saved를 보내면 그 핸들러가 무조건location.hash='#/item/<ts>'로 튀어 사용자가 이미 다른 항목·목록을 보고 있어도 화면을 가로챘다(Playwright 재현: 412/1280 스샷 검증 중 실제로 잡음). ⛒처방 =saved핸들러에서currentRoute.screen === 'record'일 때만 자동전환(live/static/app.js). 다른 화면으로 넘어간 뒤엔 그 항목은 목록·폴링으로 조용히 "완료"로 바뀌는 게 맞다. - 2026-09-11 [코디] 🔴웹 라이브 녹음은 화면을 끄면 약 60초 뒤 «녹음 자체가» 멈춘다 — 백로그로 못 살린다(실측). 노트20(Android 13 · Chrome 152 · USB 충전 중)에서 wav 증가량을 쟀다: 켜짐 32,031 B/s → 끈 뒤 60초 정상 → 이후 2분 0 → 다시 켜니 32,094 B/s, 같은 sid·같은 파일로 이어짐. 서버 로그상 소켓은 17:18:06~17:20:16 내내 붙어 있었는데 데이터가 1초분(102→103초) = 네트워크가 아니라 폰의 캡처가 멈춘 것 → 폰 백로그도 비어 있어 갭은 영구 유실(저장 142초 / 실제 268초). 충전 중(= Doze 아님)에도 끊겼으니 「충전 연결」 권고는 틀렸다(README 정정). ⛒화면 켜둠이 유일한 웹 해법, 화면 끄고도 녹음은 네이티브 마이크 포그라운드 서비스뿐. 측정 =
C:\dev\talklog\live\_test_screenoff.py. ⚠S25(페페 폰)는 무선디버깅이 꺼져 있어 못 쟀다 — 기종·One UI 에 따라 60초가 달라질 수 있다. - 2026-09-11 [코디] 🔴
tailscale serve가 같은 포트를 잡으면 0.0.0.0 서버는 «조용히» 폰에서 사라진다 — 127.0.0.1 검증은 계속 통과한다. 26.9.8 photoapp 이tailscale serve --https=8444(tailnet only)를 걸자 tailscaled 가100.108.234.45:8444를 직접 바인드했다. live 서버는0.0.0.0:8444라 둘 다 뜨고(윈도우는 더 구체적인 바인드가 이긴다), 로컬 검증(rehearse·_test_live= 127.0.0.1)은 26.9.9 에도 전부 통과했는데 폰 = tailnet IP 는 tailscaled 로 가서 IP 접속엔 인증서가 없으니 TLS 실패 → 폰엔 「보안 연결」 오류(curl =SEC_E_INTERNAL_ERROR). ⛒live = 8445 로 이동. ⛒검증은 반드시https://100.108.234.45:<포트>/로(서버 로그의 접속 IP 가 100.108.234.45 인지까지). 새 serve 포트를 고를 땐netstat리슨만 보지 말고 꺼져 있는 상주 서버의 포트(이 파일·README)도 대조한다 — live 는 자동기동이 없어 꺼져 있는 날이 많고, 그때 8444 는 «빈 포트»로 보인다(26.9.10 02:33 재부팅 후에도 꺼져 있었다). - 2026-09-09 [코디] 🔴긴 작업을 ws 메시지 루프 안에서
await하면 heartbeat 가 소켓을 죽인다 — 파일은 저장되는데 폰만 결과를 못 받는다.WebSocketResponse(heartbeat=20)은 서버가 20초마다 ping 을 보내고receive()가 돌아야 pong 을 읽는다. 그런데stop처리를async for msg in ws:본문에서 await 하면 그 수십 초 동안receive()가 안 돌아 pong 이 안 읽히고 →_pong_not_received()가_closed=True로 만든다 → 이후push()가 조용히 전부 실패한다. 실측 26.9.9 = 서버 로그엔saved:가 멀쩡히 찍히고 md 도 정상인데 클라이언트는saved를 영영 못 받았다(회의가 길수록 확실히 터진다 — 40초 오디오는 통과, 55초는 실패한 간헐성이라 더 나쁘다). ⛒마감처럼 오래 걸리는 건asyncio.create_task()로 빼고 루프는 계속 받게 둔다. 그리고push()실패를 삼키지 마라 — 삼키면 원인 추적이 불가능하다(이번에 한참 헤맸다). - 2026-09-09 [코디] 라이브 세션은 소켓 수명에 묶으면 안 된다 — 끊기면 파일이 갈리고 그 사이가 통째로 빈다. 26.9.4 실측(
live\_live.log) = 모임 하나가 파일 3개로 갈렸고(19:16:54·19:18:35·20:51:33) 가운데 세션이 19:59:11 에 죽은 뒤 20:50:41 까지 50분이 비었다. 재접속 코드가 없어ws.onclose는 문구만 띄우고 이후 PCM 을 전부 버렸다. ⛒세션을SESSIONS[sid]로 떼어내 끊겨도TALK_RESUME_SEC(600초) 살려 두고, 폰은 백로그 5분치를 들고 자동 재접속해 같은 파일에 이어 쓴다. 🔑재접속 심장박동을setInterval에 걸면 안 된다 — 백그라운드에서 스로틀된다. 오디오 워크릿 콜백(256ms) 에 걸어야 살아 있다. - 2026-09-09 [코디] 라이브 화자분리는 「정지 후 일괄」이고, 엔진은 통화와 같은 코드를 쓴다.
talklog\server\stt_groq.py의 발화단위 임베딩(3D-Speaker campplus, ONNX·CPU)을 재사용한다 — sherpa 자체 파이프라인은 같은 정답셋에서 76.2% 로 더 나쁘다(우리는 whisper 가 준 발화 경계로 자르니까 임베딩이 안정적이다). 통화는 2인 고정이라 2-means, 회의는 인원을 몰라cluster()(평균연결 AHC). 🔴자동 임계값의 평지가 좁다 — 정답 통화 4건 실측(server\_bench_cluster_auto.py)에서 0.35~0.36 에서만 인원 2.0·87.1%(=인원 고정과 동급)이고 0.34=1.8명/76.8%, 0.38=2.5명/81.6%, 0.45+는 2인을 6명으로 쪼갠다. ⛒인원을 알면TALK_SPEAKERS=N으로 고정한다. 그리고 이 값은 전화 대역 기준이라 룸마이크 회의에선 다시 재야 한다(추측 금지). - 2026-09-09 [코디] 마감과 전사루프가 같은 버퍼를 겹쳐 돌면 발화가 두 벌 쌓인다.
finalize가 꼬리 flush 를 하는 사이transcribe_loop이 아직 executor 에서 돌고 있으면 같은 83초를 두 번 전사한다(실측). ⛒전사→확정→버퍼트림을 세션당asyncio.Lock하나로 묶는다. - 2026-09-04 [코디] live 서버는 오디오를 안 남긴다 — 화자분리를 하려면 폰에서 따로 녹음해야 한다. ⛒26.9.9 해소 — 이제
lecture-<ts>.wav로 실시간 저장한다(아래 26.9.9 항목). 아래 서술은 그 전 상태의 기록이다.live_server.py의save_lecture()는lecture-<ts>.md(회의록+전사)만 쓰고, ws 로 받은 PCM 은 전사 후 버린다(Session.buf는 미확정 구간만 들고 있다) → 모임이 끝난 뒤엔 pyannote 를 돌릴 원본이 없다. 실측 26.9.4 = 미니PC·노트20(USB)·_inbox/telegram어디에도 그 시간대 오디오 없음, S25 는 무선디버깅이 꺼져 있어(포트스캔 30000~50000 에서 열린 포트 1개뿐, adb 거부) 확인 불가. ⛒화자분리가 필요한 자리면 폰 녹음기를 같이 켠다 → 파일을 텔레그램으로 보내면stt한 마디로 WhisperX+pyannote 경로가 돈다. 녹음이 없으면 남는 건 내용 기반 추정(자기소개·질문/답변 관계로 가름 · 확신 낮은 턴은 표시) — 선례 =devplan/meetup-20260904-nakseong.html(63턴, 앵커 방식이라 원문 글자는 안 건드린다). - 2026-09-04 [코디] 전사 파일은 폰에서 보낸 것만 보지 말고
data/lectures/를 날짜로 훑는다. 페페가 텔레그램으로 4개를 보냈는데 실제로는 중복 2건이었고, 대신 그 자리에 없던 19:16 세션(lecture-20260904-191654.md, 501자 = 모임 도입부)이 미니PC 에만 남아 있었다. 세션 파일은 전사 확정마다 덮어쓰기라 같은 세션의 이른 판이 별도 파일로 보내질 수 있다 → 대조는 ①md5 ②전사 길이·접두 일치(짧은 쪽이 접두면 폐기) 순으로 한다. - 2026-09-04 [코디] 라이브 전사·회의록 페이지(
C:\dev\talklog\live) 포트 = 8444, 기동은 창 없는 vbs. 옛 8443 은 이제tailscaled가 잡는다(Funnelpoin-mini.tailc5d3c1.ts.net:8443→ 127.0.0.1:8815), 기본 8793 도C:\dev\tools\furniture-voice\server.py가 쓴다 → 둘 다 피해 8444. 방화벽은 포트가 아니라 프로그램 단위 허용(python39.exe) 이라 포트만 바꿔도 폰에서 그대로 열린다(새 규칙·UAC 불필요). 기동 =wscript C:\dev\talklog\live\start_live_hidden.vbs(→start_live.bat, 로그C:\dev\talklog\live\_live.log). 🔴.bat은 cp949 로 읽힌다 — 한글 주석이 든 bat 을 UTF-8 로 저장하면'CE'은(는) ... 아닙니다류로 조용히 깨진다(26.9.4 실측) → 코디가 고치는 bat 은 ASCII 주석만.start /b prog > log 2>&1도 안 먹어start /b cmd /c "prog > log 2>&1"로 감싼다. 리허설(rehearse.py)은 TLS 서버엔wss+ 검증끄기가 필요해TALK_CERT있으면 자동 전환하게 고쳤다 —TALK_PORT=8444 TALK_CERT=1 .venv\Scripts\python rehearse.py <오디오>. - 2026-09-01 [코디] 과거 이력 조회는 카톡·통화 두 코퍼스를 같이 봐야 하고, 커버리지가 서로 다르다. 카톡 전량 =
talklog/data/text_full/_chunks/chunk_*.txt(105개, 방 구분===== 방:헤더, 실측 커버 ~2026-07-17까지) +poin-agent/data/kakaopc/text/*.txt(내부방 6개, 8월까지). 통화 =talklog/data/calls/transcripts_medium/*.txt(파일명 = 상대_번호_YYYYMMDDHHMMSS, 9월까지). 🔴카톡이 통화보다 한 달 반 뒤처져 있어 최근 건은 카톡에 아예 없다 → "카톡에 없다"를 "그런 일 없었다"로 읽으면 오진. 조회는 자재코드·현장명 grep 후 방 헤더로 상대를 특정하고, 통화 전사는 STT라 숫자·업체명이 흔들리니(997↔911, 에넥스↔NX) 코드값은 카톡 원문으로 확정한다. - 2026-08-21 [코디] 통화종료 트리거(v0.30) =
PHONE_STATE매니페스트 리시버 + 45초 알람. 왜 리시버냐 = 앱 프로세스가 죽어 있어도 시스템이 깨워 주므로 adb·망 상태와 무관하다(ContentObserver·서비스 방식은 프로세스가 살아 있어야 한다). 규칙 3개 = ①IDLE은 부팅·부재중·거절에도 온다 →OFFHOOK/RINGING을 본 뒤의IDLE만 통화종료로 친다(Config.call_active플래그) ②즉시 훑으면 0건 — 녹음 파일은 통화가 끊긴 직후에 쓰이고 MediaStore 색인은 더 늦다 →setAndAllowWhileIdle로 45초 뒤 기존TriggerReceiver(audio/new)를 깨운다(수집 코드 재사용, 연속 통화는 같은 REQ 라 하나로 합쳐짐) ③READ_PHONE_STATE가 없으면 브로드캐스트가 아예 안 온다 — 위험권한이라 설치만으론 안 되고adb shell pm grant com.poin.talklog android.permission.READ_PHONE_STATE(또는 사람이 설정에서). ⚠PHONE_STATE는 보호된 브로드캐스트라am broadcast로 흉내낼 수 없다(-n으로 컴포넌트를 찍어도 SecurityException) → 검증은 실통화로만 된다. - 2026-08-21 [코디] 트리거 수집은 서버를 다시 고르고 시작해야 한다 — 안 그러면 adb 없는 경로가 통째로 죽는다. 30분 배치가
setserver http://127.0.0.1:8792(역터널)를 폰 prefs 에 눌러 놓고 가므로, adb 가 없는 통화종료 경로에서 그대로 쓰면127.0.0.1= 폰 자기 자신이라/calls-have실패 → fail-closed 로 업로드 0건이 된다. →TriggerReceiver의 audio 분기 첫 줄에Config.resolveServer()(캐시 → Tailscale → 로컬WiFi 순 핑). 실측 = 역터널이 깔려 있는데도server_resolved = http://100.108.234.45:8792(Tailscale) 를 골랐고uploaded 4 / skipped 1253 / failed 0. - 2026-08-21 [코디] 무선 adb 는 폰이 Wi-Fi 에 붙어 있을 때만 산다 — Tailscale 이 켜져 있어도 아니다. 12:00 에 붙어 있던
100.106.4.88:41625가 12:26 스캔에서 사라졌다(열린 포트44877하나뿐,connect는 offline). 안드로이드 무선 디버깅은 Wi-Fi 종속이라 Wi-Fi 가 끊기면 adbd 의 TCP 리스너가 내려가고, 다시 켜면 포트가 또 바뀐다. 🔑앱 업로드 자체는 adb 와 무관하다 — 앱은 Tailscale100.108.234.45:8792로 직접 올린다(LTE 여도 된다). adb 는 「지금 올려라」는 깨우기 신호일 뿐이다. 따라서 adb 가 끊기면 수시수집만 멈추고 야간 03:00 알람 수집은 그대로 돈다. 진짜 수시수집을 망 무관하게 하려면 앱에 통화종료 트리거(PHONE_STATE) 를 넣는 수밖에 없다(확정 15 의 「앱 수정 0」 전제가 adb 불안정으로 깨진 지점 — 재결정 필요). - 2026-08-21 [코디] 통화 항목은
field_no없이 적재된다 → 붙이기 전엔 통합현장 카드에 안 뜬다.pipeline_worker의 INSERT 는 모델이 쓴 자유문구site_name만 넣는다. 통합현장(get_worklog_fusion)은field_no로 묶고 이번주(week_start)만 보므로, 붙이지 않은 통화는 전부 「현장 미연결 묶음」에 쌓인다 — 실측 = 이번주 통화 143건 전부 미연결, 현장 카드 items 0. 붙이기 =pfs/scripts/_wl_autolink_run.py prod apply(threshold 80). 실측 = 143건 중 27건만 자동연결(현대 포항 13·롯데 화성 향남 5·넥서스 4·현대 청량리 2·삼성물산 2·고양 행신동 1), 나머지 116건은ambiguous/lowcover/weak로 사람 몫. 낮은 연결률의 원인은 매칭기가 아니라 분류 LLM 이 쓰는 현장명(「한샘넥서스(추정)」·「광주 현장」 같은 회사·지역 문구는 현장명이 아니다). 이제 30분 배치가 정리본 직전에 autolink 를 돌린다(run_fusion30.autolink, 이력 =data/_autolink_log.jsonl). - 2026-08-21 [코디] Tailscale 무선 adb의 「접속 포트」는 사람이 안 봐도 된다 — 포트 스캔으로 찾는다. 그동안 "재부팅마다 포트 랜덤 + mDNS 미전달이라 사람이 폰 화면을 봐야 한다"고 반증 처리했는데, 페어링만 사람이 하면(페어링 코드) 나머지는 기계화된다. 🔑페어링 포트 ≠ 접속 포트(페페가 준
35011·37207은 페어링 대화상자 포트라adb connect가failed to connect/offline) → 접속 포트는 asyncio TCP 스캔으로 발견(실측100.106.4.88:41625, 300 동시·타임아웃 4초로 5,000포트당 ~1분, 범위 30000~50000). 열린 포트가 여러 개 뜨는데(예 35083·36493·44877)adb connect가device로 붙는 것 하나만 진짜, 나머지는 페어링/부산물이라offline로 남으니adb disconnect로 치운다. 페어링은 폰이 기억한다(guidadb-R5CY831RHVH-Lmt0hf) → 다음 재부팅부턴 스캔만으로 재접속 가능. - 2026-08-21 [코디] 폰이 서버에 못 닿은 구간은 텔레메트리가 통째로 비어 「조용히」 지나간다. 실측 = S25 Tailscale 이 8/13~8/21 offline →
_telemetry.jsonl마지막 줄이 8/12 15:20에서 멈췄고 그 사이 통화 143건이 폰에만 쌓였다(서버data/calls최신 = 8/11). 텔레메트리 자체가 HTTP POST 라 서버 미도달 = 로그도 안 남는다 → "로그가 조용하다"를 정상으로 읽으면 안 된다. 복구는 adb 붙여am broadcast -n com.poin.talklog/.TriggerReceiver -a com.poin.talklog.TRIGGER --es action audio --es mode new한 방(실측uploaded 143 / skipped 1110 / failed 1, 소요 ~5분). ⚠audio_collect텔레메트리는 수집이 끝나야 찍힌다 — 트리거 직후 로그가 비었다고 "브로드캐스트 실패"로 오진하지 말 것(파일은 이미 들어오고 있었다). -
2026-08-21 [코디] S25 앱 업데이트에 adb는 선택지일 뿐 — 기본 경로는 앱 자가업데이트다.
UpdateChecker가 앱을 열 때(MainActivity) 서버/version을 보고/app.apk를 받아PackageInstaller로 설치한다(사용자 1탭). 새 APK를server/apk/에 넣고version.json의 versionCode만 올리면 폰이 서버에 도달하기만 하면 끝. 도달 조건 = 폰 Tailscale ON(http://100.108.234.45:8792) 또는 집 WiFi(192.168.45.2폴백 — 사무실에선 안 걸린다). 무선 adb는 폰 Tailscale이 죽으면 그대로 막힌다(2026-08-21 실측offline, last seen 8d→adb connect 100.106.4.88:555510060 타임아웃). ⚠사무실PC(poinimos01)엔C:\dev\tools\platform-tools·C:\dev\talklog·C:\dev\tools\android-build가 전부 없다 — 사무실 USB adb는 platform-tools 복사 + 폰 USB디버깅 허용 탭이 선행이라 자가업데이트보다 손이 더 간다. 빌드 =JAVA_HOME=/c/dev/tools/jdk-17 /c/dev/tools/gradle-8.7/bin/gradle -p /c/dev/talklog/app :app:assembleDebug --no-daemon(gradlew 없음). -
2026-08-11 [코디] 야간 통화 파이프라인은
SINCE(=20260801) 이후 통화만 다룬다 — 백로그는 처리하지 않는다. 증상 = 페페 지적 "야간 통화 수집이 아직도 8월 이전을 수집하고 자가학습에 올린다". 원인 =night_worklog.py가 날짜가 아니라 「미처리 여부」로만 대상을 골랐다. P3 는transcripts_medium/*.txt전량(1,105)에서kb_done을 뺀 나머지를 파일명 알파벳순 40개/밤씩 먹는다 → 미처리 585 중 557이 6~7월이라 앞으로 14밤을 더 옛날 통화로 자가학습 큐를 채울 참이었다. P1 도--since없이 돌아 7/7 통화 1건을 매일 다시 훑었다. 🔑"오래된 걸 골라내는" 게 아니라 "새 것만 본다" —SINCE상수 하나를 P1(--since)과 P3(파일명 날짜) 양쪽에 물린다. 파일명 날짜 =_(20\d{6})마지막 매치(#넥서스 김경록 부장_01093713557_20260521_173056처럼 시각이 따로 붙은 것 있음). 날짜 미상 = 처리 안 함('' < SINCE) · 잘라낸 건수는p3_since_skip로 로그에 남긴다(조용한 절단 금지). ⚠kb_done에는 옛 파일을 채워 넣지 않았다 — 원장은 실제로 처리한 것만 담아야 나중에 소급이 가능하다. 검증 =--selfcheck(날짜 파서 4항목 추가) · 실측 585→차단 557 / 대상 28 · P1--since 20260801은 통화 10건(전부 8/10). 큐에 이미 쌓인 6~7월 pending 17건은/api/learn/decide로 일괄 reject(learn_history 에 이력 남음, 복원 가능). -
2026-08-10 [코디] 카톡 방 목록은 PC와 공용폰(노트20)이 서로 다르다 — 발송 대상은 폰 기준으로 확인한다. 실측 = 페페가 만든
신동혁1:1 방이 PC 카톡 목록 3번째에는 보이는데 노트20 목록엔 아예 없다 (스크롤해도 11개가 전부,P.send는room not found in chat list: '신동혁'). 발송은 폰이 하므로 PC에 보인다고 보낼 수 있는 게 아니다. ⚠폰 목록 덤프도 믿을 것이 못 된다 — 스와이프가 안 먹으면 같은 첫 화면을 반복 덤프해 "목록이 이게 전부"처럼 보인다(scroll0~3 결과가 동일했다). 방 존재 판정은P.open_room(이름)성공 여부로 한다. 🔑2026-08-10 해소 — 방은 폰에도 있었다. 이름이 달랐을 뿐이다. 같은 1:1 방이 페페 PC(delix0731)에선신동혁, 노트20(poin3133)에선포인 신동혁 대리(폰 목록 전체 16행 덤프로 확정, 같은 방에 신동혁 대리의 10:53 메시지가 들어 있다). "폰에 방이 없다 / 미동기화" 는 오진이었고, 범인은 계정별 표시이름이다(친구 저장명이 계정마다 다르니 1:1 방 이름도 다르다 — 단톡 이름이 다른 것과 같은 원리). → 발송 대상은 폰 목록 이름으로 박는다(intake_check.pyKAKAO_TO=포인 신동혁 대리). ⚠목록 덤프는 맨 위로 복귀 후 아래로 훑어야 전량이 나온다(첫 화면만 반복 덤프하면 11행에서 끊긴다). 🆕도구 =poin-agent/people_map.py(번호 ↔ 카톡 이름 매핑, 단일 출처data/rooms/people.json). 발송 지시를 받으면 먼저find로 폰 방 이름을 확인하고 그 이름으로 보낸다.PYTHONUTF8=1 python C:/dev/poin-agent/people_map.py find 신동혁→ 발송이름포인 신동혁 대리·번호·페페PC 표시명. 방이 없으면 주소록(1,860명)에서 번호까지 같이 찍어 준다 = 방을 새로 파야 하는 건지 바로 갈린다. 갱신 =people_map.py build(폰 채팅목록 + 주소록 재수집, 방 14). 페페 PC 표시명은 관측된 것만 채운다(추측 금지). -
2026-08-10 [코디] 🔴카톡 PC 사진수집(
collect_photos.py)은 미니PC 화면이 잠겨 있으면 못 돈다 — 그런데 실패가 조용하다. 증상 = Session1(schtasks /IT)로 띄워도SetCursorPos가 에러코드 0으로 False를 반환하며 중간에 죽는다(pywintypes.error: (0, 'SetCursorPos', ...)). 🔑혼동 포인트 = 같은 프로세스에서OpenInputDesktop은Default를 돌려주고 단발SetCursorPos는 True가 나온다 → 잠금 여부를 그 두 값으로 판정하면 틀린다. 확실한 판별 = Session1에서ImageGrab.grab(all_screens=True)스크린샷 1장(잠금이면 PIN 화면이 찍힌다). 창 열거(EnumWindows)·uiautomation트리 읽기는 잠금 상태에서도 정상이라 "카톡 창은 보이는데 클릭만 안 먹는" 모양이 된다. ⚠Bash 도구는 Session 0라 카톡 창이 0개로 보인다(잠금과 무관, 항상 그렇다) → GUI 관련은 전부schtasks /create /it /ru delix+pythonw액션으로 Session1에서 돌린다(vbs 래퍼 불필요). 해소 = 페페가 미니PC 잠금 해제. - 2026-08-10 [코디] 🔴같은 증상(
SetCursorPosFalse)의 두 번째 원인 = Chrome 원격 데스크톱 접속 중. 잠금보다 이쪽이 더 헷갈린다. 실측 = 화면 안 잠겼고 데스크톱 캡처도 멀쩡한데 커서 제어만 전부 False. 범인 =remoting_desktop.exe(SYSTEM 권한)가 포그라운드 — 페페가 CRD로 붙어 설정 바꾸고 연결을 안 끊은 상태였다(프로세스 시작 09:17 ↔ 실패 시작 09:18 일치). 🔑판별 2줄 = ①GetForegroundWindow()의 프로세스명(remoting_desktop.exe·#32770·title 빈칸) ②BlockInput(False)가err=5(ACCESS_DENIED) = 더 높은 권한이 입력을 잡고 있고 우리가 못 푼다. 해소 = 페페가 CRD 연결 종료(코디는 SYSTEM 프로세스를 못 죽인다). ⚠잠금 가설로 오진하기 쉽다 — 잠금 상태에서도SetCursorPos가 True 를 뱉은 실측이 있어(08:51) 커서 함수 반환값만으론 둘을 못 가른다. 순서 = 포그라운드 프로세스명 → BlockInput → 스크린샷. - 2026-08-10 [코디] 미니PC 카톡이 튕겨 있으면
poin-agent/kakao_pc_login.py --who cody로 코디 계정(poin3133) 을 넣는다 — 페페 계정을 다시 넣으면 사무실PC 카톡이 로그아웃된다(같은 계정 PC 1대). 포인01 방은 poin3133 도 멤버라 수집에 지장 없다. 단 방 이름이 계정마다 다르다 — 포인01 은 poin3133 에서아빠, 갈랑 포인, 신동승, 포인 신동혁 대리, 김성수 이사님으로 보인다(poin-agent/data/rooms/mapping.json) → 수집 스크립트는 이름 완전일치가 아니라 부분매칭으로 찾는다. 로그인 성공 판정 = 로그인 창 핸들 소멸(그때shot()이 1400 으로 죽는 건 정상) +netstat에:995(LOCO). ⚠로그인 자동화 = 위험작업, 페페 승인 필요(2026-08-10 승인). -
2026-08-10 [코디] 카톡 사진 대체경로 = 폰 캐시
adb pull. 단 원장이 아니라 「본 것만」이다. 경로 =/sdcard/Android/data/com.kakao.talk/contents/<종류b64>/<방ID>/<해시앞2>/<해시>(확장자 없음, 실제로는 JPEG/PNG/MPO). adb shell 은 Android 11+ 에서도/sdcard/Android/data를 읽는다(앱만 차단). 큰 파일=원본·작은 파일=썸네일이 같은 초에 쌍으로 떨어진다. ⚠한계 3개 = ①방ID→방이름 매핑이 없다(PCchatLogs_<id>.edb파일명과 같은 ID지만 이름은 암호화) ②mtime = 내려받은 시각(게시 시각 아님 — 방을 열면 그 순간 한꺼번에 찍힌다) ③폰에서 안 연 방·안 본 사진은 아예 없다 → 전수 대조용으로는 못 쓴다. 폰이 잠겨 있으면(dumpsys powermWakefulness=Dozing) 새로 받게 할 수도 없다. PC 카톡 캐시(%LOCALAPPDATA%\Kakao\KakaoTalk\users\<uid>\chat_data\cli\*.cng)는 암호화라 사용 불가. -
2026-08-08 [코디] 통화 원본은 이제 지워도 안 돌아온다 — 서버가
_calls_seen.json(원장)을 갖는다. 예전엔/calls-have가 디스크 목록이라 삭제 = "PC에 없음" = 폰이 재업로드였다(7/23 삭제분 829개가 7/24에 그대로 복귀한 이유). 지금은 (디스크 ∪ 원장)으로 답하고/upload-audio가 dup까지 원장에 적는다. ⚠뒤집힌 대가 = 지운 원본을 폰에서 되받을 방법도 같이 사라진다 → 복구 경로는 휴지통(또는 원장에서 해당 항목 삭제 후 재수집)뿐. 첫 적용 = 전사+적재 완료 1,093개(1.35GB) 휴지통, 전사본 1,095개·_purged_2026-08-08.tsv보존. - 2026-08-08 [코디] 통화 증분수집은 fail-closed다 —
/calls-have를 못 받으면 업로드를 중단한다(v0.29). 옛 코드는 조회 실패 시 빈 맵 = 전부 신규 취급이라 과거분을 통째로 재업로드했다(8/8 20:25 실측:night_audio uploaded=586 skipped=0, S25를 미니PC에 유선연결한 직후 서버 미도달). 🔑증상이 정상처럼 보인 이유 = 업로드 실패를catch {}로 조용히 삼켜 카운터가 안 올라갔다 → 알파벳 앞쪽 506건은 업로드도 못 하고 누락(24건은 PC에 아예 없었다)인데 로그엔 실패 0. 그래서failed를 세어 텔레메트리에 싣는다. 2중 방어 = 서버/upload-audio가 같은 이름+크기면 안 쓴다(dup:true) — 앱을 재설치해 폰 상태가 비어도 PC는 안 더러워지고 파이프라인 재실행도 안 걸린다. 검증 = 트리거 2회 →uploaded 25/skipped 1069/failed 0→uploaded 0/skipped 1094/failed 0(완전 멱등). - 2026-08-08 [코디]
pipeline_worker.py가 부르는claude.cmd창이 화면에 떴다 — pythonw 데몬의 손자까지 봐야 한다. talklog_server(pythonw)가 워커를 pythonw로 띄우니 워커도 창이 없다 → 워커가 부른 claude.cmd가 자기 콘솔을 새로 만든다. 부모에nowin을 걸어도 손자에겐 안 물려준다 = 중간 파이썬 자식에도import nowin이 필요하다. 전역 규칙 =seedchain\rules\tooling.md. - 2026-08-04 [코디] 카톡PC 로그인 위치 판정 =
netstat의 비-루프백 ESTABLISHED. 레지스트리 쓰면 틀린다. 실측: 로그인 중인 미니PC는HKCU\Software\Kakao\KakaoTalk\UserAccounts가 비어 있고, 로그아웃된 사무실PC엔 계정 2건이 남아 있다 → UserAccounts는 로그인 이력이다. 실제 판정은 KakaoTalk PID의 외부 연결 유무(:995=LOCO,:443) — 로그아웃이면 루프백(10151↔10152)만 남는다. GUI 없이 판정되므로 SSH(Session 0)로 원격 판정 가능. 부수 = 같은 계정은 PC 한 대만 로그인(미니PC 로그인 중 사무실PC는 로그인 창). - 2026-08-04 [코디] "500자 이상 본문" 함정은 PC 내보내기엔 없다. 폰 화면 스크랩과 알림 미리보기에만 있다. 실측 = PC 정식 내보내기 산출
text_full/_chunks105파일 258,825줄 중 3,439자 한 줄까지 온전, 500자↑ 552줄·1,000자↑ 16줄, 잘림 마커(전체보기·긴 메시지) 0건. 줄바꿈 장문도parse_kakao_export.py가 비-타임스탬프 줄을 직전 메시지extra로 이어붙여 보존한다. 반면 ①폰 접근성 스크랩은 접힌 말풍선 전문을 못 읽고(폐기로 소멸) ②알림 미리보기는 잘리므로sig를 미리보기만으로 만들면 장문끼리 충돌 →방|발신자|초단위ts|본문길이|앞60자로 만든다(v0.28 반영). 500자↑ 도착은 서버가kakao_dirty.json의long_msgs로 세어 수집본 검증에 쓴다. - 2026-08-04 [코디] 카톡 순회가 0방인 진짜 이유 = 카톡 26.6.1(vc 29260610) UI 변경으로 행 선택자
chat_room_list_item소멸. S25 USB 실측(야간 플로우 강제 2회):wake_unlock_proceed(dismissed)→night_kakao_launch→sweep_start→sweep_loaded targets=58까지 전부 정상인데 1초 만에sweep_done rooms=0—findAllById(root,"chat_room_list_item")가 0개라 target=null, 그 폴백인ACTION_SCROLL_FORWARD도 false(바닥) → 즉시sweepFinish. uiautomator 대조로 현 구조 확인 =folder_view_pager > chat_roomLayout > recycler_view > **layout_content**(행) > {name, message, time, members_count, profile}— 자식 id는 전부 생존, 행 컨테이너 id만 바뀜(폴더탭folderTabLayout신설). 방 안은toolbar_default_title_text생존, 말풍선은bubble_root/bubble_linearlayout/bubble_layout구조에message0건(확인한 2방이 이미지·알림톡이라 텍스트 방 재확인 필요). ⚠접근성 트리 자체는 정상(flagReportViewIds유효, rid 정상 수신) = 우리 앱 버그 아니라 카톡 업데이트. 🔑adb로 카톡 못 띄운다 —am start -n com.kakao.talk/…MainActivity는 not exported → SecurityException,monkey -p com.kakao.talk -c android.intent.category.LAUNCHER 1을 쓴다. - 2026-08-04 [코디] S25 PIN은 아직 안 지워졌다(페페는 없앴다고 알고 있었음). 판정 =
cmd lock_settings verify→ "User has a lock credential" +dumpsys trust의trustManaged=1. 낮에deviceLocked=0으로 보이는 건 Extend Unlock(GoogleTrustAgent)이 신뢰를 준 상태일 뿐이고, 03:00엔 4시간 강인증 룰로 다시 1이 된다 → 잠금 상태 판단은deviceLocked한 값만 보지 말고 자격증명 존재 여부(verify)까지 확인한다. - 2026-08-04 [코디] 카톡 수집은 케이블·adb와 무관하다 — 유일 전제는 폰→미니PC HTTP 도달(8792). 스케줄이 폰 안에 있다(AlarmManager 03:00,
NightAlarmReceiver가 매번 다음날 재무장·BootReceiver가 재부팅 복구). PC 서버 스크립트에 adb 구동 0건(grep adb server/*= 0). 서버 선택 =Config.resolveServer핑 순서(캐시→Tailscale100.108.234.45→집WiFi192.168.45.2) → 테일스케일만 살아 있으면 LTE·외부에서도 수집된다(08-04 03:19 실측server_resolved=테일스케일, 그 시점 USB엔 노트20만 붙어 있었음). ⚠도달 실패한 밤은 카톡이 통째로 유실된다 —/rooms를 못 받으면 대상목록 0 →sweep_abort_no_targets로 방에 들어가지도 않고(개인방 보호 설계·의도된 동작), 로컬 큐·자동 재전송 없음(SessionStore는 저장만, 재업로드는 패널 [업로드] 수동뿐), 텔레메트리도 HTTP라 그 밤은 기록조차 안 남는다(07-29·08-01 이벤트 0건). 통화녹음만 자가복구 = 원본이 폰 MediaStore에 남고/calls-have증분이라 다음 성공한 밤에 밀린 것 전량 업로드(07-24 854건 실측). 카톡 소급은 체크포인트까지 위로 스크롤하는MAX_STEPS=40이 상한 → 갭이 길거나 방이 바쁘면 중간이 영구 유실. - 2026-08-03 [코디] 카톡 야간수집 2대 실패원인 = ①3am 보안잠금 ②
<queries>누락. ①WakeUnlockActivity는 Smart Lock 신뢰상태(deviceLocked=0)일 때만requestDismissKeyguard로 무프롬프트 해제한다. 앱은 보안 PIN을 스스로 못 푼다(안드 보안모델 — device-owner 공장초기화만 가능). 3am엔 안드 4시간 강인증 규칙으로 신뢰가 풀려deviceLocked=1→카톡 생략(텔레메트리 최근 4밤 중 3밤). 페페 결정 = S25 잠금을 스와이프전용으로 완화(deviceLocked 항상 0 → 기존 앱 로직 3am 작동, 코드 무변경). ②targetSdk 34인데 매니페스트에<queries>0건 → 야간에getLaunchIntentForPackage("com.kakao.talk")가 조용히 null→카톡 전면 안 뜸(sweep_no_kakao pkg=instagram). v0.27에서<queries><package name=com.kakao.talk/>추가로 근본수정(aapt로 병합 확인). - 2026-08-03 [코디] talklog APK는 마이그레이션(07-25)으로 디버그 키스토어가 소실 → 서명 불일치로
-r재설치 불가. 기존 v0.26 서명0cf11fe3…은 현~/.android/debug.keystore(gradle가 새로 생성)·tools/android-build/debug.keystore어느 것과도 불일치. 해결 = uninstall→install(수집 데이터는 전부 PC라 안전). 재설치 후 재프로비저닝(전부 adb, 폰 UI 안 건드림): ①pm grant3종(POST_NOTIFICATIONS·READ_MEDIA_AUDIO·READ_CONTACTS) ②settings put secure enabled_accessibility_services com.poin.talklog/…CollectorAccessibilityService+accessibility_enabled 1③야간자동 = ⚠implicit 브로드캐스트는 새 설치 앱에 안 꽂힌다 →am start -n …/.MainActivity로 한번 깨운 뒤 explicit 컴포넌트am broadcast -n com.poin.talklog/.TriggerReceiver --es action nighton(검증 =run-as com.poin.talklog cat shared_prefs/talklog.xml에night_auto=true). 다음 알람dumpsys alarm | grep poin.talklog.NIGHT(OW=다음날 03:00). ⚠night_armed텔레메트리 POST는 리시버 수명 짧아 자주 유실 = 실패 아님, prefs·alarm이 진실. - 2026-08-02 로컬 오디오 파일 전사는
C:\dev\talklog\stt_file.py로 한다. ytmine 파이프라인은 URL 입력 전용이라 카톡·녹음기에서 받은 로컬 파일을 처리할 경로가 없었다. 실행 =PYTHONUTF8=1 C:/dev/talklog/live/.venv/Scripts/python.exe stt_file.py <audio> [--chunk 600] [--gemini]. 자체검사--selftest(9항목). 산출 =<base>.txt(타임스탬프) +<base>.segments.json. 근거·첫 적용 = 정준화 코칭 세션 3시간 녹음(projects/ai-study/kb/04-session-jjh-2026-08-02.md). - 2026-08-02 긴 녹음은 청크마다 캐시하고 재실행으로 이어붙인다. 조각은
<base>.chunks<초>/에 남기고 청크별.json을 캐시 → 망이 끊기거나 API가 죽어도 재실행하면 끝난 청크는 건너뛴다. 폴더명에 청크 길이를 박아 옛 분할본과 안 섞이게 한다. (실측: 이 3시간 녹음은 Groq 장애·429·커버리지 오판으로 5회 재실행해서 끝냈다. 캐시가 없었으면 매번 처음부터였다.) - 2026-08-02 ⚠조각 ogg의 ffprobe duration은 '길이'가 아니라 '끝 시각'이다. ffmpeg
-f segment로 자른 조각은 원본 타임스탬프를 유지한다 → 두 번째 조각의 duration이 1800이 아니라 3600으로 나온다. 이걸 길이로 알고 누적하면 오프셋이 폭주한다(실측: 3번째 조각 오프셋이 1시간→1시간 30분으로 튐, 전사 자체는 멀쩡한데 시각만 전부 틀린다 = 눈으로 안 보면 못 잡는다). 오프셋 = 앞 조각의 duration을 그대로 쓴다. - 2026-08-02 ⚠Gemini 전사 커버리지 검사는 뒤쪽만 본다. 모델은 조용한 구간을 건너뛰는데 그건 정상이다(실측: 어떤 조각은 앞 230초가 평균 -49dB에 101초 공백 → 건너뛴 게 맞았다). 앞부분 누락으로 판정하면 무한 재시도에 빠진다. 반대로 끝이 잘리는 건 진짜 조기 중단이라 잡아야 하고, 이때
silencedetect로 끝쪽 무음을 뺀 발화 구간과 비교해야 한다(끝 3분이 무음인 조각을 누락으로 오판했다). - 2026-08-02 ⚠2초 미만 꼬리 조각에는 전사를 시키지 않는다. 0.1초짜리 조각에 Gemini가 없던 대화를 창작했다(실측: "안녕하세요, 오늘 날씨가 정말 좋네요…" 7줄). 에러 없이 그럴듯하게 나와서 전사본 끝에 붙는다 → 길이로 걸러 건너뛴다.
- 2026-08-02 ⚠Gemini 조기 중단은 temperature 0에서 결정론적이다. 같은 조각을 3회 재시도해도 똑같이 380초에서 멈춘다 → 재시도마다 temp를 올린다(0 → 0.4 → 0.8). 단 위 사례처럼 진짜 무음이면 temp를 바꿔도 안 바뀌니, 재시도 전에 무음 판정을 먼저 본다.
§1 로컬 파일 전사 — 백엔드 선택
- 순서 = Groq(whisper-large-v3-turbo) → Gemini. Groq이 정상이면 Groq이 빠르고 무료 한도도 크다(오디오 28,800초/일).
- Groq STT는 죽을 때
models.list는 멀쩡하고 오디오 엔드포인트만 죽는다 (2026-08-02 실측:502 internal_server_error / service_unavailable+ 타임아웃,whisper-large-v3·turbo둘 다, 1시간 넘게 지속). 키 만료나 한도 초과로 오진하지 말 것 — 키 진단은models.list로 하고, 전사만 실패하면 서비스 장애다. - Gemini 모델은
gemini-3.1-flash-lite가 기본 (envSTT_GEMINI_MODEL로 변경).gemini-3.6-flash는 무료 하루 20건이라 3시간 녹음 한 건에 다 쓴다(429 응답의generate_content_free_tier_requests: 20으로 확인). 429는 짧게 재시도해도 또 429 → 70초 이상 쉰다. - 청크 = 10분. 30분도 되지만 Gemini 조기 중단이 잦고, 10분이면 조각당 약 1.2MB라 Groq 무료 25MB 한도에도 여유가 크다.
- 전사 품질은 Gemini(플래시 계열)가 Groq whisper보다 문장 단위가 자연스러웠다(같은 30분 조각 비교).