# 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 시도: 모든 리셋 제거

`lastPendingStepIndex`와 `stallProbed` 리셋을 완전 제거 → **무한 루프 해소**, 하지만:
- 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`만 설정. `lastPendingStepIndex`와 `stallProbed`는 건드리지 말 것 |
| 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 | ✅ 해결 |
