Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions devlog/_fin/260716_grok_build_hardening/000_plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# 000 — Grok Build 대조 기반 xAI OAuth/캐시 경로 강화 로드맵

## Objective

공개된 공식 Grok Build 클라이언트(로컬 미러: `/Users/jun/Developer/codex/180_grok-build`)의
auth/wire/cache 계약을 기준으로, OpenCodex의 xAI 서빙 경로에서 확인된 4개 High + 2개
Medium 격차를 닫는다. 근거는 2026-07-16 두 SOL 독립 감사(소스 인용 기반, verdict FAIL)와
실제 프록시 스모크(GROK_SMOKE_OK, cached_tokens=128)이다.

## Constraints / Boundaries

- IN: xAI 경로 한정 — `src/responses/parser.ts`, `src/adapters/openai-chat.ts`,
`src/oauth/{local-token-detect,xai,store,index}.ts`, `src/server/responses.ts`,
`src/providers/xai-transport.ts` + 각 테스트.
- OUT: xAI Responses-native 업스트림 마이그레이션, usage `context_details`/cost-tick,
타 프로바이더, dashboard UI, `git push`(로컬 커밋만; push는 사용자 승인 필요).
- 실행 중인 ocx 프로세스(127.0.0.1:10100)는 서빙 경로 변경 후 재시작해야 반영된다
(레포 관례: green tests ≠ live proxy 반영).
- 각 work-phase = 1 PABCD 사이클. 두 decade doc을 한 B에서 구현하지 않는다.

## Loop-spec (C3, HOTL)

- Archetype: spec-satisfaction repair (verifier가 done을 정의).
- Trigger: 공식 소스 공개로 계약 격차가 확정됨.
- Goal: 위 5개 격차가 회귀 테스트 + 라이브 스모크로 닫힌 로컬 커밋.
- Non-goals: OUT 항목 전부.
- Verifier: `bun test <targeted>` + `bunx tsc --noEmit`(구성 시) + 서빙 phase는
`curl 127.0.0.1:10100/v1/responses` xai/grok-4.5 completed 스모크.
- Stop: 5 phase 완료(DONE) / 외부 인증 불능(BLOCKED) / ~4h(BUDGET_EXHAUSTED).
- Memory artifact: 본 유닛 decade docs + goalplan ledger.
- Escalation: 저장소 보안 약화가 필요해지면 UNSAFE로 중단.

## Dependency-ordered phase map

| Doc | Work-phase | 내용 | 의존 |
|-----|-----------|------|------|
| 010 | wp1_reasoning_fold | [reasoning, assistant] → 단일 assistant + reasoning_content 폴딩, parser-to-wire 회귀 | — (wire 형태가 이후 스모크의 기준) |
| 020 | wp2_localcli_ownership | `~/.grok/auth.json` 임포트 자격증명 단일 소유권(refresh 전 재읽기/세대 채택) | — |
| 030 | wp3_refresh_lock | `~/.opencodex/auth.json` cross-process 락 + 세대 재확인 + 토큰 교환 bounded retry | 020 (소유권 규칙 위에 직렬화) |
| 040 | wp4_401_replay | OAuth xAI 요청 업스트림 401 → singleflight 강제 refresh + 1회 replay | 030 (replay가 락/세대 경로를 사용) |
| 050 | wp5_header_parity | 공식 per-request 헤더 패리티 + 클라이언트 버전 호환 프로파일 | 010 (동일 transport 파일, 순서 고정) |

효과 기반 버킷 없음 — 각 phase는 이전 phase의 검증된 산출물을 소비한다(PHASE-SPLIT-01).

## 공식 계약 앵커 (감사 확정)

- folding: `xai-grok-sampling-types/src/conversation.rs:1814` (+회귀 8413)
- auth 단일 소유/락: `xai-grok-shell/src/auth/manager.rs:1529,1560,1604`
- 401 회복: `xai-grok-shell/src/auth/recovery.rs:440`, `manager_tests.rs:1293`
- retry: `xai-grok-sampler/src/retry.rs:14,210,225`
- 헤더: `xai-grok-sampler/src/client.rs:43-70,485`

## SoT sync target (SOT-SYNC-01)

확정: 레포에 CHANGELOG 파일과 xAI 전용 docs 문서는 없음. SoT는
`structure/04_transports-and-sidecars.md`(전송/사이드카 구조 문서)로 하고,
xAI 전송 계층의 락/401 replay/헤더 프로파일 변경을 각 phase D에서 반영한다.
`README.md:202`의 xAI 표는 프로토콜/모드 변경이 없는 한 그대로 둔다.

## Verification commands (확정)

