Files
gravity_control/docs/approval-flow.md
Variet Worker 2d9fe964f6 fix(bridge): v0.3.12 approval state management — sawRunningAfterPending gate + approval-flow.md system doc
- processResponseFile: set sawRunningAfterPending=true instead of removing resets
  (prevents infinite pending loop AND known-issues L479 auto_resolve regression)
- Hoist sawRunningAfterPending to module level for cross-function access
- Add recentPendingSteps memory dedup Map (60s TTL) for file-deletion resilience
- Create docs/approval-flow.md: complete system flow guide with state diagram
- Update known-issues.md: 2 new entries (state reset fix, memory dedup)
2026-03-16 11:11:50 +09:00

9.1 KiB

Gravity Bridge — 승인 시스템 완전 Flow 가이드

Last Updated: 2026-03-16 (v0.3.12) SSOT: 이 문서는 승인 시스템의 전체 데이터 플로우와 상태 관리를 설명합니다. 수정 시: known-issues.md와 동기화 필수


1. 시스템 아키텍처 개요

AG IDE (Antigravity)
  ├── Extension (extension.ts)  ← Bridge 핵심
  │     ├── setupMonitor()     ← 5초 폴링 (GetAllCascadeTrajectories)
  │     ├── step_probe         ← WAITING step 감지 (GetCascadeTrajectorySteps)
  │     ├── DOM Observer       ← 렌더러 스크립트 (버튼 감지)
  │     ├── processResponseFile() ← Discord 응답 처리
  │     ├── writePendingApproval() ← pending 파일 생성 (dedup 포함)
  │     └── tryApprovalStrategies() ← RPC 실행
  ├── bridge/ (파일 시스템)
  │     ├── pending/*.json     ← 승인 대기 목록
  │     ├── response/*.json    ← Discord 응답
  │     ├── snapshot/*.json    ← 채팅 릴레이
  │     └── register/*.json   ← 세션-프로젝트 매핑
  └── Bot (bot.py)            ← Discord 통신
        ├── pending_approval_scanner  ← 3초 폴링
        ├── auto_approve_scanner     ← !auto 토글
        └── snapshot_scanner         ← 채팅 릴레이

2. 데이터 플로우: 승인 요청 → 응답

2.1 Pending 생성 경로 (2개)

경로 A: Step Probe → writePendingApproval()

1. setupMonitor() 5초 폴링 → GetAllCascadeTrajectories
2. RUNNING + delta=0 + modTime 미변경 → stall 감지
3. consecutiveIdleCount >= 1 && !stallProbed
4. GetCascadeTrajectorySteps → WAITING step 발견
5. si !== lastPendingStepIndex 확인 (dedup)
6. writePendingApproval() 호출
   ├── recentPendingSteps 메모리 dedup 체크 (60초 TTL)
   ├── 기존 pending 파일 dedup 체크 (15초 윈도우)
   └── pending 파일 생성 + recentPendingSteps에 기록
7. stallProbed = true, lastPendingStepIndex = si

경로 B: DOM Observer → HTTP POST /pending

1. 렌더러 MutationObserver → 버튼 감지 (Run, Accept, Allow 등)
2. FALSE_POSITIVE_RE 필터 (Proceed, Continue, Deny 등 차단)
3. "Run"은 sessionStalled=true && lastPendingStepIndex < 0 일 때만 통과
4. HTTP POST /pending → Extension HTTP 핸들러 (L738-812)
5. 파일 직접 생성 (writePendingApproval() 우회!)
   ⚠️ recentPendingSteps 메모리 dedup 미적용

주의: 경로 B는 writePendingApproval()의 메모리 dedup을 우회합니다. 하지만 lastPendingStepIndex >= 0일 때 "Run" 필터(L757)와 15초 파일 기반 dedup이 방어합니다.

2.2 Response 처리 경로

1. Bot pending_approval_scanner → pending 파일 발견
2. auto-approve OR Discord 버튼 → write_response() 호출
   ├── response/*.json 생성
   └── pending/*.json 삭제 (!)
3. Extension response watcher (fs.watch + 3초 폴링 fallback)
   → processResponseFile() (300ms 딜레이)
4. processResponseFile():
   ├── 파일 존재 확인 (HTTP handler가 먼저 삭제했을 수 있음)
   ├── stale timeout 필터 (2분)
   ├── auto_resolved/expired 상태 skip
   ├── project_name 필터
   └── tryApprovalStrategies() → RPC 실행
5. sawRunningAfterPending = true (v0.3.12 핵심 수정)
6. response 파일 삭제 (DOM observer 경로는 HTTP handler에 위임)

3. 상태 변수 완전 참조

3.1 모듈 레벨 변수 (extension.ts)

변수 위치 역할 설정 리셋
lastPendingStepIndex L707 마지막으로 pending을 생성한 step index step_probe(L2047,2108), error_probe(L2178) delta>0(L1972), session change(L1841)
stallProbed L708 현재 stall에서 probe 완료 여부 step_probe(L2046,2107,2177) delta>0(L1980), modTime changed(L1986), session change(L1842)
sawRunningAfterPending L709 pending 후 delta>0 발생 여부 (auto_resolve gate) delta>0(L1979), processResponseFile(L2622) step_probe(L2049,2110)
sessionStalled L706 AG가 stall 상태인지 idle count≥1(L1993) delta>0(L1937), not WAITING(L2135)
recentPendingSteps L54 메모리 기반 pending dedup Map writePendingApproval(L2787,2837) delta>0(L1974), TTL 60초

3.2 setupMonitor() 로컬 변수

변수 역할
consecutiveIdleCount 연속 idle poll 수 (stall 감지 debounce)
lastPendingTime 마지막 pending 생성 시간
lastModTime 마지막 modifiedTime (thinking vs approval 구분)
wasRunning RUNNING→IDLE 전이 추적
lastResponseCaptureStep 응답 캡처 dedup

4. 핵심 상태 전이 다이어그램

[IDLE] ──step진행(delta>0)──→ [RUNNING]
                                  │
                      delta=0 + modTime 변동 → [THINKING] (stall 카운터 리셋)
                      delta=0 + modTime 고정 → [STALLED]
                                                    │
                                  !stallProbed → step_probe 실행
                                                    │
                              WAITING 발견 → [PENDING_CREATED]
                              (stallProbed=true, lastPendingStepIndex=si)
                                                    │
                 ┌──────────────────────────────────┤
                 ▼                                  ▼
         [DISCORD_APPROVED]                  [AG_LOCAL_APPROVED]
     processResponseFile()               delta > 0 + !sawRunningAfterPending
     sawRunningAfterPending=true          → auto_resolve → Discord 알림
                 │                                  │
                 └──────────────────────────────────┘
                                  │
                            [STEP_PROGRESSED]
                       delta > 0 → 전체 리셋
                    lastPendingStepIndex = -1
                    stallProbed = false
                    sawRunningAfterPending = true
                    recentPendingSteps 클리어

5. v0.3.12 수정 — 왜 sawRunningAfterPending = true인가

5.1 이전 문제: 무한 루프 (v0.3.11 이전)

processResponseFile이 lastPendingStepIndex = -1로 리셋 → step_probe가 같은 WAITING step 재감지 → 새 pending → auto-approve → response → 다시 리셋 → 무한 루프

5.2 v0.3.11 시도: 모든 리셋 제거

lastPendingStepIndexstallProbed 리셋을 완전 제거 → 무한 루프 해소, 하지만:

  • known-issues L479 회귀: Discord 승인 후 AG 진행 시 sawRunningAfterPending=false + lastPendingStepIndex>=0 → auto_resolve 중복 알림
  • stallProbed 영구 잠금 우려 (실제로는 delta>0에서 자연 리셋)

5.3 v0.3.12 해결: sawRunningAfterPending = true

Discord 승인 response 처리 후 sawRunningAfterPending = true만 설정:

  1. 무한 루프 방지: lastPendingStepIndex 유지 → dedup 작동
  2. auto_resolve 중복 방지: sawRunningAfterPending = true → L1939 조건 FALSE
  3. stallProbed 자연 리셋: delta>0에서 L1980
  4. 신호 수집 무영향: step_probe, GetAllCascadeTrajectories 코드 미변경

6. 위험 지점 목록 (수정 시 반드시 확인)

코드 위치 위험 확인 사항
processResponseFile 리셋 (L2607+) 무한 루프 or auto_resolve 중복 sawRunningAfterPending = true만 설정. lastPendingStepIndexstallProbed는 건드리지 말 것
HTTP POST /pending (L738-812) DOM observer 경로가 writePendingApproval() 우회 "Run" 필터(L757)와 파일 기반 dedup이 방어
bridge.py write_response (L460-461) pending 파일 삭제 메모리 dedup(recentPendingSteps)이 재생성 방지
auto_resolve (L1939-1977) 중복 알림 sawRunningAfterPending gate 확인
step_probe offset (L2025-2070) 775-step 리밋 stepOffset으로 최신 step 조회
session change (L1832-1854) 모든 상태 초기화 lastPendingStepIndex, stallProbed, sawRunningAfterPending 모두 리셋

7. 과거 이슈 교차 참조

이슈 (known-issues.md) 방어 코드 상태
L252: 중복 승인 요청 writePendingApproval dedup (15초 윈도우 + 메모리 dedup) 해결
L264: pending 무한 누적 write_response()에서 삭제 + 5분 age filter 해결
L288: DOM observer ENOENT isDomObserver 분기 삭제 (L2619) 해결
L384: 크로스 프로젝트 MERGE project_name 가드 (L2774) 해결
L444: DEDUP 크로스 세션 conversation_id 가드 (L2794) 해결
L474-479: auto_resolve 중복 sawRunningAfterPending = true (v0.3.12) 해결
L493: Double-Fire auto-approve Extension auto-approve 경로 제거, Bot 단일 경로 해결
L499: Deny false positive FALSE_POSITIVE_RE + Bot reject guard 해결