talklog — 작업 규칙 (rules)
📑 인덱스: 🆕 신규 규칙 → §1 로컬 파일 전사(stt_file.py)
규칙 = 이 파일 / 상태·이력 =
handoff.md. 전역 규칙은C:\dev\user_brief\rules\가 우선한다.
🆕 신규
- 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이 필요하다. 전역 규칙 =user_brief\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분 조각 비교).