- `bun test --isolate ./tests/xai-transport.test.ts` (+ phase별 신규 테스트 파일)
- `bun run typecheck` (= `bun x tsc --noEmit`)
- 서빙 phase 라이브 스모크: ocx 재시작 후
`curl -sS 127.0.0.1:10100/v1/responses -d '{"model":"xai/grok-4.5",...}'` → `status: completed`

## Acceptance criteria (활성화 시나리오 포함)

- c1: 새 parser-to-wire 테스트가 `[reasoning(summary), assistant]`와
`[reasoning(encrypted-only), assistant]` 두 입력에서 assistant 메시지 1개를 단언.
활성화: 테스트가 parser 경계를 실제 통과(기존 테스트처럼 수동 조립 금지).
- c2: Grok 저장소에 더 새로운 refresh 세대가 있을 때 OpenCodex가 IdP 호출 없이 채택.
활성화: temp HOME에 모의 `~/.grok/auth.json` 세대 교체 후 refresh 경로 호출.
- c3: 동시 refresh 2건 → 토큰 엔드포인트 호출 1건. 활성화: mock fetch 카운터.
- c4: mock 업스트림 401 → refresh 1회 + replay 1회, 두 번째 401은 그대로 전파.
활성화: 서빙 경로 통합 테스트 + ocx 재시작 후 라이브 스모크 completed.
- c5: 아웃바운드 헤더 스냅샷 테스트 + 라이브 스모크 completed.
52 changes: 52 additions & 0 deletions devlog/_fin/260716_grok_build_hardening/001_audit_synthesis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# 001 — A-round 1 감사 합성 (reviewer: sol/Aristotle, VERDICT: FAIL, blockers 9)

전 블로커 수용. 반박 없음. 문서별 처분:

| # | 대상 | 결정 | 수정 방향 |
|---|------|------|-----------|
| 1 | 010 | 수용 | pendingReasoning의 경계 규칙 완전 정의: user/tool-result/agent 경계에서 clear, function/tool call은 보존(공식 BackendToolCall 규칙), 복수 reasoning sibling은 공식대로 newline join, trailing reasoning은 폐기. 각 경계 활성화 테스트 추가 |
| 2 | 020 | 수용 | placeholder 주석 제거, 완전한 before/after 블록, `hasComparableGrokIdentity` 정의·export, Kiro/merge 분기 명시 보존 |
| 3 | 020 | 수용 | "newer" 판정을 단조 규칙으로 교체: authority = 더 늦은 `expiresAt` 세대(고정 수명 토큰에서 늦은 만료 = 늦은 발급), 동률이면 disk(Grok 소유) 우선. 단순 불일치를 newer로 간주 금지. stale-different-generation 테스트 추가 |
| 4 | 030 | 수용 | 락 획득/해제, stale takeover, retry 루프, index 통합, 테스트 본문 전부 컴파일 가능한 코드로 |
| 5 | 030 | 수용 | persist 직전 store-version compare-and-retry(CAS): 락 진입 시 스냅샷한 파일 fingerprint와 persist 직전 재읽기 비교, 불일치 시 재병합 후 기록. 무관 계정 mutate 경쟁 테스트 추가 |
| 6 | 030+040 | 수용 | 세대 표현 통일: opaque string fingerprint(SHA-256) + equality 비교. numeric/greater-than 제거 |
| 7 | 040 | 수용 | `...`/prose 블록을 완전한 코드로. 네트워크 seam은 030 확정 심볼 기준으로 서술하되 stale-check에 rebase 의무 유지 |
| 8 | 050 | 수용 | `x-grok-req-id` 생성을 transport resolve 시점이 아니라 outbound attempt(fetch wrapper 내부) 시점으로 이동. retry/sidecar 경로에서 attempt별 상이 ID + 고정 session/conv ID 테스트 |
| 9 | 050 | 수용 | 테스트를 산문 지시가 아닌 완전한 교체 코드로 |
| 10 | 020-050 | 수용(Medium) | 각 문서에 Risk/rollback 섹션 추가(재태깅·adopt·needsReauth 상태의 롤백 의미 포함) |

리뷰어 확인 사항(유지): 공식 앵커 전부 실재, 401은 recovery loop에 실제 도달(사이드카 경로는 미커버로 명시), 020 Grok 재읽기는 030 락과 재귀 없음.

## Round 2 (VERDICT: FAIL, blockers 5 — 1,2,6,7,9,10 해소 확인)

전 블로커 수용. 반박 없음.

