프라이버시 일시정지¶
이 페이지는 CLAUDE.md의 구현 노트를 미러합니다. 서브시스템 변경 시
양쪽 다 업데이트하세요.
프라이버시 일시정지는 Cue의 first-class "Cue가 보고 있지 않음" 상태입니다. 수동 핫키, 자동 비밀번호 필드 감지, 설정 가능한 앱 / 윈도우 / URL 필터, 시스템 슬립 / 깨어남을 커버합니다. 활성 시 스트리밍 레코더 kill, 최근 캡처 프레임 purge, 빨간 테두리 오버레이로 상태 시각 확인.
상태 모델¶
일시정지는 사유의 frozenset[str]로 키잉. 집합이 비어있지 않으면
일시정지 활성.
stateDiagram-v2
[*] --> Watching
Watching --> Paused : 사유 추가 → 집합 비어있지 않음
Paused --> Paused : 사유 추가 / 제거지만 여전히 ≥ 1
Paused --> Watching : 마지막 사유 제거
Paused --> [*] : 프로세스 종료 (atexit)
다섯 사유 값:
| 사유 | 소스 신호 |
|---|---|
manual |
핫키 또는 트레이 메뉴 토글. |
secure_input |
macOS Carbon IsSecureEventInputEnabled latch + 포커스된 요소의 AX kAXSecureTextField subrole. Windows UIA IsPasswordPropertyId on 포커스된 요소. |
blocked_app |
최상단 앱 이름이 차단 목록에 있거나 윈도우 타이틀이 설정된 정규식과 매치. store.TERMINAL_APPS 안의 터미널 클래스 앱이 foreground일 때 타이틀 regex 매치는 skip (Terminal / iTerm2 / Warp / 터미널 / ターミナル / 终端 / 終端機 / Console / 콘솔 / WindowsTerminal / Cmd / PowerShell / ConEmu / Cmder) — 로그인 셸 배너 (Last login: …, bash --login)나 login-1 같은 SSH 호스트명이 false-trigger되지 않게. |
browser_url |
최상단 브라우저 활성 탭 URL이 설정된 정규식과 매치 (브라우저가 authorized 상태일 때만). |
system_transition |
NSWorkspaceWillSleep / DidWake (mac) 또는 WM_POWERBROADCAST (Windows) — 일반 감지 사이클이 동작하기 전 lock-screen / unlock 윈도우를 덮는 1.5초 펄스. |
reason_label()이 generic, UI-safe 문자열 반환 — "브라우저 콘텐츠",
"차단된 앱", "시스템 슬립/깨어남 전환" — 절대 특정 앱 이름이나
도메인 노출 안 함.
두 컴포넌트 분리¶
graph LR
Hooks[NSWorkspace / SetWinEventHook<br/>AXObserver / AX poll<br/>fast 200ms + slow 1s] -- "set() wake" --> Monitor[PrivacyMonitor]
Monitor -- "submit(reasons)" --> Controller[PauseController]
Controller -- "queue drain<br/>최신으로 coalesce" --> Worker[Worker thread]
Worker --> Recorder[recorder.stop_for_pause<br/>recorder.resume_after_pause]
Worker --> Overlay[overlay.show / hide]
PrivacyMonitor가 폴링 루프 (200ms fast / 1s slow)와 OS 이벤트 훅 listening 실행. Hook 콜백은 wake event에set()만 호출 —set_reasons나 COM/UIA 호출 절대 안 함 — 그래서 OS 이벤트 delivery 스레드를 절대 막지 않음.PauseController가 레코더 + 오버레이 사이드 이펙트 소유. Worker 스레드가 coalescingQueue드레인하고 한 곳에서만recorder.stop_for_pause/resume_after_pause/overlay.show/hide호출.put_nowait로queue.Full시 drop — 콜백 storm이 모니터를 막지 않음.
PrivacyMonitor가 작은 신호 표면 노출:
request_rescan()— 즉시 폴링 wake.request_rescan_debounced(min_interval_s=0.1)— burst coalesce (이벤트 기반 title/focus 리스너에서 사용).pulse_reason(reason, duration_s)—_compute_reasons안에서 만료되는 transient 사유.system_transition1.5초 블랙아웃에 사용.
일시정지 진입 시퀀스¶
recorder.stop_for_pause(purge_lookback_s)가 다음을 함:
_pause_requestedset으로 레코더 watchdog가 ocap 재실행 멈춤.- ocap 서브프로세스 즉시 SIGKILL (graceful EOS 없음 — ocap가 잡고 있는 pynput 키보드 tap이 SIGINT grace 기간 동안 macOS auth 다이얼로그를 막을 수 있어서). 최대 1초 wait, 그 후 return.
- inclusive
>=파일명 timestamp cutoff로 purge cascade 실행: - 현재 세션의 MCAP +
.log파일 — 통째로 삭제. pruner.evict_newer_than(cutoff_ns)— lookback 안의 키프레임.digest.evict_entries_newer_than(cutoff_ns)— cutoff ≥ 디지스트 row를 SQL에서 제거;digest.md를 가장 최신 살아남은 row에서 재작성.- 모든 화면에 빨간 테두리 오버레이 표시.
해제¶
recorder.resume_after_pause()가 _pause_requested 클리어 후
새 ocap 세션 spawn. 녹화 catch up까지 ~1초의 빈 공간.
브라우저 권한 tristate¶
<config root>/browser_auth.json:
authorized— 사용자가 macOS Automation 프롬프트 승인; URL 자동 일시정지 활성.denied— 사용자가 명시적으로 거부.fail_closed_on_denied_browsers: True(기본) 시 이 브라우저를 최상단으로 가져오면 일시정지 트리거.unknown— 사용자가 결정 안 함. fail-closed knob 무관 허용적. "Skip-all → 모든 브라우저 brick" 함정을 막는 critical fix.
수동 일시정지 영속성¶
기본적으로 재시작 시 유지 안 됨 (persist_manual_pause: False).
수동은 ephemeral ("지금 일시정지"); 자동 사유는 다음 launch에 라이브
신호에서 재적용.
스레드 모델 + 락 순서¶
PauseController가_lock소유.enter_pause/exit_pause만이 레코더/오버레이 호출 사이트.- Hook 콜백 (
NSWorkspace,SetWinEventHook)은 wake event에set()만 — COM/UIA 호출 또는 lock 절대 안 함. PauseController.submit()은put_nowait()사용 +queue.Full시 drop. Monitor가 다음 폴링에 재제출; 손실 무해.- Worker가 큐 드레인 후 transition 전에 latest 명령으로 coalesce.
_pause_lock(recorder)이 outermost;_digest_lock이 innermost._digest_lockholding 중 module-outside-digest 호출 금지.
atexit 순서¶
LIFO: recorder.stop 먼저 등록, privacy.stop 두 번째 등록.
종료 시:
privacy.stop먼저 — 오버레이 숨김, 리스너 unhook, worker join.recorder.stop이 ocap 정리.
알려진 한계¶
- Windows
cmd.exe/ ssh 안 콘솔 비밀번호 프롬프트가 별도 다이얼로그 윈도우 없이: UIA 표면 없음, 시스템 차원 latch 없음. Wrapping 프로세스 이름 (runas등)은 차단된 앱이라 권한 상승 플로우 자체가 일시정지 트리거하지만, 별도 프롬프트 윈도우 없이 plaincmd윈도우에 비밀번호 입력하면 missed. 수동 일시정지 사용. - Windows 레거시 Win32
#32770+ES_PASSWORD다이얼로그 (이전 VPN 클라이언트, SAP GUI, runas wrapper)는 foreground HWND의 자식 윈도우를 walking하여ES_PASSWORD스타일을 가진 visibleEdit로 감지. UIA의IsPasswordPropertyId가 동작할 때 선호. - Windows UAC consent 프롬프트는 secure desktop에서 실행; 사용자 모드 코드는 그 데스크톱을 enumerate 못 하므로 오버레이가 거기에 그릴 수 없음. 포커스가 일반 데스크톱으로 돌아오면 일시정지 fire.
- macOS
loginwindow는 차단 목록 — 잠금 화면 / 빠른 사용자 전환 동안 foreground "앱"이 됨. 의도된 동작. - Purge horizon: 감지 전
purge_lookback_s(기본 5초)보다 더 이전에 캡처된 바이트는 살아남음. knob을 늘리거나 수동 일시정지 더 빨리 트리거. - 오디오 / 클립보드: 오늘 Cue는 둘 다 캡처 안 함. 미래에 그런
기능 추가 시 작성 사이트에서
privacy.is_paused()를 반드시 consult 해야 함.
더 보기¶
cue.privacy— 모듈 API.- 스트리밍 레코더 — 일시정지에 무엇이 purge되는지.
- 프라이버시 컨트롤 — 사용자 facing 페이지.