| # | 대상 | 결정 | 수정 방향 |
|---|------|------|-----------|
| R2-1 | 030 | 수용 | authority 규칙 자체 재구현 금지: 020의 `shouldAdoptGrokGeneration(stored, disk, now, REFRESH_SKEW_MS)`를 import해 사용. 사용 불능(skew창 내) disk 세대는 refresh 입력 금지. later-but-unusable/equal-expiry/missing-expiry 조합 테스트 추가 |
| R2-2 | 030 | 수용 | 읽기-재병합-원자적 rename은 CAS가 아님. 결정: 모든 whole-store writer(`persist` 경유 전부)가 동일 글로벌 락을 짧게 획득(로그인·계정전환 포함, 쓰기 빈도 낮아 비용 무시 가능), refresh는 교환 전체 동안 보유. CAS-read 직후 결정적 테스트 seam 추가, 무관 writer 경쟁 테스트를 그 seam에서 수행 |
| R2-3 | 030 | 수용 | cleanup/release 전 exact-byte/stat 스냅샷 검증(스탈 takeover와 동일 기법). 부분 기록 실패·release-검사-unlink 사이 교체 테스트 추가 |
| R2-4 | 050 | 수용 | adapter-level fetchResponse hook이 서버의 `fetchWithHeaderTimeout`(responses.ts:1228) 경로를 우회함. req-id 주입을 timeout-capable fetch 경계 안쪽(transport fetch wrapper)으로 이동, `ctx.timeoutMs`/`ctx.stream` 의미 보존. timeout 활성화 테스트 추가 |
| R2-5 | 010 | 수용 | 서명 무결성: newline join은 unsigned plaintext sibling에만 적용. `ocxr1` 서명 sibling은 각각 독립 thinking part로 보존(서명↔텍스트 대응 유지). 서명 2개 sibling의 Anthropic replay 테스트 추가 |

## Round 3 (VERDICT: FAIL, blockers 3 — R2-1/R2-3/R2-5 해소 확인) + 아키텍처 재계획

LOOP-REPAIR-01 3회 도달 → 메인 에이전트가 계획을 구조적으로 변경(단순 재수정 아님).

| # | 대상 | 결정 | 아키텍처 변경 |
|---|------|------|---------------|
| R3-1,2 | 030 | 수용 | 글로벌 동기 락 폐기. **이원 락 설계**로 교체: (a) refresh-intent 락 — provider+account 단위 락 파일, IdP 교환 전체 동안 비동기 보유(이중 소비 방지); (b) store-write 락 — 짧게 잡는 글로벌 락, load-merge-persist 임계구역만 보호, 네트워크 I/O 절대 포함 금지. 모든 whole-store writer는 async 직렬 큐 + store-write 락 경유(동기 `Atomics.wait` 금지 — Bun 이벤트 루프 차단). 기존 동기 호출자는 async로 전파 전환(호출부 실측 후 명시). refresh 흐름: intent 락 → 세대 재확인 → 교환 → store-write 락 안에서 재읽기·병합·persist → 해제. 동일 프로세스 writer-during-refresh 활성화 테스트 필수 |
| R3-3 | 050 | 수용 | 서버 seam을 050이 명시 소유: `fetchWithHeaderTimeout`(responses.ts:1228)에 executor 파라미터 추가(기본 global fetch, xAI route는 `route.provider.fetch` 전달), ordinary/recovery 호출자 전부 before/after 제시. 테스트는 실제 서버 경로 통과로 UUID 회전 + TimeoutError 증명. 040과 responses.ts 중복은 stale-check rebase 의무로 관리 |

## Round 4 (VERDICT: FAIL, blockers 1 — R3 3건 전부 해소 확인)

| # | 대상 | 결정 | 수정 방향 |
|---|------|------|-----------|
| R4-1 | 030 | 수용(메인 직접 수정) | 교환 중 동일 계정 write 경쟁: `mergeAccountCredential`에 `expectedGeneration` 가드 추가 — 저장 세대가 시도 세대와 다르면 덮어쓰지 않고 superseded 반환, 사용 가능하면 채택/불가하면 fail-closed(`OAuthLoginRequiredError`, 제3자 세대 재귀 소비 금지 근거 기록). `needsReauth`는 `markAccountNeedsReauthIfGeneration`으로 세대 조건부. 성공/터미널 실패 양쪽의 replacement-during-exchange 테스트 추가 |

## Round 5 (blockers 2 — R4-1 본체 해소 확인)

| # | 대상 | 결정 | 수정 방향 |
|---|------|------|-----------|
| R5-1 | 030 | 수용(메인 직접 수정) | 020-adoption 분기(pre-IdP)에도 동일 `expectedGeneration` 가드 적용: superseded 시 usable→채택, 아니면 fail-closed. adoption 경쟁 테스트 방침 기록 |
| R5-2 | 030 | 수용(메인 직접 수정) | 테스트 코드 결함: `XaiTokenRequestError` 인자 순서 `(status, oauthError, message)`로 정정, 가상 픽스처 별칭 제거 — 파일 내 실제 헬퍼(`seed`/`def`/`saveCredential`/`getAccountSet`)로 재작성 |
Loading