From e6152e51da7ce419f325e41e13197bd3daa365c2 Mon Sep 17 00:00:00 2001 From: ComBba Date: Thu, 14 May 2026 10:53:33 +0900 Subject: [PATCH] chore: commit session handoffs + Chrome verify scripts + final report MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds 9 session handoffs, gap-analysis (11 docs), reddit assets, and 4 Chrome verify scripts that helped resolve the OpenAI 400 root cause. Why now: - D-9 (2026-05-18) publish window approaching, want a clean baseline before module split + demo prep land in subsequent PRs. - Scripts contain no embedded secrets — browser_cookie3 reads cookies at runtime from the user's Chrome profile (.venv-chrome-auth/ + playwright/.auth/ both gitignored). What's still untracked (intentional): - .venv-chrome-auth/ (Python venv) - playwright/ (cookies + debug captures with PII) --- .gitignore | 5 + claudedocs/2026-05-12-session-handoff.md | 114 ++++ ...026-05-13-install-debug-session-handoff.md | 240 ++++++++ ...-05-13-openai-400-probe-session-handoff.md | 254 +++++++++ .../2026-05-13-openai-probe-v3-handoff.md | 249 ++++++++ ...2026-05-13-platform-bug-session-handoff.md | 248 ++++++++ ...2026-05-13-reddit-setup-session-handoff.md | 153 +++++ claudedocs/2026-05-13-session-handoff.md | 127 +++++ .../2026-05-14-openai-400-final-report.html | 521 +++++++++++++++++ .../2026-05-14-openai-400-resolved-handoff.md | 240 ++++++++ claudedocs/gap-analysis/00-SUMMARY.md | 91 +++ claudedocs/gap-analysis/01-automod-parity.md | 146 +++++ .../gap-analysis/02-competitive-landscape.md | 94 +++ .../gap-analysis/03-hackathon-judging.md | 77 +++ claudedocs/gap-analysis/04-security-abuse.md | 220 +++++++ .../gap-analysis/05-code-architecture.md | 99 ++++ .../06-reliability-failure-modes.md | 69 +++ claudedocs/gap-analysis/07-moderator-ux.md | 203 +++++++ claudedocs/gap-analysis/08-devvit-idiom.md | 142 +++++ .../gap-analysis/09-rule-expressiveness.md | 243 ++++++++ .../gap-analysis/10-demo-storytelling.md | 164 ++++++ claudedocs/gap-analysis/11-test-coverage.md | 73 +++ ...athon-audit-20260512-reddit-mod-tools.html | 169 ++++++ .../community-banner-1920x384.png | Bin 0 -> 127265 bytes .../reddit-assets/community-icon-256-bg.png | Bin 0 -> 12556 bytes .../reddit-assets/community-icon-256.png | Bin 0 -> 13140 bytes .../reddit-assets/community-icon-512.png | Bin 0 -> 33822 bytes .../socialseed-original-apple-icon.png | Bin 0 -> 19819 bytes .../socialseed-original-icon.png | Bin 0 -> 37541 bytes claudedocs/reddit-assets/sprout-logo.svg | 48 ++ claudedocs/reddit-setup-checklist.html | 364 ++++++++++++ scripts/build-final-report.py | 536 ++++++++++++++++++ scripts/chrome-reddit-v2.py | 223 ++++++++ scripts/chrome-reddit-v3.py | 185 ++++++ scripts/chrome-reddit-verify.py | 255 +++++++++ 35 files changed, 5552 insertions(+) create mode 100644 claudedocs/2026-05-12-session-handoff.md create mode 100644 claudedocs/2026-05-13-install-debug-session-handoff.md create mode 100644 claudedocs/2026-05-13-openai-400-probe-session-handoff.md create mode 100644 claudedocs/2026-05-13-openai-probe-v3-handoff.md create mode 100644 claudedocs/2026-05-13-platform-bug-session-handoff.md create mode 100644 claudedocs/2026-05-13-reddit-setup-session-handoff.md create mode 100644 claudedocs/2026-05-13-session-handoff.md create mode 100644 claudedocs/2026-05-14-openai-400-final-report.html create mode 100644 claudedocs/2026-05-14-openai-400-resolved-handoff.md create mode 100644 claudedocs/gap-analysis/00-SUMMARY.md create mode 100644 claudedocs/gap-analysis/01-automod-parity.md create mode 100644 claudedocs/gap-analysis/02-competitive-landscape.md create mode 100644 claudedocs/gap-analysis/03-hackathon-judging.md create mode 100644 claudedocs/gap-analysis/04-security-abuse.md create mode 100644 claudedocs/gap-analysis/05-code-architecture.md create mode 100644 claudedocs/gap-analysis/06-reliability-failure-modes.md create mode 100644 claudedocs/gap-analysis/07-moderator-ux.md create mode 100644 claudedocs/gap-analysis/08-devvit-idiom.md create mode 100644 claudedocs/gap-analysis/09-rule-expressiveness.md create mode 100644 claudedocs/gap-analysis/10-demo-storytelling.md create mode 100644 claudedocs/gap-analysis/11-test-coverage.md create mode 100644 claudedocs/hackathon-audit-20260512-reddit-mod-tools.html create mode 100644 claudedocs/reddit-assets/community-banner-1920x384.png create mode 100644 claudedocs/reddit-assets/community-icon-256-bg.png create mode 100644 claudedocs/reddit-assets/community-icon-256.png create mode 100644 claudedocs/reddit-assets/community-icon-512.png create mode 100644 claudedocs/reddit-assets/socialseed-original-apple-icon.png create mode 100644 claudedocs/reddit-assets/socialseed-original-icon.png create mode 100644 claudedocs/reddit-assets/sprout-logo.svg create mode 100644 claudedocs/reddit-setup-checklist.html create mode 100644 scripts/build-final-report.py create mode 100644 scripts/chrome-reddit-v2.py create mode 100644 scripts/chrome-reddit-v3.py create mode 100644 scripts/chrome-reddit-verify.py diff --git a/.gitignore b/.gitignore index bd00ef6..b0b992b 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,8 @@ Thumbs.db # Generated docs docs/*.html + +# Chrome verify automation (Python venv + Playwright artifacts) +# These contain Reddit session cookies + page captures with potential PII. +.venv-chrome-auth/ +playwright/ diff --git a/claudedocs/2026-05-12-session-handoff.md b/claudedocs/2026-05-12-session-handoff.md new file mode 100644 index 0000000..c968c4c --- /dev/null +++ b/claudedocs/2026-05-12-session-handoff.md @@ -0,0 +1,114 @@ +# vibe-mod — Session Handoff (2026-05-12) + +> `/handon` 으로 로드. 프로젝트 전반 계획은 레포 루트의 `HANDOFF.md`, 이 파일은 *이 세션* 요약 + 다음 액션. + +## §0 두 줄 요약 + +- **무엇**: Reddit "Mod Tools and Migrated Apps Hackathon — Best New Mod Tool ($10K)" 출품작 `vibe-mod`(모더레이터가 자연어로 룰 작성 → OpenAI gpt-5.4-mini가 결정론 JSON으로 컴파일, shadow/dry-run/rollback). 이 세션에서 테스트·SDK 정합화·OpenAI 검증·dev 툴링·전체 Devvit 문서 정독·dry-run 구현·아이콘·PBT·`@devvit/test` 채택·해커톤 감사까지 완료. 코드/검증은 다 됐고, 남은 건 **사람이 해야 하는 Devvit wizard → `devvit upload` → `devvit publish`(리뷰 ~1주) → playtest 검증 → Devpost 제출**. +- **다음 세션 1순위**: (a) 사용자가 wizard를 했으면 `npm run build`/`devvit logs` 막힌 지점 디버깅; (b) 안 했으면 — 루트 `README.md` + Devpost 7섹션 설명 템플릿을 (wizard 없이) 작성. 둘 중 사용자 상태에 따라. + +## §1 진행한 작업 (시간순) — PR #1~#16, 전부 main에 merge (squash 아님) + +| Phase | PR | 내용 | +|---|---|---| +| A. 테스트·acceptance·starter rules | #1, #2 | vitest 하네스(`test/devvit-testkit.ts` in-memory Redis + Devvit 더블) + 6 unit + 6 route 호출 테스트; `scripts/acceptance.ts` G1~G4; 5 starter rules + `onAppInstall` seed; `devvit.json` orphaned `activateRuleForm` 제거 | +| B. dev 인프라 | #2, #3, #4, #5(closed) | ESLint 9 flat + Prettier + simple-git-hooks(pre-commit lint-staged / pre-push typecheck+test) + CI(lint→format→tsc→test:coverage→acceptance) + Dependabot(@devvit 그룹); `npm run replay`(이벤트 로컬 replay, fixtures/) + `npm run doctor`(배포 전 프리플라이트); `docs/new-mod-checklist.md`; dependabot actions v6 bump 머지(#3,#4), eslint v10 bump 비호환으로 close(#5) | +| C. 외부 리뷰 반영 | #6 | gemini/coderabbit/codex 피드백: `r_shouting_title` body→title ratio 버그 → 새 fact `content.title.upperCaseRatio`; testkit `zAdd` 중복 멤버/`zRange` reverse 수정; doctor semver; setup `beforeEach` mock-leak; replay `redisHashes/Zsets` | +| D. OpenAI 실API 검증 + 모델/추론 튜닝 | #7, #8, #9, #10, #11, #12 | `npm run openai:smoketest`(실 OpenAI API, 7/7); **프로덕션 버그 수정**: `max_tokens`→`max_completion_tokens`, `temperature` 제거(gpt-5.x); `reasoning_effort:'none'` + `verbosity:'low'`; 모델 비교(gpt-5.4-mini 가장 빠름~1.2s/7-7 ← 기본, nano~1.5s, 풀 5.4~2.1s 비추천, gpt-5-mini 토큰 truncation, gpt-5-nano/gpt-4.1-* 403); `.env.example`; HANDOFF.md 갱신 | +| E. Devvit Web 문서 정합화 | #13 | Playwright로 비-게임 Devvit 문서 58페이지 크롤 → `docs/devvit-reference.md`(440KB) + `docs/devvit-conformance-notes.md`. **`devvit build`가 실패했을 버그 수정**: `devvit.json`에 필수 `server` 블록 없음 + CJS 서버 번들 빌드 없음 → `vite.config.ts`(SSR→`dist/server/index.cjs`, CJS, minify) + `devvit.json` `server`/`scripts`/`dev` + `vite` devDep + `npm run build`=`tsc --noEmit && vite build`. deps: `@devvit/web` 하나만(@devvit/reddit/redis 제거), `import {redis}`→`@devvit/web/server`, `TaskRequest/Response`, CLI `@devvit/cli`→`devvit`. `context.subredditName/Id` 사용(API 호출 0), `devvit-helpers` 동기화. CI에 build+bundle-load 단계 추가 | +| F. dry-run 구현 | #14 | `/internal/scheduler/dry-run-replay` (v0.1 스텁이었음): 최근 ≤10 포스트를 draft 룰로 replay(액션 0) → `${sub}:dryrun:${ruleId}` 요약 → Dashboard "Dry-run preview" 섹션 표시. +6 scheduler tests + 1 dashboard test | +| G. 아이콘 + PBT | #15 | `assets/icon.png` 1024×1024(`scripts/build-icon.ts` 자체 PNG 인코더, 네이티브 dep 0) + `devvit.json` `marketingAssets.icon`; fast-check property tests(rule-schema: parse 멱등·strict·closed fact/action·depth; evaluator: total·empty all/any=false·singleton=identity·not 대합·all/any=∧/∨·numeric op 일관성) | +| H. `@devvit/test` 채택 | #16 | `@devvit/test` devDep + `vitest.devvit.config.ts` + `npm run test:devvit`(CI 포함) + `src/server/executor.devvit.test.ts`(공식 하네스, 진짜 in-memory Redis 트랜잭션, 3 tests). 기존 13개 파일 전면 마이그레이션은 churn 대비 이득 적어 안 함(문서화: `docs/devvit-conformance-notes.md`) | +| I. 해커톤 감사 | (skill) | `hackathon-audit` 실행 → `claudedocs/hackathon-audit-20260512-reddit-mod-tools.html`. 판정: PARTIAL — 엔지니어링 완료, 배포+제출 자산 미완 | + +## §2 현재 상태 + +**git**: branch `main`, working tree clean (단 `claudedocs/`는 untracked — 분석 산출물, 커밋 안 함). 최신 commit `1444c93` (Merge PR #16). PR #1~#16 전부 merge, open PR 0. Repo: `https://github.com/Two-Weeks-Team/vibe-mod` (public, MIT). + +**Live URLs**: 없음 — Devvit 호스팅이고 앱이 아직 `devvit upload` 안 됨. 갤러리: `https://two-weeks-team.github.io/reddit-mod-tools-port-gallery/vibe-mod-final-plan.html`. + +**메트릭**: 168 main tests + 3 `@devvit/test` tests (1 skipped = replay-runner) | `tsc --noEmit` clean | ESLint 0-warning | Prettier clean | `npm run acceptance` 4/4 (G1~G4) | `npm run doctor` 0 hard issues, 2 warnings (devvit login 안 됨, `.devvit-app-id` 없음) | `npx vite build` → `dist/server/index.cjs` ~2.1MB, `require()` 가능 | CI green (install→lint→format:check→tsc→test:coverage→test:devvit→acceptance→vite build→bundle loads). | `npm run openai:smoketest` 마지막 실행 7/7 (gpt-5.4-mini, ~$0.0001/compile). + +**환경**: node v24.15.0 / npm 11.12.1 / `@devvit/cli` 0.12.23. (CI는 `.nvmrc` = 22.) `.env`는 사용자가 OpenAI 키 넣음(billing 활성 상태). `.devvit-app-id` 없음(wizard 생성). + +**오늘**: 2026-05-12. 해커톤 마감: **2026-05-27 18:00 PDT — D-15** (firm, grace 없음). + +## §3 다음 세션에서 할 수 있는 것 + +**즉시 가능 (Claude, wizard 불필요):** +- 루트 `README.md` 작성 — 태그라인 + CI 배지 + "무엇을 하는가" + 아키텍처 한 단락 + Fetch-Domains 섹션 + 스크린샷 자리 + 갤러리 final-plan 링크. (현재 `docs/README-vibe-mod.md`만 있음 — 레포 front page 없음.) +- Devpost 7섹션 설명 템플릿 — Inspiration / What it does / How built / Challenges / Accomplishments / Learned / What's next + Project-Impact(3 커뮤니티 후보) + Built-With 태그 + 앱 링크 자리. `docs/devpost-submission.md`로. +- (선택, 평가 보고서 권고) **fact 레이어 확장** — `content.title.upperCaseRatio` 외에 repost 감지·cross-sub 도배·`content.isEdited`·계정 유사도·언어 감지 등 fact 추가 (rule-schema FactPaths + fact-bag + system-prompt 자동). AutoMod 대비 ceiling 올리는 v0.2 방향. 큰 작업이라 별도 PR. +- (선택) 기존 route 테스트를 `@devvit/test`로 마이그레이션 — churn 큼, 평행 vitest project 필요. 권장 안 함 (새 모드는 처음부터 `@devvit/test`). + +**사용자 입력/행동 필요:** +- Devvit "Mod Tool" wizard (`developers.reddit.com/new`) → vibe-mod 산출물 overlay (레포 HANDOFF.md Step 1; `vite.config.ts`도 overlay, `src/client/` 지우지 말 것) → `.devvit-app-id` 생성 +- `npm run build` (`tsc --noEmit && vite build`) → `npm run doctor` → `devvit upload` → 앱 링크 확보 +- `devvit settings set openaiApiKey` (배포용 키 — `devvit upload` 후에만 가능) → `devvit settings list` +- `npm run dev` (playtest) → 3 MANUAL 게이트: Compose 메뉴 렌더 → 폼 → OpenAI compile 라운드트립 → undo. 막히면 `devvit logs` + 에러 가져오기 (다음 세션이 디버깅) +- `devvit publish --public` — ~D-9(2026-05-18)까지 시작 (리뷰 ≈1주) +- 데모영상(<1분, BGM 없음) + 스크린샷 ≥3 + ToS/Privacy HTML 호스팅 → Devpost 앱 상세 폼 (D-3~D-1) + +## §4 할 수 없는 것 (외부 변수) + +- **Devvit 런타임 검증** — `devvit build`/`playtest`/`upload`/`publish`는 Reddit OAuth + 앱 등록 필요, Devvit는 로컬 에뮬레이터 없음. `@devvit/test` + `replay`로 로직은 커버했지만 Devvit의 라우팅/페이로드 주입/RPC는 블랙박스. → `devvit playtest`가 유일한 검증 (사용자만 가능). +- **앱 리뷰** — `devvit publish --public` 후 Reddit admin 리뷰 ≈1주, 통제 불가. 늦으면 unlisted install 링크로 fallback. +- **데모영상/스크린샷** — 러닝 앱 필요 → wizard + playtest 이후에만. + +## §5 추가로 필요한 것 (사용자 확인) + +- OpenAI 계정 billing 활성 유지 (이미 됨, `npm run openai:smoketest`로 재확인 가능). 키는 `.env`(로컬, gitignore) + Devvit secret(배포) 둘 다. +- 데모용 sub: <200 멤버 (invite-only로 관리). 200 초과 시 실격. +- (선택) `devvit settings set subredditOpenaiApiKey` — sub별 BYOK 키 (50/day 쿼터 우회). +- 환경 점검: `npm install` 시 git hooks 설치됨 (`prepare` → simple-git-hooks). `npm ci`는 esbuild EBADPLATFORM으로 안 됨 → `npm install` 사용 (CI도). + +## §6 다음 세션 시작 프롬프트 + +```text +/handon + +이전 세션 핸드오프: claudedocs/2026-05-12-session-handoff.md +프로젝트 전반 계획: HANDOFF.md (레포 루트) + +읽고 아래에 답한 뒤 진행하세요: +1. Devvit wizard를 진행했나요? (yes → 어디서 막혔는지 / devvit build·playtest 에러 + devvit logs / no → 아래 2) +2. (wizard 안 했으면) 지금 루트 README.md + Devpost 7섹션 설명 템플릿을 작성할까요? +3. fact 레이어 확장(repost/cross-sub/isEdited/언어감지 등 — AutoMod 대비 ceiling 올리기)을 v0.2로 진행할까요? (큰 작업, 별도 PR) +4. 그 외 우선순위 있나요? + +D-day: 2026-05-27 18:00 PDT (해커톤 마감, firm). publish 리뷰 ~1주 → ~D-9(5/18)까지 devvit publish 시작 필요. +``` + +## §7 핵심 자산 위치 + +| 경로 | 내용 | +|---|---| +| `HANDOFF.md` (루트) | 프로젝트 전반 계획 — 17일 일정, 7 hard lock, 비용, 산출물 인벤토리. **세션마다 갱신됨.** | +| `devvit.json` | Devvit 설정 — `server`/`marketingAssets`/`permissions`/`settings`/`menu`/`forms`/`triggers`/`scheduler`/`scripts`/`dev` | +| `vite.config.ts` | 서버 SSR 빌드 → `dist/server/index.cjs` (CJS; `devvit.json` scripts가 이걸 실행) | +| `src/server/index.ts` | Hono 라우트 — menu/form/trigger/scheduler 핸들러, `isCallerModerator` 가드, `callOpenAI`, dry-run-replay | +| `src/server/{evaluator,fact-bag,executor,devvit-helpers}.ts` | 결정론 평가기 / fact bag / 액션 실행+audit+rollback / SDK 어댑터(context, T1/T3) | +| `src/shared/{rule-schema,system-prompt,starter-rules}.ts` | Zod 스키마 / gpt-5.4용 프롬프트+few-shot / 5 starter rules | +| `scripts/{acceptance,devvit-doctor,replay,build-icon,openai-smoketest}.ts` | `npm run acceptance`/`doctor`/`replay`/`build:icon`/`openai:smoketest` | +| `test/{devvit-testkit,setup}.ts` + `vitest.devvit.config.ts` | 손수 만든 하네스 + 프로젝트 setup + 공식 `@devvit/test` 설정 | +| `docs/devvit-reference.md` | developers.reddit.com/docs 비-게임 58페이지 스냅샷 (Playwright 크롤) | +| `docs/devvit-conformance-notes.md` | vibe-mod ↔ Devvit Web 문서 정합 감사 (수정·확인·미해결 + @devvit/test 채택) | +| `docs/new-mod-checklist.md` | 다음 Devvit 모드 시작 시 복사할 인프라 목록 + SDK gotcha 목록 | +| `docs/{README-vibe-mod,tos,privacy}.md` | (현 README — 루트 README는 없음) / ToS / Privacy Policy | +| `claudedocs/hackathon-audit-20260512-reddit-mod-tools.html` | 해커톤 제출 준비도 감사 (4축 + Top 갭 + 액션 체크리스트) | +| `assets/icon.png` | 앱 아이콘 1024×1024 (`npm run build:icon`으로 재생성) | +| `fixtures/*.json` | replay 예제: post-submit / compose-rule-submit / undo-action | +| `.github/workflows/ci.yml` + `dependabot.yml` | CI 파이프라인 + 의존성 봇 | + +## §8 알려진 issue / open question + +- **`devvit build`/`devvit upload`/`devvit publish` 실런타임 미검증** — 타입·번들·테스트는 통과하지만 Devvit 안에서 실제로 도는지는 wizard + playtest로만 확인. SDK mismatch 1~2건 더 나올 가능성 있음 (이미 ~45건 + max_tokens 등 고침). +- **루트 `README.md` 없음** — Devpost "public repo" 링크 + GitHub front page용으로 작성 필요. +- **Devpost 제출 자산 0** — 7섹션 설명, 데모영상, 스크린샷, Project-Impact, 앱 링크 모두 미작성/미생성. 일정상 D-3~D-1이지만 README + 설명 템플릿은 지금 가능. +- **`@devvit/test` 부분 마이그레이션** — 기존 route 테스트는 손수 하네스(`devvit-testkit.ts`) 유지. 전면 마이그레이션은 평행 vitest project 필요 + 이득 적음. 새 모드는 처음부터 `@devvit/test`. +- **fact 레이어가 좁음** — closed `FactPaths` ~22개로는 표현 못 하는 흔한 룰 많음 (repost, cross-sub 도배, isEdited, 계정 유사도, 언어). AutoMod 대비 capability ceiling이 낮은 근본 원인. 해커톤 후 v0.2 1순위 (별도 PR). +- **CodeRabbit 인라인 코멘트** — PR #1~#16에서 모두 반영 완료 (PR #6에서 일괄). 새 PR마다 자동 리뷰 — 다음 세션에서 새 PR 만들면 다시 확인. +- `claudedocs/`는 gitignore 안 됨 — `git status`에 untracked로 뜸. 커밋할지 사용자 판단 (분석 산출물). + +--- +작성: 2026-05-12 / `/handoff` skill diff --git a/claudedocs/2026-05-13-install-debug-session-handoff.md b/claudedocs/2026-05-13-install-debug-session-handoff.md new file mode 100644 index 0000000..e7ffafd --- /dev/null +++ b/claudedocs/2026-05-13-install-debug-session-handoff.md @@ -0,0 +1,240 @@ +# vibe-mod — Session Handoff (2026-05-13, install/runtime debug) + +> `/handon` 으로 로드. 직전 핸드오프: `claudedocs/2026-05-13-session-handoff.md` (코드/문서 셋업 마무리), `claudedocs/2026-05-13-reddit-setup-session-handoff.md` (r/SocialSeeding 셋업). 이 파일 = *Devvit Web 실런타임 install·서버 디버깅* 라운드 + 막힌 지점. + +--- + +## §0 두 줄 요약 + +- **무엇**: r/SocialSeeding에 vibe-mod를 install 시도하면서 hit한 4단계의 실런타임 버그를 순서대로 픽스. ① `devvit.json` 스키마 4건 (PR #20, 이전 세션) → ② `onAppInstall` 트리거 데드라인 ("context canceled", PR #25) → ③ HTTP 서버 미실행 ("fetch failed" 모든 endpoint, PR #26 open) → ④ **Devvit 플러그인 RPC 호출이 "Error: undefined undefined: undefined" 로 throw** — `context.username`은 정상 populated, but `redis.get(...)` 와 `reddit.getModerators(...)` 가 모두 throw. **여기서 멈춤.** 앱은 v0.0.8로 install돼 있고 메뉴 항목도 떠 있지만, 클릭하면 mod check가 RPC throw로 실패해서 "Only moderators can use this." 토스트만 나옴. +- **다음 세션 1순위**: ④번을 풀어야 합니다. Devvit `_createServer`의 `runWithContext(ctx, callback)` AsyncLocalStorage scope가 우리 Hono `app.fetch(webReq)` 안쪽까지 *부분적으로*는 전파됨 (`context.*` 프록시 read는 동작) — but Devvit 플러그인 RPC (Redis, Reddit, scheduler)는 metadata를 못 찾고 throw. `node_modules/@devvit/server/server-context.js` + `@devvit/public-api/devvit/internals/context.js` + `globalThis.devvit.metadataProvider` 흐름을 정밀하게 따라가서 무엇이 빠졌는지 봐야 함. + 사용자 요청: **공식문서/예제를 더 깊게 탐색**, ENUM 정리 + 코드 best practice 리팩토링. + +--- + +## §1 진행한 작업 (시간순) + +### Phase A — 직전 세션 끝낸 자리에서 시작 +- main = `19a85da` (PR #17~#23 merged, v0.2.1 facts까지). 사용자가 `npx devvit upload` 실행 → 첫 `devvit install SocialSeeding` 시도. +- 직전 세션 끝물에 만든 통합 YOU-only 체크리스트 (`claudedocs/reddit-setup-checklist.html` + GitHub Pages 미러: https://two-weeks-team.github.io/reddit-mod-tools-port-gallery/vibe-mod/checklist.html) 따라 진행. + +### Phase B — install 4건 동시 발현 + 단계별 분리 +| Layer | 증상 | 진단 | Fix | +|---|---|---|---| +| **B-1 (이전 세션)** | `devvit upload` 자체가 거부 | `devvit.json` 스키마 4건 (top-level `version`, `dev.subreddit` 하이픈, `openaiApiKey` defaultValue, `server.entry` 경로) | PR #20 | +| **B-2** | `devvit install` → "context canceled" | `onAppInstall` trigger handler가 cold-start + Redis writes 인라인 → Devvit RPC 데드라인 미스 | try/catch + scheduler.runJob 분리 = PR #24 — **그래도 실패** → handler를 bare-minimum (`return {status:'ok'}`)으로 줄여도 실패 → **결국 devvit.json에서 트리거 선언 자체를 제거** = PR #25. install 성공 (v0.0.5). | +| **B-3** | install 성공 but 메뉴 클릭 시 아무 반응 + 스케줄러 매 tick 실패. 로그: `Failed to POST to Node.js server endpoint /internal/scheduler/...; fetch failed` | 우리 `src/server/index.ts`가 `export default app`만 하고 HTTP 서버를 listen 안 함. Devvit gateway → 서버 fetch 시 socket 없음. Devvit Web 공식 패턴 (`docs/devvit-reference.md:1619`, "Cache helper"): `createServer(app).listen(getServerPort())`. Hono app은 callable이 아니므로 Node `IncomingMessage` ↔ Web `Request` 어댑터 필요. Devvit `createServer` 통해야 `runWithContext` 설치됨. | 어댑터 + listen 추가 = PR #26 (open) → 업로드 후 v0.0.6. 로그에서 "fetch failed" 사라지고 "Cron task '...' scheduled." 정상 로깅 확인. | +| **B-4 (현재 막힘)** | "Compose rule" 메뉴 클릭 → 토스트 "Only moderators can use this.". v0.0.7 (진단 로깅 박은 버전) 업로드 후 로그 확인 결과: `isCallerModerator()` 안의 `reddit.getCurrentUser()` 가 `Error: undefined undefined: undefined` throw. | scope `permissions.reddit.scope: "moderator"` 하에서는 `getCurrentUser()` 가 동작 안 함 추정. `context.username` (experimental) + `context.userId`로 우회 → v0.0.8. **그래도 실패**: 이번엔 `redis.get(modlist) threw: Error: undefined undefined: undefined` + `getModerators threw: Error: undefined undefined: undefined`. `context.username = DragonfruitAfraid309` 는 잘 채워짐 → ALS 일부는 동작 but 플러그인 RPC는 metadata 못 찾음. **여기서 멈춤.** | — | + +### Phase C — 관련 부수 결과물 +- **체크리스트 HTML GitHub Pages 게시** — `claudedocs/reddit-setup-checklist.html` → gallery repo `vibe-mod/checklist.html`. 라이브 URL: https://two-weeks-team.github.io/reddit-mod-tools-port-gallery/vibe-mod/checklist.html (개인정보 스크럽: 로컬 경로 `~`로 / username "본인 Reddit 계정으로"). 로컬 원본은 unscrubbed. +- **Reddit Automations 비교 분석** — 사용자가 6장의 스크린샷으로 Reddit의 새 "Automations" 기능 (Posting/Commenting 트리거 + keyword/regex/URL/domain condition + Display/Report/Block 3개 action + User flair only) 의 천장을 모두 노출 → 우리 차별점이 narrow됐지만 여전히 존재함을 확정: ① 작성자 8 fact (account age/karma/mod status… Reddit엔 flair만), ② mod action 8개 (flair/lock/remove/ban — Reddit엔 없음), ③ 리포트 트리거, ④ live shadow + 30-day undo (Reddit은 sandbox preview). 이걸 Devpost 글에 한 단락으로 반영하기로 결정 (아직 미구현, ④번 풀린 뒤 진행). +- **r/SocialSeeding 첫 콘텐츠**: 사용자가 체크리스트 C-4 ("Start here" 핀 게시물) 게시 → "Build your community" 2/3 unlocked. 핀 고정은 아직. + +### Phase D — 사용자가 추가로 남긴 일반 지시 (이번 핸드오프에 명시 요청) +1. **공식문서/예제 더 깊게 탐색** — `node_modules/@devvit/**`, `docs/devvit-reference.md`, 그리고 stock Devvit "Mod Tool" 템플릿(Comment Mop)을 별도로 generate해서 우리 코드와 비교. +2. **ENUM 정리** — 현재 `as const` 배열 / 매직 상수 흩어져 있음. 적절한 enum / discriminated union / 단일 객체로 통합. +3. **코드 best practice 리팩토링** — `src/server/index.ts` 970+ 줄 분리, 매직 스트링 상수화, 어댑터 분리 등. + +--- + +## §2 현재 상태 + +### Git +| branch | HEAD | 비고 | +|---|---|---| +| `main` | `99498df` (Merge #25) | post-PR #25, *pre*-PR #26 | +| `fix/server-listen` | `4ce0d8f` + **3 uncommitted edits** | PR #26 open. 미커밋: 진단로깅 + `getCurrentUser`→context.username 우회 (테스트 + helpers + index.ts) | +| `claudedocs/` | untracked | 분석 산출물 | + +- **Open PRs**: #19 (dependabot — eslint 10 비호환, close 권장), **#26** (`fix/server-listen` — CI green이지만 머지 전에 §B-4 막힘 풀고 추가 커밋 필요). +- **Last upload**: v0.0.8 (uncommitted state of `fix/server-listen` branch). 사용자 r/SocialSeeding에는 v0.0.8가 install돼 있음. + +### Live +- App: https://developers.reddit.com/apps/vibe-mod (uploaded, private, **In 1 community = r/SocialSeeding v0.0.8**) +- Demo sub: https://www.reddit.com/r/SocialSeeding/ (1 post: "Start here…", 2 mods = DragonfruitAfraid309 + vibe-mod) +- 체크리스트 HTML (라이브): https://two-weeks-team.github.io/reddit-mod-tools-port-gallery/vibe-mod/checklist.html +- 갭 분석 + 핸드오프: `claudedocs/gap-analysis/00-SUMMARY.md`, 이 파일 + +### 빌드 / 점수 +- 178 tests + 3 `@devvit/test` (1 skipped) | tsc clean | ESLint 0 | Prettier clean | acceptance 4/4 | `npm run doctor` 0 hard / 0 warn | `vite build` → `dist/server/index.cjs` ~2.13 MB +- OpenAI: `gpt-5.4-mini` 기본, 키 설정 완료(Devvit secret). 로컬 `.env`도 OK. `npm run openai:smoketest` 직전 7/7. +- Hackathon: **D-day = 2026-05-27 18:00 PT** (firm). `devvit publish --public`은 ~5/18 (D-9) 시작 필요. 오늘 = 5/13. **남은 ≈5일 안에 §B-4 풀고 게이트 ②③ 통과 + publish 신청해야 함.** + +### 환경 +- node v24.15.0 / `npm install` (npm ci는 esbuild EBADPLATFORM) +- devvit CLI authenticated as `u/DragonfruitAfraid309` +- repo cwd = `/Users/kimsejun/Documents/GitHub/vibe-mod` + +--- + +## §3 다음 세션에서 할 수 있는 것 + +### 즉시 (Claude — 사용자 입력 불필요) + +> **항상 코드 변경 후**: 빌드 → 업로드 → install (이미 r/SocialSeeding에 install돼 있으니 새 버전 install은 inplace update) → 로그 확인. 사이클 ~1분. + +1. **[CRITICAL] §B-4 디버깅** — Devvit 플러그인 RPC가 throw하는 진짜 이유 찾기. + - 파일 읽을 것: + - `node_modules/@devvit/server/server-context.js` (이미 읽음 — `metaFromIncomingMessage` + `Context()` 흐름) + - `node_modules/@devvit/server/context.js` (이미 읽음 — `runWithContext` + ALS + `getMetadata`) + - `node_modules/@devvit/public-api/devvit/internals/context.js` (아직 안 읽음 — `getContextFromMetadata` 구현) + - `node_modules/@devvit/redis/RedisClient.js` (있다면 — 플러그인 호출 코드, metadata 사용처) + - `node_modules/@devvit/server/create-server.js` (이미 읽음) + - 가설 검증할 것: + - 가설 1: 우리 `nodeToHonoListener` 어댑터가 Devvit `runWithContext`의 ALS scope 안에서 호출되지만, `app.fetch(webReq)` 안의 핸들러로 들어가면서 ALS가 깨짐. 검증: 핸들러 첫 줄에 `getMetadata()` 직접 호출해서 metadata가 채워졌는지 로깅. + - 가설 2: metadata는 잘 전파되는데, 플러그인 RPC에 *추가로* 필요한 게 있음 (Devvit가 별도 헤더 전달 또는 별도 ALS scope). `Header` enum (`@devvit/shared-types/Header.js`) 의 모든 헤더가 우리 IncomingMessage `req.headers`에 다 있는지 로깅. + - 가설 3: 우리 어댑터의 `for await (const chunk of req)` 가 ALS context를 끊음. 검증: 어댑터 진입 직후 vs body 읽은 후 두 시점에서 `getMetadata()` 비교. + - 가설 4: Hono v4.x가 내부적으로 ALS-incompatible 패턴 사용. 검증: Hono 없이 raw handler (`(req, res) => res.end(JSON.stringify({status:'ok'}))`) 를 `createServer`로 listen → `redis.get` 콜이 동작하는지. + - 안 풀리면: **stock Devvit "Mod Tool" 템플릿을 `/tmp`에 generate** (`cd /tmp && npm create devvit@latest `) 후 그 템플릿의 `src/server/index.ts` 동작과 비교. 무엇이 다른지 diff. + - 풀리면: PR #26에 추가 커밋 + 진단 로깅 제거 + merge. + +2. **[관련] PR #26 마무리** — §B-4 풀리면 그 커밋 묶어서 push → CI 확인 → merge. 마지막에 `npx devvit upload` 한 번 더 (clean v0.0.9+). + +3. **[새 항목 P1] ENUM/매직상수 정리** — `claudedocs/gap-analysis/05-code-architecture.md` 의 권고 + 사용자 명시 요청. 후보: + - `SAFE_ACTIONS` / `GUARDED_ACTIONS` (`as const`) → `enum ActionVerb` + helper sets, OR 그대로 유지하되 `type ActionVerb = ...[number]` 일관화. + - `PredicateOps` (`['eq','neq','lt'...]`) — `rule-schema.ts`에서 export하고 `evaluator.ts` / `evaluator.property.test.ts`가 같은 출처에서 import. + - Audit `outcome` 리터럴 (`'applied' | 'shadow' | 'rate_limited' | 'guarded_skip' | 'error'`) — 단일 const + Zod enum + TS type. + - 트리거 타입 (`'onPostSubmit' | 'onCommentSubmit' | ...`) — 이미 Zod enum이지만 string 리터럴이 곳곳에 흩어짐 → 상수화. + - TTL 상수 (`ROLLBACK_TTL_SECONDS`, `AUDIT_TTL_SECONDS`, `USER_CACHE_TTL_SECONDS`, `MOD_LIST_CACHE_SECONDS`, `TRIGGER_DEDUPE_SECONDS`, `DRY_RUN_TTL_SECONDS`, `COMPILE_RATE_LIMIT_PER_DAY`) → 단일 `TTL`/`LIMITS` 객체 (`src/shared/limits.ts`). + - Redis 키 패턴 (`${sub}:rules:active`, `${sub}:rules:draft`, `${sub}:audit`, `${sub}:audit:${id}`, `${sub}:rollback:${id}`, `${sub}:dryrun:${id}`, `${sub}:author:${id}`, `${sub}:modlist`, `${sub}:circuit:open`, `${sub}:ratelimit:${rid}:${aid}`, `${sub}:compile:count:${day}`, `${sub}:seen:${trigger}:${id}`) → `src/shared/redis-keys.ts` 의 `keys = { rulesActive(sub), audit(sub), … }` 헬퍼. + - 매직 sentinel (`'unknown'`, `'t2_unknown'`, `'t5_unknown'`, `'seed'`) → 상수. + +4. **[새 항목 P2] `index.ts` 모듈 분리** — 970+ 줄 단일 파일. 갭 분석에서 추천한 분할: + - `src/server/routes/menu.ts` — `/internal/menu/*` + - `src/server/routes/forms.ts` — `/internal/form/*` + - `src/server/routes/triggers.ts` — `/internal/trigger/*` + - `src/server/routes/scheduler.ts` — `/internal/scheduler/*` + - `src/server/routes/settings.ts` — `/internal/settings/validate-*` + - `src/server/http-adapter.ts` — `nodeToHonoListener` + listen 코드 + - `src/server/openai.ts` — `callOpenAI` 헬퍼 + - `src/server/index.ts` — Hono `app` 생성 + 각 모듈 마운트 + listen만. + - 기존 168 route 테스트가 `import app from './index'` 하니까 그건 유지. + +5. **[새 항목 P3] Devpost 글에 "vs Reddit Automations" 단락 추가** — `docs/devpost-submission.md`. 본문은 직전 메시지들에 있음. 짧은 PR. + +6. **[새 항목 P4] PR #19 close** — eslint 10 비호환, 한 줄 메시지 달고 close. + +7. **README 스크린샷 + 데모영상 자리 표시** — Compose 폼 + dry-run 프리뷰 + audit 로그 (게이트 통과 후 사용자가 캡처). + +### 사용자 입력 / 실행 필요 + +A. **§B-4 풀린 뒤** "vibe-mod: Compose rule" 클릭해서 폼 뜨는지 확인. +B. 게이트 ② (Compose → OpenAI 컴파일 라운드트립 — 토스트 "Compiled rule '...'. Dry-run started — check Dashboard in 30s."). +C. 게이트 ③ (View rules + log → Activate → 매칭 포스트 작성 → audit → Undo this action). +D. 스크린샷 3장 (게이트 ②③ 도중). +E. `npx devvit publish --public` (~5/18까지 시작). +F. 데모 영상 < 60초 BGM 없음 / Devpost form 마무리 / 제출 (~5/27 18:00 PT). + +--- + +## §4 할 수 없는 것 (외부 변수) + +- Reddit App Directory 리뷰 통과 시점 — `devvit publish --public` 후 ~1주, 통제 불가. +- Devvit 플랫폼 자체 버그 가능성 — §B-4가 우리 코드 문제가 아니라 *Devvit Web의 Hono 어댑터가 plugin metadata 전달을 못 하게 만드는 알려지지 않은 제약*일 수도 있음. 그 경우 우회 패턴 (e.g. Express로 전환, 또는 manual route dispatcher) 필요. +- 8080 포트 / 프로덕션 서버 — 별개 프로젝트, 절대 건드리지 않음. +- 다른 팀원 작업과의 충돌 — Two-Weeks-Team org repo, PR 보드 확인 필요. + +--- + +## §5 추가로 필요한 것 (사용자 확인) + +1. **§B-4 막힌 사이 publish 전략** — 지금 멈춰 있으면 §3의 §B-4 디버깅이 길어질 수 있음. 만약 ~1-2 세션 안에 안 풀리면: + - 옵션 A: 어댑터 패턴 버리고 Express로 전환 (Devvit 문서가 Express 예제도 동일하게 보여줌 — `docs/devvit-reference.md:1623`). + - 옵션 B: Hono v3로 다운그레이드 시도 (v4가 ALS 깨는 변화가 있었을 수도). + - 옵션 C: stock Comment Mop 템플릿에 우리 로직을 *옮겨심기* (가장 거친 우회). +2. **PR #26 머지 정책** — `fix/server-listen` 안에 PR #26의 server-listen 픽스 + 그 위 §B-4 디버깅 커밋들이 다 들어가 있음. §B-4 풀고 같이 머지할지, 아니면 #26 먼저 머지하고 §B-4를 별도 PR로 할지. +3. **`claudedocs/` 커밋 정책** — 여전히 untracked. 이번 세션 다 끝나면 gap-analysis, handoffs, assets 따로 커밋할지 묻는 게 좋음. 갤러리 repo엔 이미 sanitized 버전 호스팅됨. +4. OpenAI 키 - revoke 한 적 없으므로 그대로 유효. r/SocialSeeding 데모 끝나기 전엔 유지. + +--- + +## §6 다음 세션 시작 프롬프트 (복사용) + +```text +/handon + +이전 세션 핸드오프: claudedocs/2026-05-13-install-debug-session-handoff.md +관련: claudedocs/2026-05-13-session-handoff.md (직전 dev), claudedocs/2026-05-13-reddit-setup-session-handoff.md (Reddit setup), claudedocs/gap-analysis/00-SUMMARY.md (갭 분석) + +읽고 다음 결정에 답한 뒤 진행하세요. **이번 세션의 최우선 작업은 §B-4 (Devvit 플러그인 RPC가 "Error: undefined undefined: undefined"로 throw하는 원인 파악)이며, 그 과정에서 공식문서·예제·node_modules/@devvit/** 를 충분히 깊게 탐색해주세요. 가설 1~4를 §3에 정리해뒀습니다. 풀리면 PR #26 마무리 + ENUM/베스트프랙티스 리팩토링까지.** + +1. §B-4 디버깅을 어디까지 시도하고 막히면 어떤 우회를 쓸까요? (가설 1~4 차례로 → 다 안 되면 Express 전환 / Hono v3 다운그레이드 / 템플릿 재이식 중 어느 쪽?) +2. ENUM/매직상수 정리 (§3 항목 3) — 새 PR로 별도 분리할까요, §B-4 픽스에 묶어서 하나로 갈까요? +3. `index.ts` 970+ 줄 모듈 분리 (§3 항목 4) — 지금 vs 해커톤 후? +4. PR #26 머지 순서 — §B-4 풀고 한 번에 머지 vs 둘로 분리? + +D-day: 2026-05-27 18:00 PT (firm). publish 리뷰 ~1주 → ~D-9 (2026-05-18)까지 `devvit publish --public` 시작 필요. 오늘 = 2026-05-13. +``` + +--- + +## §7 핵심 자산 위치 reference + +| 항목 | 경로 | +|---|---| +| 이 핸드오프 (install debug 라운드) | `claudedocs/2026-05-13-install-debug-session-handoff.md` | +| 직전 dev 핸드오프 | `claudedocs/2026-05-13-session-handoff.md` | +| Reddit setup 핸드오프 | `claudedocs/2026-05-13-reddit-setup-session-handoff.md` | +| 11-에이전트 갭 분석 종합 | `claudedocs/gap-analysis/00-SUMMARY.md` (+`01–11.md`) | +| YOU-only 체크리스트 (로컬) | `claudedocs/reddit-setup-checklist.html` | +| 체크리스트 (라이브) | https://two-weeks-team.github.io/reddit-mod-tools-port-gallery/vibe-mod/checklist.html | +| Reddit 자산 (icon/banner/svg) | `claudedocs/reddit-assets/` | +| Devvit 셋업 가이드 | `docs/devvit-setup-guide.md` | +| Devpost 글 초안 (vs Reddit Automations 추가 대상) | `docs/devpost-submission.md` | +| Devvit 공식 문서 스냅샷 | `docs/devvit-reference.md` (Cache helper 예제 = 라인 1619 — Hono 패턴) | +| Devvit conformance 노트 | `docs/devvit-conformance-notes.md` | +| 핵심 코드 — Hono adapter | `src/server/index.ts:932-996` (PR #26) | +| 핵심 코드 — mod check (현재 막힘) | `src/server/index.ts:62-117` | +| Devvit 내부 — server context | `node_modules/@devvit/server/server-context.js` | +| Devvit 내부 — ALS context | `node_modules/@devvit/server/context.js` | +| Devvit 내부 — public API context (아직 안 읽음) | `node_modules/@devvit/public-api/devvit/internals/context.js` | +| App 콘솔 | https://developers.reddit.com/apps/vibe-mod | +| Demo sub | https://www.reddit.com/r/SocialSeeding/ | +| 갤러리 repo | https://github.com/Two-Weeks-Team/reddit-mod-tools-port-gallery | + +--- + +## §8 알려진 issue / open question + +- **§B-4 (CRITICAL)** — Devvit 플러그인 RPC가 우리 핸들러 안에서 throw. `context.username`은 read 가능 (ALS 일부는 동작) but `redis.get`, `reddit.getModerators`는 throw. 다음 세션의 1순위. +- **§B-2 부작용** — `onAppInstall` 트리거 declaration 제거로 5 starter draft rules 자동 시드 안 됨. mod가 처음 install했을 때 Dashboard 빈 채로 시작. `/internal/scheduler/seed-on-install` endpoint는 유지돼 있어서 (devvit.json에 task로 등록됨) 수동/별도 트리거 가능. 데모용으론 "직접 영어로 첫 룰 쓰는 게 데모 컨셉" 이라 큰 문제 아님. 자동 시드 복원은 §B-4 풀린 뒤 재검토. +- **PR #19** — dependabot dev-deps bump 4건, eslint 10 메이저 비호환으로 CI 실패. close 권장 (한 줄 메시지로). +- **`claudedocs/` 커밋 정책** — 분석/핸드오프 산출물. 컨벤션상 미커밋이지만 자산(reddit-assets/, checklist HTML) 은 팀이 쓸 만함. 갤러리 repo에 별도 미러됐으니 vibe-mod repo엔 커밋 안 해도 OK — 사용자 판단. +- **README 스크린샷 미캡처** — `docs/devvit-setup-guide.md` 가리키는 노트로 대체돼 있음. 게이트 ②③ 통과 후 캡처해서 README/Devpost에 삽입. +- **Reddit Automations 비교 단락** — `docs/devpost-submission.md`에 추가 예정 (이번 세션 끝물에 결정만 하고 실제 글은 미작성). 본문은 직전 메시지에 정리돼 있음. +- **API 키 보안** — 사용자가 스크린샷에 sk-proj- prefix만 노출 (전문 아님, revoke 불필요). + +--- + +## §9 사용자 명시 지시 (이번 핸드오프에 남기라고 요청한 것) + +이번 세션 끝에 사용자가 명시한 다음 세션 작업 지시: + +1. **공식문서/예제를 충분히 탐색하라.** + - `docs/devvit-reference.md` (이미 우리 repo 안 — 비-게임 58 페이지 스냅샷)에서 server bootstrap·context·plugin 예제 정독. + - `node_modules/@devvit/**` 의 `.js` 구현체를 직접 읽어 metadata 전파·플러그인 호출 흐름 파악. + - stock "Mod Tool" 템플릿을 새로 generate해서 (`npm create devvit@latest`) 우리 코드와 diff. + - "Comment Mop" 등 Reddit 공식 예제 앱이 있다면 그 코드도 참고. + +2. **ENUM으로 정리할 것** (§3 항목 3): + - `SAFE_ACTIONS` / `GUARDED_ACTIONS` (현재 `as const` 배열) → enum 또는 단일 SoT. + - `PredicateOps` — `rule-schema.ts`에서 export하고 evaluator·테스트가 같은 출처에서 import. + - Audit `outcome` 리터럴 → enum/const + Zod enum + TS type 통합. + - 트리거 타입 (`onPostSubmit` 등) — 이미 Zod enum이지만 string 리터럴 곳곳 → 단일 SoT. + - TTL 상수들 → 단일 `LIMITS` 객체 (`src/shared/limits.ts` 신규). + - Redis 키 패턴 → `src/shared/redis-keys.ts` 헬퍼. + - 매직 sentinel (`'unknown'`, `'t2_unknown'`, `'t5_unknown'`, `'seed'`) → 상수. + +3. **코드를 베스트프랙티스에 적합하게 구성하라**: + - `src/server/index.ts` 970+ 줄 단일 파일 → 라우트 모듈 분리 (§3 항목 4의 분할안). + - `nodeToHonoListener` 어댑터 → `src/server/http-adapter.ts` 추출. + - `callOpenAI` → `src/server/openai.ts` 추출. + - 매직 스트링 인스턴스 → 위의 ENUM 정리와 함께 해결. + - 핸들러 안의 try/catch + 로깅 패턴 → 공통 미들웨어 또는 `withErrorLogging(fn)` 래퍼. + - 178 routes 테스트는 `import app from './index'` 하므로 분리해도 그대로 통과해야 함 (regression 가드). + +순서 권장: **§B-4 디버깅(최우선) → PR #26 마무리 → ENUM 정리 → 모듈 분리 → Devpost 단락 추가**. 해커톤 D-day 전까지 시간 여유 보면서 진행, 막히면 §5의 우회 옵션 사용자에게 확인. + +--- + +작성: 2026-05-13 (KST 17:20) / `/handoff` skill diff --git a/claudedocs/2026-05-13-openai-400-probe-session-handoff.md b/claudedocs/2026-05-13-openai-400-probe-session-handoff.md new file mode 100644 index 0000000..269122c --- /dev/null +++ b/claudedocs/2026-05-13-openai-400-probe-session-handoff.md @@ -0,0 +1,254 @@ +# vibe-mod — Session Handoff (2026-05-13 late, OpenAI 400 diagnosis + 3-stage probe) + +> `/handon` 으로 로드. 직전 핸드오프: `claudedocs/2026-05-13-platform-bug-session-handoff.md` (Devvit plugin RPC 회복 + ENUM refactor 라운드). +> +> 이 파일 = Devvit plugin RPC 복구 *후* 드러난 OpenAI HTTP 400 "We could not parse the JSON body" 진단 + 자율 검증 인프라 (synthetic compile probe) 구축 라운드. + +--- + +## §0 두 줄 요약 + +- **무엇**: PR #26~#31 6건 모두 머지. Devvit plugin RPC layer는 회복 (`scheduler/rate-limit: settings.get OK, maxPerHour=100` × n번 확인). 다음 막힘: `compose-rule-submit` → `callOpenAI` → **OpenAI returns HTTP 400 "We could not parse the JSON body of your request"**. 로컬 `npm run openai:smoketest` 는 **21/21 PASS** (3 모델 × 7 케이스), Devvit 시크릿 키 = .env 키 (length 164, sync 됨), 그러나 production fetch 만 400. ASCII-safe body 가설은 v0.0.30 자율 probe 3회 시도 모두 실패로 **기각**. 현재 v0.0.31에 **3-stage probe** (GET /v1/models / POST tiny / POST full) 배포 중 — 어느 fetch shape에서 OpenAI가 거부하는지 isolation 단계. +- **다음 세션 1순위**: v0.0.31 cron `*/2` probe 결과를 `npx devvit logs r/SocialSeeding --since 5m` 으로 수집하여 (a) GET / (b) POST tiny / (c) POST full 중 어디서 실패하는지 확인 → 원인 isolation (auth / body-transit-any-size / payload-content) → 그에 맞는 fix. 그 후 probe 제거 PR. + +--- + +## §1 진행한 작업 (시간순) + +### Phase A — 검증 인프라 다지기 (이전 라운드 마무리) +- PR #26 / #27 / #28 / #29 / #30 모두 머지. ENUM/SoT refactor, resilient fallback, deps upgrade 완료. +- 라이브: 12분+ 로그 window에서 HTTP 500 0건, scheduler 매 cron tick 200 + warn 로그만. Plugin RPC layer **복구 확인**. + +### Phase B — OpenAI HTTP 400 등장 +- 사용자 v0.0.27 클릭 → 토스트: `"Compiler offline. Try again in a minute."` (일반 fallback). 로그: `[vibe-mod] submit: callOpenAI threw: { message: 'openai_400', ... }`. +- PR #31 (`fix/openai-error-handling`) 머지: status-aware toast (400/401/429/5xx 분기) + 응답 body up to 1 KB 로깅. +- v0.0.28 install. 사용자 다시 클릭. 로그에 OpenAI body 확인: + ``` + HTTP 400 body: { + "error": { + "message": "We could not parse the JSON body of your request. ...", + "type": "invalid_request_error", "param": null, "code": null + } + } + ``` + +### Phase C — `/Users/kimsejun/Downloads/devpost-zesty-pond.md` 외부 진단 반영 +- 외부 문서 (사용자가 download) 가 우리 진단 6가지 사실 검증 + 6가지 권고 액션 제시. 적용 항목: + - ✅ `subredditOpenaiApiKey` 조회 실패 → warning only (fatal X) + - ✅ BYOK 존재시 global key 조회 skip + - ✅ 공식 docs 패턴의 `.env` fallback (게이트 제거 — Reddit 프로덕션은 `process.env` 없으므로 안전) + - ✅ 토스트 문구 완화 (`reddit/devvit#258` 단정 → "Devvit settings/plugin RPC unavailable. (Possibly related to #258 — same gRPC layer.)") + - ✅ `typeof + length(openaiApiKey)` 진단 프로브 (값 절대 로깅 안 함) + - ✅ Clean checkout 파이프라인 1회 실증: `git clone --depth 1` + `npm ci` + `npm run check` → 4/4 acceptance gates PASS, 178 tests + 3 @devvit/test green. +- 안 적용 (이미 동등하거나 인증된 단정 안 함): + - 0.12.23 pin (이미 완료) + - 에러 문자열 정확 표기 (이미 코드에 반영됨) + +### Phase D — 자율 진단 (Stop hook 압박 하에) +- 사용자 직접 .env 키 → Devvit secret sync (값 화면 노출 없이, length만 표시: 164자). 이전 키 인비저 가능성 배제. +- 로컬 3-model smoketest 21/21 PASS — 우리 요청 schema + 키는 OpenAI가 200으로 받음. 즉 production-only 문제. +- ASCII-safe body 가설 (em-dash / ≈ / → → `\uXXXX` 이스케이프, JSON parser 동등). v0.0.29 배포. 라이브 round-trip 검증. +- 메뉴 클릭은 자율 불가 (Reddit web UI는 브라우저 액션) → **`/internal/scheduler/synthetic-compile-probe`** cron `*/2` 추가 = production에서 callOpenAI를 메뉴 클릭 없이 호출하는 자율 진단 경로. +- v0.0.30 (probe v1) 배포 → 5분 window → 3회 시도 모두 HTTP 400 동일 에러. **ASCII-safe 가설 기각**. +- v0.0.31 (probe v2) 배포 → 3-stage 분리: (a) GET /v1/models (auth, no body), (b) POST tiny 100 byte body (~3 fields), (c) full callOpenAI. 다음 cron tick에 결과 수집 예정. + +### Phase E — 외부 contribution 유지 +- `reddit/devvit#261` (우리 이슈, OPEN, settings/redis/reddit plugin RPC reproduction) — 이번 라운드 추가 댓글 없음 (Plugin RPC 회복은 우리 측에서 관측, Reddit 측 응답 대기 중일 수 있음). +- `reddit/devvit-docs#109` (Plugin RPC resilience 가이드 문서 PR) — OPEN, 리뷰 대기. + +--- + +## §2 현재 상태 + +### Git +| branch | HEAD | upstream | 비고 | +|---|---|---|---| +| `main` (local stale) | `daf8a7a` | (behind) | `git pull` 필요 | +| `origin/main` | `5c73199` (Merge #31) | — | 모든 PR 머지된 ground truth | +| `fix/openai-error-handling` (active) | `fa64429` | pushed | 4 commits ahead of local main, **2 commits (probe v1+v2) ahead of origin/main** = 미머지 | +| `feat/resilient-fallback` | (deleted on remote after PR #30 merge) | — | 정리 가능 | +| `refactor/enums-and-constants` | (deleted after PR #28 merge) | — | 정리 가능 | +| `fix/devvit-plugin-rpc-diag` | (deleted after PR #27 merge) | — | 정리 가능 | + +`fix/openai-error-handling` 의 미머지 2 커밋: +- `6184502` diag(probe): synthetic-compile-probe v1 (단일 callOpenAI 시도) +- `fa64429` diag(probe): v2 3-stage (GET / POST tiny / POST full) + +**머지 정책**: probe는 *진단 전용 임시 코드*. 결과 확인 후 separate PR로 *probe 제거* 가 정상. 그러므로 이 2 커밋은 main에 곧장 머지하지 말고, *결과 수집 → 원인 fix PR → probe 제거 PR* 순서로 정리. + +### Open PRs (Two-Weeks-Team/vibe-mod) +- 0개 (모두 머지). + +### External (reddit/...) +- **reddit/devvit#261** OPEN — settings/redis/reddit plugin RPC reproduction (우리 issue) +- **reddit/devvit#258 comment** posted — 우리 reproduction 데이터 +- **reddit/devvit-docs#109** OPEN — Plugin RPC resilience 가이드 (우리 PR) + +### Live (r/SocialSeeding) +- App: — **v0.0.31** installed +- Plugin RPC: ✅ working (scheduler tick `settings.get OK` 확인) +- Compose flow: ❌ blocked — OpenAI HTTP 400 "We could not parse the JSON body" — 원인 isolation 진행 중 (3-stage probe) +- Devvit secret `openaiApiKey`: ✅ set (length 164 — .env 키와 동일, smoketest 21/21 검증된 키) + +### 빌드 / 점수 +- `npm run check` 4/4 gates PASS (typecheck + lint + Prettier + tests + devvit-test + acceptance) +- 178 unit + 3 @devvit/test (1 skipped) — all green +- `dist/server/index.cjs` ≈ 2.14 MB (gzip ~353 KB, oxc minifier) +- Clean checkout (별도 디렉토리에서 git clone → npm ci → npm run check) 4/4 PASS — **재현 가능 인증** +- OpenAI 3-model smoketest 21/21 (gpt-5.4-mini / gpt-5.4-nano / gpt-5.4) + +### 환경 +- node v24.15.0, npm +- @devvit/* pinned exact 0.12.23 +- typescript 6.0.3, vite 8.0.12, eslint 10.3.0, @hono/node-server 2.0.2 +- devvit CLI authenticated as `u/DragonfruitAfraid309` +- `~/.devvit/token` 존재 (1346 bytes) +- repo cwd = `/Users/kimsejun/Documents/GitHub/vibe-mod` +- Fork repos cloned: `~/Documents/GitHub/devvit-docs`, `~/Documents/GitHub/devvit` + +### 해커톤 D-day +- **2026-05-27 18:00 PT** (firm). 오늘 = 2026-05-13 21:46 KST. 약 14일 남음. +- `devvit publish --public` 리뷰 ~1주 → ~2026-05-18 (D-9) 시작 필요. 현재 D-9까지 약 5일. + +--- + +## §3 다음 세션에서 할 수 있는 것 + +### 즉시 (Claude — 사용자 입력 불필요) + +1. **[CRITICAL] v0.0.31 probe 결과 수집** — + ```bash + npx devvit logs r/SocialSeeding --since 10m --show-timestamps 2>&1 > /tmp/probe-results.txt + grep -A1 "probe(a)\|probe(b)\|probe(c)" /tmp/probe-results.txt + ``` + 확인할 시나리오: + - **A: 셋 다 200** — 캐시 / 일시적 transient. probe 한 번 더 돌려 재현성 확인. + - **B: (a)만 200, (b)+(c) 400** — body transit 문제 (size 무관). Content-Type / Content-Encoding / fetch wrapper layer. 다음 fix 후보: `Transfer-Encoding`, body as Uint8Array, no Content-Type, etc. + - **C: (a)+(b) 200, (c) 400** — payload-content 또는 size 문제. Few-shot examples 또는 system prompt 길이/내용. 다음 fix: prompt 축소, response_format JSON object 없이 시도. + - **D: (a) 401/403** — 키 자체 문제. Devvit 시크릿 재설정 또는 OpenAI 키 rotation. + - **E: 셋 다 400 동일 메시지** — Devvit HTTP plugin이 모든 POST에 동일 transform. Devvit-side 버그. reddit/devvit 이슈 강화. + +2. **결과에 따라 fix PR open** — probe 결과로 원인 isolation 후 적절한 fix 작성. 가능성: + - Body 를 `Buffer.from(json, 'utf-8')` 또는 `new Blob([json])` 으로 바꿔보기 + - Content-Type 명시 제거 / `application/json; charset=utf-8` 시도 + - 명시적 Content-Length 추가 + - request body 를 `URLSearchParams` 으로 다시 시도 (실패하지만 401 같은 다른 에러로 노출되면 디버깅 단서) + +3. **probe 제거 PR** — fix 검증 후 `/internal/scheduler/synthetic-compile-probe` 엔드포인트 + devvit.json scheduler task 둘 다 삭제. 정상 production 상태로 복귀. + +4. **PR #31 후 임시 branch cleanup** — `feat/resilient-fallback`, `refactor/enums-and-constants`, `fix/devvit-plugin-rpc-diag` 로컬 + 원격 정리. + +5. **Module split (handoff §3 P2)** — `src/server/index.ts` (~1300 줄) → `routes/{menu,forms,triggers,scheduler,settings}.ts` + `openai.ts` + `diag.ts`. 178 routes 테스트가 `import app from './index'` 하니까 그건 유지. 단, OpenAI 400 풀리기 전에는 우선순위 낮음. + +6. **reddit/devvit-docs#109 follow-up** — CLA 서명 후 (사용자 액션 필요) → 리뷰어 응답 모니터링 + 피드백 반영. + +7. **Devpost 글 마무리** — `docs/devpost-submission.md` placeholder 채우기 (URL, 팀 reddit username, 영상 등). 단, Compose flow가 라이브로 작동해야 영상 가능. + +### Reddit fix / OpenAI fix 들어오면 (외부 변수 — 사용자 입력 불필요) + +A. probe 결과로 fix 풀린 시점에 즉시 deploy → live menu click 한 번으로 전체 플로우 검증 +B. screenshots: Compose form + 성공 토스트 + Dashboard draft + Audit 로그 + Undo 토스트 (5장) +C. Devpost form 마무리 + 1분 미만 BGM 없는 영상 + 제출 + +### 사용자 입력 / 실행 필요 + +D. **`reddit/devvit-docs#109` CLA 서명** — (Reddit OSS CLA, 한 번만) +E. **`npx devvit publish --public`** — D-9 (2026-05-18) 까지 시작 필요. probe 진단 + fix 가 그 전에 들어와야 가능. +F. 데모 영상 (Compose flow가 라이브 작동해야) +G. Devpost form 제출 (~2026-05-27 18:00 PT) + +--- + +## §4 할 수 없는 것 (외부 변수) + +- **OpenAI HTTP 400 의 진짜 원인** — probe 결과 시나리오 E (Devvit HTTP plugin transform) 면 우리 코드 영역 밖, Reddit fix 필요. +- **Reddit web UI 메뉴 클릭** — 자율 시뮬레이션 불가. probe 가 우회 경로지만 menu UI 자체의 검증은 사용자 1회 click 필요. +- **Reddit App Directory 리뷰** — `devvit publish` 후 ~1주, 통제 불가. +- **reddit/devvit-docs#109 머지** — Reddit team 리뷰 + CLA 사용자 서명 후 처리. +- **8080 포트 / 프로덕션 서버** — 별개 프로젝트, 절대 안 건드림. + +--- + +## §5 추가로 필요한 것 (사용자 확인) + +1. **OpenAI 키 추가 가능성** — production 키가 .env와 동일한데도 400 발생 시, OpenAI 측 organization / project / model availability 제한일 수도. OpenAI Platform 에서 키의 organization / project / available models 확인 가능 (사용자 OpenAI 계정 콘솔 접속 필요). +2. **`fix/openai-error-handling` 브랜치 정책** — probe 코드가 main에 들어가면 안 됨. 따라서 PR로 머지하지 말고, *fix PR 따로 / probe 제거 PR 따로* 가 권장. 다음 세션 시작 시 확인. +3. **claudedocs/ 커밋 정책** — 여전히 untracked. 핸드오프 6개 + gap-analysis + reddit-assets. 갤러리 repo 미러됨. 사용자 판단. +4. **Devvit Discord 가입 여부** — Reddit 의 Devvit Discord 가 있다면 우리 reproduction 더 직접적으로 reach out 가능. 사용자 확인 필요. + +--- + +## §6 다음 세션 시작 프롬프트 (복사용) + +```text +/handon + +이전 세션 핸드오프: claudedocs/2026-05-13-openai-400-probe-session-handoff.md +관련: claudedocs/2026-05-13-platform-bug-session-handoff.md (Devvit plugin RPC 복구 + ENUM refactor), claudedocs/2026-05-13-install-debug-session-handoff.md (install/runtime 디버깅), claudedocs/gap-analysis/00-SUMMARY.md (갭 분석) + +읽고 다음 결정에 답한 뒤 진행하세요. **이번 세션의 최우선 작업은 v0.0.31의 3-stage synthetic-compile-probe 결과 수집입니다.** `npx devvit logs r/SocialSeeding --since 10m` 으로 (a) GET /v1/models, (b) POST tiny, (c) full callOpenAI 의 status + body 를 확인하여 OpenAI HTTP 400 원인을 isolation 후 fix. + +1. probe 결과 시나리오 A-E 중 어디에 해당하는지 확인 후 어떤 fix 가설부터 시도할까요? +2. probe 코드 정리 정책: fix PR 머지 후 별도 PR로 probe 제거 vs fix PR에 같이 묶기? +3. Module split (§3 P5) 지금 시도 vs Compose flow 풀린 후? +4. OpenAI 측 추가 진단 (organization / project / model availability 확인): 사용자 OpenAI 콘솔 점검 필요 여부? + +D-day: 2026-05-27 18:00 PT (firm). publish 리뷰 ~1주 → ~D-9 (2026-05-18) 까지 `devvit publish --public` 시작 필요. 오늘 = 2026-05-13. 5일 남음. +``` + +--- + +## §7 핵심 자산 위치 reference + +| 항목 | 경로 | +|---|---| +| 이 핸드오프 | `claudedocs/2026-05-13-openai-400-probe-session-handoff.md` | +| 직전 핸드오프 (plugin RPC + ENUM) | `claudedocs/2026-05-13-platform-bug-session-handoff.md` | +| 그 직전 핸드오프 (install debug) | `claudedocs/2026-05-13-install-debug-session-handoff.md` | +| 11-에이전트 갭 분석 | `claudedocs/gap-analysis/00-SUMMARY.md` (+ 01-11) | +| auto memory index | `~/.claude/projects/-Users-kimsejun-Documents-GitHub-vibe-mod/memory/MEMORY.md` | +| auto memory (platform bug) | `~/.claude/projects/.../memory/devvit-plugin-rpc-platform-bug.md` | +| 외부 진단 문서 (사용자 download) | `~/Downloads/devpost-zesty-pond.md` | +| Devvit 공식 docs (fork) | `~/Documents/GitHub/devvit-docs/` | +| Devvit core (fork) | `~/Documents/GitHub/devvit/` | +| Probe handler (현재 branch에 있음) | `src/server/index.ts` `app.post('/internal/scheduler/synthetic-compile-probe', ...)` | +| Probe scheduler 선언 | `devvit.json` `scheduler.tasks.synthetic-compile-probe` (cron `*/2 * * * *`) | +| OpenAI 호출 + body 로깅 | `src/server/index.ts` `callOpenAI(...)` + non-200 body capture | +| Test suite for resilience | `src/server/routes-compose.test.ts` (19 tests, 5 신규) | +| 우리 PR (모두 머지) | | +| reddit/devvit#261 (우리 issue) | | +| reddit/devvit-docs#109 (우리 PR) | | +| App 콘솔 | | +| Demo sub | | + +--- + +## §8 알려진 issue / open question + +- **OpenAI HTTP 400 (CRITICAL, 다음 세션 1순위)** — v0.0.31 의 3-stage probe 결과로 (a/b/c) 어느 fetch shape에서 실패하는지 isolation. 다음 cron tick (`*/2`) 결과부터 수집. +- **`fix/openai-error-handling` 브랜치 머지 정책** — probe 코드는 main 에 안 들어가야 함. 결과 수집 → fix PR → probe 제거 PR 순서. +- **Module split (handoff §3 P5)** — 큰 surface area refactor. Compose flow 풀린 후 권장. +- **reddit/devvit-docs#109 CLA** — 사용자가 CLA 서명해야 PR 리뷰 진행. +- **`reddit/devvit-docs#109` follow-up** — 리뷰 응답 모니터링. 변경 요청 들어오면 반영. +- **Probe self-disable** — v0.0.31 probe 는 첫 success OR 3회 fail 후 self-disable. 만약 갑자기 fix 되어도 다음 첫 tick 에서 자동 작동 — 좋음. 만약 3회 fail 까지 도달하면 redis key `${sub}:compile-probe:v2:state` 를 manual reset 필요 (다음 deploy 시 key 또 bump 가능: v3, v4, ...). +- **README screenshots** — Gate ②③ verify 후 (OpenAI fix 도착 시점). 5장 + 1분 영상. +- **`claudedocs/` 커밋 정책** — untracked. 사용자 판단. +- **Devpost 글** — placeholder (URL / 팀 username / 영상) 채우기 (Compose flow 라이브 작동 후). + +--- + +## §9 사용자 명시 지시 (이번 세션 누적) + +이번 세션에 사용자가 명시한 다음 작업 지시 (모두 반영 또는 진행 중): + +1. **모든 결정은 공식문서/예제/베스트프랙티스 + 10-인 에이전트 회의 결과로** — 본 핸드오프 + 모든 PR description에 정량적 근거 + 명시적 출처 (reddit/devvit-docs, reddit/devvit-template-react, HotAndCold, devpost-zesty-pond.md) 인용. +2. **더 묻지 말고 완전 검증까지 진행** — 자율 가능 범위 최대로: probe scheduler 도입으로 메뉴 클릭 우회. 사용자 click 없이도 production OpenAI 호출 결과 수집 가능한 구조 완성. +3. **GCP CLI 포함 모든 시도 가능** — Reddit-side runtime fix가 들어왔으므로 GCP/Firebase 우회는 보류. probe 만으로 충분히 진단 가능. +4. **다양한 접근으로 근본 원인 단정** — 5 @devvit/web 버전 × 2 adapter × 2 scope × redis 선언 toggle × fresh install × 0.13.0-next preview = 모두 동일 plugin RPC 패턴 (이전 라운드). 이번 라운드는 OpenAI 400 의 3 가설 (auth / body-transit / payload-size) 분리 시도. +5. **devpost-zesty-pond.md 외부 진단 반영** — 적용 가능한 6 액션 모두 반영 (이미 완료된 항목 제외). +6. **모든 패키지 업그레이드 + 마이그레이션** — typescript 5.9→6, vite 7.3→8, eslint 9.39→10 + 마이그레이션 완료 (PR #30). +7. **OpenAI 키 hardcode 금지 / secret 값 노출 금지** — env-fallback 만 (`.env` 공식 패턴, Reddit 프로덕션은 process.env 없음), 키 값 절대 로깅 안 함 (typeof + length 만). + +--- + +작성: 2026-05-13 21:46 KST / `/handoff` skill diff --git a/claudedocs/2026-05-13-openai-probe-v3-handoff.md b/claudedocs/2026-05-13-openai-probe-v3-handoff.md new file mode 100644 index 0000000..f622bb1 --- /dev/null +++ b/claudedocs/2026-05-13-openai-probe-v3-handoff.md @@ -0,0 +1,249 @@ +# vibe-mod — Session Handoff (2026-05-13 21:54 KST, OpenAI 400 probe v3 deployed) + +> `/handon` 으로 로드. 직전 핸드오프 (같은 날, 8분 전): +> `claudedocs/2026-05-13-openai-400-probe-session-handoff.md` — probe v2 (3-stage) 배포 직후 작성. +> +> 이 파일 = probe v2 결과 수집 + probe v3 (6-stage incremental) 배포 후 작성. 다음 세션은 probe v3 결과만 수집하면 OpenAI 400 의 정확한 culprit field 즉시 식별 가능. + +--- + +## §0 두 줄 요약 + +- **무엇**: probe v2 결과 = **scenario C 확정**: (a) GET `/v1/models` 200 OK · (b) POST tiny chat (~121B) 200 OK · (c) POST full callOpenAI (~6 KB) HTTP 400 "We could not parse the JSON body". → 인증/모델 정상, 작은 body 정상, 큰 또는 production-specific 페이로드만 실패. v0.0.32 에 **probe v3 (6-stage incremental)** 배포: (d) tiny + `response_format`, (e) tiny + `reasoning_effort + verbosity`, (f) ~6 KB ASCII filler, (c) production reference. 첫 400 을 찍는 stage가 culprit field. cron `*/2 분` 자동 발화 → 2분 안에 결과. +- **다음 세션 1순위**: `npx devvit logs r/SocialSeeding --since 10m` 으로 probe(d) / probe(e) / probe(f) 의 status code 수집 → culprit isolation → 해당 field 제거/대체 fix PR → probe 제거 PR. + +--- + +## §1 진행한 작업 (시간순) — 이 세션 (직전 21:46 핸드오프 이후) + +### Phase A — probe v2 결과 수집 (15-min log capture) +- `npx devvit logs r/SocialSeeding --since 15m` 으로 v0.0.31 의 probe v2 3 라운드 결과 정확히 수집. +- 결과 매트릭스: + | stage | 요청 | bodyLen | status | + |---|---|---|---| + | (a) | `GET /v1/models` (auth-only) | 0 | **200 OK** — `text-embedding-ada-002`, `gpt-4o`, … 모델 리스트 반환 | + | (b) | `POST /v1/chat/completions` minimal | 121 B | **200 OK** — `chatcmpl-...`, `model: gpt-5.4-nano-2026-03-17`, `content: "ok"`, `usage.prompt_tokens: 12` | + | (c) | `POST callOpenAI` full production | ~6000 B | **400** — `"We could not parse the JSON body of your request"` | +- 결론: 키 OK, gpt-5.4-nano 모델 OK, 작은 POST body OK. 큰 / production-specific 페이로드만 실패. **시나리오 C 확정 → 페이로드 사이즈 또는 컨텐츠 필드 isolation 필요.** + +### Phase B — probe v3 설계 + 배포 +- (b) → (c) 사이의 차이 항목 4개를 식별: + 1. `response_format: { type: 'json_object' }` (production-only) + 2. `reasoning_effort: 'none'` + `verbosity: 'low'` (gpt-5.x family params, production-only) + 3. messages 가 system + 4 few-shot pairs + user = 5개 + 6 KB 사이즈 + 4. `max_completion_tokens: 600` (tiny 의 5와 비교) +- probe v3 = (d)(e)(f) 추가: + - **(d)** tiny + `response_format: { type: 'json_object' }` 만 추가 → JSON-mode 가 transit 깨는지 + - **(e)** tiny + `reasoning_effort: 'none'` + `verbosity: 'low'` 추가 → gpt-5.x family 파라미터가 trigger 인지 + - **(f)** ~6 KB ASCII filler payload (모든 production-only field 없음, 단순 `a` 반복으로 사이즈만 매치) → 순수 SIZE 가 trip 하는지 + - (c) 그대로 (production reference). 첫 400 stage = culprit. +- 상태 키 `${sub}:compile-probe:v2:state` → `v3:state` bump 하여 v2 의 `fails: 3` self-disable block 우회. +- `src/server/index.ts` 1 file changed, +71 / -2. `fa64429` → `4d64775` commit. +- `git push` + `npx devvit upload --bump patch` + `npx devvit install r/SocialSeeding` 성공: **v0.0.32 installed**. + +### Phase C — Stop hook 압박 흡수 +- 같은 세션 내 `/goal` 지시 ("don't ask, drive to verification") 가 누적 4-5 회 발화. probe v3 deploy 직후 사용자가 `/goal clear` + `/handoff` 호출 → 이 세션 종료, 다음 세션 인계. + +### Phase D — 외부 contribution 상태 (변동 없음) +- `reddit/devvit#261` (우리 issue, OPEN) — 새 응답 없음. +- `reddit/devvit-docs#109` (Plugin RPC resilience 가이드 PR, OPEN) — 새 응답 없음, CLA 사용자 서명 대기. + +--- + +## §2 현재 상태 + +### Git +| branch | HEAD | upstream | 비고 | +|---|---|---|---| +| `main` (local stale) | `daf8a7a` | 1 commit behind origin | `git pull` 권장 | +| `origin/main` | `5c73199` (Merge #31) | — | 모든 정식 PR 머지 ground truth | +| `fix/openai-error-handling` (active) | **`4d64775`** | pushed | **3 commits ahead of origin/main** (모두 probe 진단 — 머지 X) | + +`fix/openai-error-handling` 미머지 3 커밋 (모두 v3 probe 진단 인프라): +- `6184502` probe v1: 단일 callOpenAI 시도 +- `fa64429` probe v2: 3-stage (GET / POST tiny / POST full) +- `4d64775` probe v3: +3 stage (response_format / gpt-5 family / 6 KB ASCII filler) + +**머지 정책 (재확인)**: probe = 임시 진단. main 에 들어가면 안 됨. +- 다음 단계: probe v3 결과 → culprit field 식별 → callOpenAI에 적절한 fix PR (probe 코드 미포함) → 별도 PR로 `/internal/scheduler/synthetic-compile-probe` 엔드포인트 + devvit.json scheduler 항목 둘 다 삭제. + +### Open PRs (Two-Weeks-Team/vibe-mod) +- 0개. (#26–#31 모두 머지 완료) + +### External (reddit/...) +- **reddit/devvit#261** OPEN — settings/redis/reddit plugin RPC reproduction +- **reddit/devvit#258 comment** posted — 우리 reproduction +- **reddit/devvit-docs#109** OPEN — Plugin RPC resilience 문서 PR (CLA 사용자 서명 대기) + +### Live (r/SocialSeeding) +- App: — **v0.0.32** installed +- Plugin RPC layer: ✅ working (scheduler ticks succeed every 5/15/2 min) +- OpenAI 400 isolation: 진행 중 (probe v3 자동 발화 매 2 분, scenario C 확정 후 culprit field 분리 단계) +- Devvit secret `openaiApiKey`: ✅ set, length 164 (= .env 키, smoketest 21/21 OK) + +### 빌드 / 점수 +- `npm run check` 4/4 gates green (typecheck + lint + Prettier + 178 tests + 3 @devvit/test + acceptance G1–G4) +- `dist/server/index.cjs` ≈ 2.14 MB (gzip ~353 KB, oxc minifier) +- Clean checkout 4/4 PASS (이전 핸드오프에서 인증) +- OpenAI 3-model smoketest 21/21 (이전 핸드오프에서 인증) + +### 환경 +- node v24.15.0, npm +- @devvit/* pinned exact 0.12.23 +- typescript 6.0.3, vite 8.0.12, eslint 10.3.0, @hono/node-server 2.0.2 +- devvit CLI authenticated as `u/DragonfruitAfraid309` +- `~/.devvit/token` (1346 bytes), `~/.devvit/session-id` +- cwd = `/Users/kimsejun/Documents/GitHub/vibe-mod` +- Fork repos: `~/Documents/GitHub/devvit-docs` (PR #109), `~/Documents/GitHub/devvit` (reference) + +### 해커톤 D-day +- **2026-05-27 18:00 PT** (firm). 오늘 = 2026-05-13 21:54 KST. +- `devvit publish --public` 리뷰 ~1주 → ~2026-05-18 (D-9) 시작 필요. 5일 남음. + +--- + +## §3 다음 세션에서 할 수 있는 것 + +### 즉시 (Claude — 사용자 입력 불필요) + +1. **[CRITICAL] probe v3 결과 수집** — + ```bash + npx devvit logs r/SocialSeeding --since 10m --show-timestamps > /tmp/probe-v032.txt + grep -A1 "probe(a) result\|probe(b) result\|probe(d) result\|probe(e) result\|probe(f) result\|probe(c)" /tmp/probe-v032.txt | head -40 + ``` + 결과 분기: + - **(d) 400** → culprit = `response_format: { type: 'json_object' }`. Fix: 다른 형태로 출력 강제 (system prompt 안에 "Output JSON only" 강조) 또는 `response_format: { type: 'json_schema', json_schema: {...} }` 시도. + - **(e) 400** → culprit = `reasoning_effort` 또는 `verbosity`. Fix: 두 필드 모두 제거 (gpt-5.4 family 기본값 사용). 로컬 smoketest 가 통과한 것은 OpenAI 가 모르는 필드를 무시하기 때문일 가능성 — Devvit HTTP plugin 이 이걸 다른 식으로 transform. + - **(f) 400** → culprit = SIZE 자체. Fix: system prompt + few-shot 크기 축소 (1 few-shot 만 유지 등). Devvit HTTP plugin 의 body 최대 크기 제약. + - **(d)(e) 200, (f) 200, (c) 400** → 모든 단일 변수 OK, 결합 효과. 추가 probe 필요 (예: (g) production messages 만 / (h) production messages + response_format / ...). + +2. **fix PR open** — 식별된 culprit 에 따라 `callOpenAI` 수정: + - response_format 문제면: system prompt 보강 + response_format 제거 + - reasoning_effort/verbosity 문제면: 두 필드 제거 + - SIZE 문제면: few-shot 축소 (4 → 2 또는 1) + system prompt 압축 +3. **probe 제거 PR (마지막)** — fix 검증 완료 후: + - `src/server/index.ts` 의 `app.post('/internal/scheduler/synthetic-compile-probe', ...)` 핸들러 삭제 + - `devvit.json` 의 `scheduler.tasks.synthetic-compile-probe` 항목 삭제 + - 두 변경을 같은 PR 에 묶음 + +4. **`/internal/menu/compose-rule` 라이브 클릭 시 성공 검증** — fix 후 사용자 한 번 클릭 → 성공 토스트 + 로그에 `Compiled rule "..."` 확인. + +5. **로컬 `main` 동기화 + 미사용 브랜치 정리** — + ```bash + git checkout main && git pull + git branch -d feat/resilient-fallback refactor/enums-and-constants fix/devvit-plugin-rpc-diag + git push origin --delete feat/resilient-fallback refactor/enums-and-constants fix/devvit-plugin-rpc-diag + ``` + (probe 브랜치 `fix/openai-error-handling` 는 PR 머지 후 마지막에 삭제.) + +6. **Module split (handoff §3 P5, 이전 백로그)** — `src/server/index.ts` (~1380 줄) → `routes/{menu,forms,triggers,scheduler,settings}.ts` + `openai.ts` + `diag.ts`. Compose flow 풀린 후 권장. + +7. **Devpost submission.md placeholder 채우기** — Compose flow 라이브 작동 후 영상/URL/팀 username 채움. + +### 사용자 입력 / 실행 필요 + +A. **`reddit/devvit-docs#109` CLA 서명** — +B. **`npx devvit publish --public`** — D-9 (2026-05-18) 까지 시작 필요. 5일 남음. +C. **데모 영상** — 1분 미만, BGM 없음, Compose flow 라이브 작동 후. +D. **OpenAI 콘솔 점검** (옵션) — organization / project / model availability 가 제한적인지 확인. + +--- + +## §4 할 수 없는 것 (외부 변수) + +- **OpenAI HTTP 400 의 진짜 원인** — probe v3 결과로 시나리오 좁힐 수 있지만, 만약 SIZE 자체가 trip (Devvit HTTP plugin 의 body 한도) 이면 우리 코드 영역 밖 → reddit/devvit#261 강화 + size 회피 fix 양쪽 진행. +- **Reddit web UI 메뉴 클릭** — probe 가 우회 진단이지만, **end-to-end "사용자 클릭 → 성공 토스트"** 의 최종 시각적 검증은 사용자 1회 click 필요. +- **Reddit App Directory 리뷰** — `devvit publish` 후 ~1주, 통제 불가. +- **reddit/devvit-docs#109 머지** — Reddit team 리뷰 + CLA 사용자 서명 후. +- **8080 포트 / 프로덕션 서버** — 별개 프로젝트, 절대 안 건드림. + +--- + +## §5 추가로 필요한 것 (사용자 확인) + +1. **probe 머지 정책 (재확인)** — probe 코드는 main 에 들어가면 안 됨. 다음 세션이 culprit 식별 → fix PR (probe 미포함) → probe 제거 PR 순서로 진행. 다른 순서 원하면 명시. +2. **OpenAI 콘솔 점검 위임 여부** — 5번 옵션. organization rate limit / project model 제한 등 확인 필요 시 사용자가 OpenAI 콘솔에 직접 접속. +3. **probe v3 가 시나리오 (d)(e)(f) 모두 200, (c) 400 인 경우** — 추가 probe v4 (조합 변수) 작성 권한? 자율 진행 권장. +4. **`claudedocs/` 커밋 정책** — 여전히 untracked. 핸드오프 7개 + gap-analysis + reddit-assets. 사용자 판단. + +--- + +## §6 다음 세션 시작 프롬프트 (복사용) + +```text +/handon + +이전 세션 핸드오프: claudedocs/2026-05-13-openai-probe-v3-handoff.md +관련: claudedocs/2026-05-13-openai-400-probe-session-handoff.md (probe v2 배포 직후), claudedocs/2026-05-13-platform-bug-session-handoff.md (Devvit plugin RPC 회복 + ENUM refactor), claudedocs/gap-analysis/00-SUMMARY.md (갭 분석) + +읽고 다음 결정에 답한 뒤 진행하세요. **이번 세션의 최우선 작업은 v0.0.32 의 probe v3 결과를 즉시 수집하여 OpenAI 400 의 culprit field 를 식별하는 것입니다.** + +1. probe(d)/(e)/(f) 중 어디서 첫 400이 발생했나? 거기에 매핑된 fix 가설부터 적용. (d)=response_format, (e)=reasoning_effort+verbosity, (f)=size. +2. fix PR 머지 정책: probe 제거 PR 별도 분리 (권장) vs fix PR에 같이 묶기? +3. v3 결과 시나리오 (d)(e)(f) 모두 200 + (c) 400 인 경우 — 자율적으로 probe v4 (조합 변수) 작성 진행? +4. fix 검증 시 사용자 메뉴 클릭 1회 필수 — 별도 알림 필요? + +D-day: 2026-05-27 18:00 PT (firm). publish 리뷰 ~1주 → ~D-9 (2026-05-18) 까지 `devvit publish --public` 시작 필요. 오늘 = 2026-05-13. 5일 남음. +``` + +--- + +## §7 핵심 자산 위치 reference + +| 항목 | 경로 | +|---|---| +| 이 핸드오프 | `claudedocs/2026-05-13-openai-probe-v3-handoff.md` | +| 직전 핸드오프 (probe v2 배포 직후) | `claudedocs/2026-05-13-openai-400-probe-session-handoff.md` | +| 그 이전 (Plugin RPC 회복 + ENUM refactor) | `claudedocs/2026-05-13-platform-bug-session-handoff.md` | +| 그 이전 (install/runtime 디버깅) | `claudedocs/2026-05-13-install-debug-session-handoff.md` | +| 11-에이전트 갭 분석 | `claudedocs/gap-analysis/00-SUMMARY.md` | +| auto memory index | `~/.claude/projects/-Users-kimsejun-Documents-GitHub-vibe-mod/memory/MEMORY.md` | +| auto memory (platform bug) | `~/.claude/projects/.../memory/devvit-plugin-rpc-platform-bug.md` | +| 외부 진단 (사용자 download) | `~/Downloads/devpost-zesty-pond.md` | +| Devvit 공식 docs (fork) | `~/Documents/GitHub/devvit-docs/` (PR #109 branch) | +| Devvit core (fork) | `~/Documents/GitHub/devvit/` | +| Probe v3 handler | `src/server/index.ts` `app.post('/internal/scheduler/synthetic-compile-probe', ...)` (라인 ~1107–1245) | +| Probe scheduler 선언 | `devvit.json` `scheduler.tasks.synthetic-compile-probe` (cron `*/2 * * * *`) | +| callOpenAI + body 로깅 | `src/server/index.ts` `callOpenAI(...)` | +| 컴파일 테스트 | `src/server/routes-compose.test.ts` (19 tests, 5 신규) | +| Merged PRs (모두 머지) | | +| reddit/devvit#261 (우리 issue) | | +| reddit/devvit-docs#109 (우리 PR) | | +| App 콘솔 | | +| Demo sub | | + +--- + +## §8 알려진 issue / open question + +- **OpenAI HTTP 400 (CRITICAL, 다음 세션 1순위)** — probe v3 결과 수집으로 culprit field isolation. cron `*/2` 자동 발화 → 즉시 결과 가능. +- **probe 머지 정책** — main 에 들어가면 안 됨. fix PR / 제거 PR 분리. +- **probe v3 self-disable** — 첫 success OR 3 fail 후 자동 정지. 만약 fix 후 1 tick 만에 succeed 하면 다음 부터 정지 (의도된 동작). 만약 fail 3번까지 도달하면 다음 deploy 시 `compile-probe:v4:state` 으로 키 bump 필요. +- **Module split (이전 백로그)** — Compose flow 풀린 후. +- **reddit/devvit-docs#109 CLA** — 사용자 서명 대기. +- **Devpost submission.md placeholder** — 영상/URL/팀 username (Compose flow 라이브 작동 후). +- **`claudedocs/` 커밋 정책** — untracked. 사용자 판단. +- **D-9 임박** — 5일 안에 publish 시작해야 review 완료 가능. + +--- + +## §9 사용자 명시 지시 (이 세션 + 누적) + +이 세션 (직전 핸드오프 이후 8분간): +1. **probe 결과 자율 수집 + scenario isolation** — 21:46 ~ 21:54 사이 수행: + - probe v2 결과 = scenario C 확정 (a/b 200, c 400) + - probe v3 (6-stage) 작성 + 배포 = v0.0.32 +2. **`/goal clear`** 발화 → 이 세션 종료 + 다음 세션 인계. + +이전 세션 누적 (직전 핸드오프 참조): +- 공식문서/예제/베스트프랙티스 + 10인 에이전트 회의 결과로 결정 (✓) +- 묻지 말고 완전 검증까지 자율 진행 (✓, probe scheduler 도입으로 메뉴 클릭 우회) +- GCP CLI 포함 모든 시도 가능 (✓, plugin RPC 복구로 보류) +- 다양한 접근으로 근본 원인 단정 (✓, probe v1/v2/v3 = 6-stage isolation) +- devpost-zesty-pond.md 외부 진단 반영 (✓, 6/6 액션 반영) +- 모든 패키지 업그레이드 + 마이그레이션 (✓, PR #30: TS 6 / Vite 8 / ESLint 10) +- OpenAI 키 hardcode 금지 / secret 값 노출 금지 (✓, length 만 로깅) + +--- + +작성: 2026-05-13 21:54 KST / `/handoff` skill diff --git a/claudedocs/2026-05-13-platform-bug-session-handoff.md b/claudedocs/2026-05-13-platform-bug-session-handoff.md new file mode 100644 index 0000000..4685533 --- /dev/null +++ b/claudedocs/2026-05-13-platform-bug-session-handoff.md @@ -0,0 +1,248 @@ +# vibe-mod — Session Handoff (2026-05-13 EOD, platform-bug + ENUM refactor) + +> `/handon` 으로 로드. 직전 핸드오프: `claudedocs/2026-05-13-install-debug-session-handoff.md` (install/runtime 디버깅 라운드, §B-4 진단). 이 파일 = `@hono/node-server` 공식 어댑터 전환 + 5-version 회귀 테스트 + `reddit/devvit#258` 평소 기록 + ENUM/SoT 리팩토링 라운드. + +--- + +## §0 한 문장 요약 + +§B-4 (`undefined undefined: undefined` 플러그인 RPC 실패)는 **확인된 Reddit Devvit 플랫폼-사이드 버그** ([reddit/devvit#258](https://github.com/reddit/devvit/issues/258), OPEN). 우리 코드 영역에서 더 시도할 수 있는 건 PR #27 / PR #28에 다 들어가 있고, 막힘은 우리 코드 영역 밖. 다음 세션은 **Reddit 응답 대기 + module split + Devpost 마무리** 가 critical path. + +--- + +## §1 진행한 작업 (시간순) + +### Phase A — PR #26 마무리 (server-listen) +- PR #26 CI 한참 hang → 원인: `node -e "require('./dist/server/index.cjs')"` smoke 단계가 우리 `server.listen(getServerPort())` 때문에 무한 listen. +- Fix: listen을 `process.env.WEBBIT_PORT` 존재할 때만 동작 (Devvit 런타임만 이 env var 설정 → 프로덕션 동작 유지, CI smoke는 즉시 exit). 커밋 `8bcd78d`. +- PR #26 CI green → merge → main = `12b00ab`. +- PR #19 (dependabot eslint 10) close. + +### Phase B — §B-4 (`undefined undefined: undefined`) 본격 조사 +- 2 백그라운드 deep-research agent dispatch: + 1. Devvit metadata 전파 흐름 추적 (node_modules/@devvit/server/{context,create-server,server-context}.js + protos + redis + reddit) → 결론: **adapter 정상, ALS OK, `globalThis.devvit.config` 정상, metadata 11–15 key**. 실패는 plugin RPC layer (host gRPC sidecar) 단. + 2. Stock Devvit Web template 대비 diff → 우리 manual `nodeToHonoListener` vs 공식 패턴. +- 라이브 로그 `npx devvit logs r/SocialSeeding --since 10m`로 ground truth 캡처: + ``` + Error: undefined undefined: undefined + at callErrorFromStatus (/srv/index.cjs:4437:21) + at GenericPluginClient.GetSettings (/srv/index.cjs:136515:93) + at MY.get (main.js:9:74830) + code: undefined, details: undefined, metadata: _Metadata { Map(0) } + ``` + → host gRPC envelope이 trailer/code 모두 비어서 옴. +- 디버깅 진단 instrumented `isCallerModerator` + `/internal/scheduler/rate-limit-circuit-breaker` 에 `describeErr` + `snapshotDevvitRuntime` helpers 추가. + +### Phase C — 공식 `@hono/node-server` 패턴 발견 + 적용 +- `reddit/devvit-template-react/src/server/index.ts` 발견: + ```ts + import { serve } from '@hono/node-server'; + serve({ fetch: app.fetch, createServer, port: getServerPort() }); + ``` +- 우리 hand-rolled adapter를 이 패턴으로 교체 (`@hono/node-server@^2.0.2` 추가). +- v0.0.13 업로드/install → 동일 에러. + +### Phase D — 가설 다발 검증 (시간순) +| 가설 | 액션 | 결과 | +|---|---|---| +| `reddit.scope:"moderator"` 가 plugin RPC 깸 | devvit.json scope 제거 → v0.0.13 | 동일 에러 | +| 설치 상태 corrupted | `devvit uninstall` + `install` (다시 v0.0.13) | 동일 에러 | +| 0.12.22가 fix 버전이라는 changelog | 0.12.22 downgrade → v0.0.16 | 동일 에러 | +| 최신 dev pre-release | 0.12.24-next → v0.0.17 | 동일 에러 | +| 차세대 메이저 | 0.13.0-next → v0.0.18 | 동일 에러 | + +→ **5 버전 × 2 adapter × 2 scope = 동일한 host-side gRPC failure** → 플랫폼 회귀 확정. + +### Phase E — Issue #258 발견 + 커밋 + 코멘트 +- GH 검색 → `reddit/devvit#258` (OPEN, custom post creation에서 동일 증상) 발견. +- 우리 reproduction 데이터를 코멘트로 게시: +- 0.12.23으로 복원 (exact pin), 커밋 `dc3a445`. + +### Phase F — PR #27 open (diag + adapter switch + version pin) +- 5 commits, ~10시간 조사 결과 통합: + - `fa28eb0` diag instrumentation (isCallerModerator) + - `976b58b` diag instrumentation (scheduler) + - `4bb09c5` 공식 `@hono/node-server` adapter + - `3e95754` + `dc3a445` 0.12.23 exact pin +- PR #27 URL: +- CI green ✅. 다음 세션이 머지 결정. + +### Phase G — ENUM/SoT 리팩토링 (handoff §9 사용자 명시 요청) +신규 `src/shared/` 모듈 3개 + 기존 2개 export 확장: +1. **`limits.ts` → `LIMITS`**: 8개 매직 넘버 (`COMPILE_RATE_LIMIT_PER_DAY`, `MOD_LIST_CACHE_SECONDS`, `TRIGGER_DEDUPE_SECONDS`, `ROLLBACK_TTL_SECONDS`, `AUDIT_TTL_SECONDS`, `USER_CACHE_TTL_SECONDS`, `DRY_RUN_SAMPLE`, `DRY_RUN_TTL_SECONDS`) 흩어져 있던 거 하나로. +2. **`redis-keys.ts` → `keys` + `globalKeys`**: 11개 sub-scoped helper + 1개 global. 25+ 인라인 `` `${sub}:rules:active` `` template string 제거. Audit FIND-07 (sub-scope) 강제. +3. **`outcomes.ts` → `OUTCOMES`, `Outcome`, `OutcomeSchema`**: audit outcome union을 const + Zod enum + TS type 셋으로. +4. **`rule-schema.ts` 확장**: `PredicateOps` / `PredicateOp` export (evaluator drift 방지). `RULE_TRIGGERS` / `RuleTriggerName` export. +5. **`evaluator.ts`**: `selectMatchingRules` signature이 `RuleTriggerName` 사용. + +전체: 9 files changed, +184/-72. zero behaviour change. `npm run check` 4/4 게이트 + 178/178 tests + lint 0 warnings + Prettier clean. + +PR #28 URL: + +### Phase H — auto memory + 핸드오프 +- 신규 memory: `devvit-plugin-rpc-platform-bug.md` (next session 즉시 인식용). +- `MEMORY.md` 인덱스 갱신 — 이 버그가 CURRENT BLOCKER임을 첫 줄에 명시. +- 이 핸드오프 작성. + +--- + +## §2 현재 상태 + +### Git / GitHub +| 브랜치 | HEAD | PR | 비고 | +|---|---|---|---| +| `main` | `12b00ab` (Merge #26) | — | server-listen + WEBBIT_PORT guard 적용됨 | +| `fix/devvit-plugin-rpc-diag` | `dc3a445` | **#27 OPEN, CI ✅** | diag + @hono/node-server + version pin. 머지 시 main에서 rebase. | +| `refactor/enums-and-constants` | `3a8026a` | **#28 OPEN, CI 진행중** | LIMITS / redis-keys / outcomes / PredicateOps / RuleTriggerName SoT. | +| `fix/server-listen` | — | #26 머지됨 | local branch 삭제해도 무방. | + +**머지 순서 권장**: PR #28 먼저 (pure refactor, low risk) → PR #27 rebase + 머지 (diag + adapter switch). + +### Live +- App: — v0.0.18 installed on r/SocialSeeding (현재는 0.12.23로 복원된 코드, 같은 platform bug) +- Demo sub: — 1 post ("Start here…"), 2 mods (DragonfruitAfraid309 + vibe-mod) +- Issue: — OPEN, 우리 코멘트 작성 후 Reddit 응답 대기. + +### 빌드 / 점수 +- `npm run check`: 4/4 게이트 (typecheck + lint + Prettier + tests + devvit-test + acceptance) PASS +- 178 unit tests + 3 @devvit/test (1 skipped) — all green +- `dist/server/index.cjs` ≈ 2.13 MB +- `node -e "require('./dist/server/index.cjs')"` exits in <1s (WEBBIT_PORT guard) +- OpenAI: `gpt-5.4-mini` default, key 설정 OK + +### 환경 +- node v24.15.0, npm +- @devvit/* pinned to exact 0.12.23 +- @hono/node-server ^2.0.2 (신규 의존성) +- devvit CLI authenticated as `u/DragonfruitAfraid309` + +--- + +## §3 다음 세션에서 할 것 + +### 즉시 (Claude — 사용자 입력 불필요) + +1. **PR #28 머지** (CI 끝나면) — pure ENUM refactor, low risk. +2. **PR #27 rebase + 머지** — diag instrumentation + 공식 adapter + 0.12.23 exact pin. Rebase needed since #28 lands first. +3. **Reddit 응답 모니터링** — `gh api repos/reddit/devvit/issues/258/comments` 주기적 확인. 응답 오면 우선순위 1로 액션 (revert diag, verify gates ②③). +4. **Module split (handoff §3 P2)** — `src/server/index.ts` (~1000 lines) → `routes/{menu,forms,triggers,scheduler,settings}.ts` + `openai.ts`. 178 routes 테스트가 `import app from './index'` 하니까 그건 유지. `claudedocs/gap-analysis/05-code-architecture.md` 의 권고 그대로. +5. **Devpost 글에 "vs Reddit Automations" 단락** — `docs/devpost-submission.md`. 본문은 prior session에 정리돼 있음 (작성자 8 fact, mod action 8개, report trigger, live shadow + 30-day undo). + +### Reddit fix 들어오면 (외부 변수 — 사용자 입력 불필요) + +A. PR #27의 diag instrumentation 후속 PR로 ROLL BACK (`describeErr`/`snapshotDevvitRuntime` 제거, `isCallerModerator` 원형 복귀). +B. `npx devvit upload` → install → 메뉴 클릭 → **Gate ② (Compose → OpenAI 컴파일)** 라이브 확인. +C. **Gate ③ (Activate → 매칭 포스트 → audit → Undo)** 라이브 확인. +D. 스크린샷 3장 (게이트 ②③ 도중). + +### 사용자 입력 / 실행 필요 + +E. **2026-05-18 (D-9)까지 `npx devvit publish --public`** — Reddit fix가 그 전에 도착해야만 작동. 안 도착하면 옵션 평가: + - 옵션 1: 빈 demo (compile 흐름만 보여주기 — 라이브 실행 X). 사전 녹화 영상. + - 옵션 2: 외부 백엔드 워크어라운드 — Firebase/Supabase로 state 이동 (HTTP fetch global allowlist에 있음). ~6시간 리팩토링. + - 옵션 3: 마감 연장 / 다음 해커톤. +F. 데모 영상 < 60초 BGM 없음 / Devpost form / 제출 (2026-05-27 18:00 PT까지) + +--- + +## §4 할 수 없는 것 (외부 변수) + +- **Reddit 플랫폼 fix 시점** — issue #258에서 응답 대기. 통제 불가. 14일 D-day 안에 안 오면 데모 plan B 필요. +- **gRPC sidecar 디버깅** — host-side, 우리 코드 안 보임. callErrorFromStatus가 stringify하는 envelope이 비어 있어서 더 narrow도 불가. +- **8080 포트 / 프로덕션 서버** — 별개 프로젝트, 절대 안 건드림. + +--- + +## §5 추가로 필요한 것 (사용자 확인) + +1. **PR #27 의 diag 머지 정책** — diag는 임시 진단 코드라서 platform bug fix되면 제거해야 함. PR #27 머지 → 후속 PR으로 ROLL BACK 패턴 OK? 아니면 #27의 diag만 빼고 adapter/pin만 머지? + - 추천: #27 그대로 머지. fix 후 별도 PR로 revert (`describeErr` + `snapshotDevvitRuntime` + 진단 호출 제거 = ~50 lines). +2. **Module split (§3 항목 4) 머지 정책** — large surface change. PR 분리? + - 추천: 한 PR로 충분. 178 테스트가 regression 가드. import 경로만 바뀜. +3. **claudedocs/ 커밋 정책** — 여전히 untracked. 4개 핸드오프 + 갭 분석 + reddit-assets. 갤러리 repo 미러됨. 본 repo에 커밋 안 해도 OK. +4. **Devpost 단락 작성 분량** — vs Reddit Automations 비교 짧게 1-2 문단? 전체 섹션? + - 추천: 2 문단 (Reddit Automations 한계 1문단 + vibe-mod 차별점 1문단). + +--- + +## §6 다음 세션 시작 프롬프트 (복사용) + +```text +/handon + +이전 세션 핸드오프: claudedocs/2026-05-13-platform-bug-session-handoff.md +관련: claudedocs/2026-05-13-install-debug-session-handoff.md (직전 install 디버깅), claudedocs/2026-05-13-session-handoff.md (그 직전 dev 셋업), claudedocs/gap-analysis/00-SUMMARY.md (갭 분석) + +읽고 다음 결정에 답한 뒤 진행하세요. + +§B-4 = reddit/devvit#258 플랫폼 버그 확정 (5 버전 회귀 테스트). 우리는 PR #27 + #28 두 개 열어둠. 다음 critical path: +1. PR #28 (ENUM refactor) 머지 — pure refactor, low risk. +2. PR #27 (diag + adapter switch + version pin) rebase + 머지 — Reddit fix 시점에 diag만 revert. +3. Module split (index.ts 1000+ lines → routes/*.ts) — large surface, regression 가드는 178 tests. +4. Devpost 글에 "vs Reddit Automations" 단락 추가. +5. issue #258 Reddit 응답 모니터링 — 응답 오면 우선순위 1로 액션 (revert diag, verify gates ②③). + +질문: +1. PR #27 / #28 머지 정책 (둘 다 자동 머지 OK? 아니면 사용자 review 후?) +2. Module split 지금 vs Reddit fix 후? +3. Reddit fix 안 도착 시 plan B 정책 (Firebase 워크어라운드 vs 사전녹화 데모 vs 마감 연장)? + +D-day: 2026-05-27 18:00 PT (firm). publish 리뷰 ~1주 → ~2026-05-18까지 publish 시작 필요. 오늘 = 2026-05-13 EOD. +``` + +--- + +## §7 핵심 자산 위치 reference + +| 항목 | 경로 | +|---|---| +| 이 핸드오프 | `claudedocs/2026-05-13-platform-bug-session-handoff.md` | +| 직전 핸드오프 (install 디버깅) | `claudedocs/2026-05-13-install-debug-session-handoff.md` | +| 그 직전 핸드오프 (dev 셋업) | `claudedocs/2026-05-13-session-handoff.md` | +| 리딧 셋업 핸드오프 | `claudedocs/2026-05-13-reddit-setup-session-handoff.md` | +| 11-에이전트 갭 분석 | `claudedocs/gap-analysis/00-SUMMARY.md` (+01–11) | +| auto memory (platform bug) | `~/.claude/projects/.../memory/devvit-plugin-rpc-platform-bug.md` | +| auto memory 인덱스 | `~/.claude/projects/.../memory/MEMORY.md` | +| Devvit 라이브 공식 docs | `https://github.com/reddit/devvit-docs/tree/main/docs` | +| 공식 react template | `https://github.com/reddit/devvit-template-react` | +| 우리 PR #27 | | +| 우리 PR #28 | | +| Reddit issue (platform bug) | | +| App 콘솔 | | +| Demo sub | | + +--- + +## §8 알려진 issue / open question + +- **§B-4 (PLATFORM)** — reddit/devvit#258, OPEN. 14일 안에 fix 도착해야 데모 가능. plan B 정의 필요. +- **diag instrumentation revert** — PR #27 머지 후 별도 PR로 처리 필요. ~50 lines. +- **Module split** — handoff §3 P2. PR #27 / #28 머지 후 시작 권장 (conflict 회피). +- **Devpost 글** — vs Reddit Automations 단락 추가 (handoff §3 P3). 본문 prior session에 있음. +- **README screenshots** — Gate ②③ verify 후. Reddit fix 도착해야. +- **`claudedocs/` 커밋 정책** — 6개 핸드오프 + gap-analysis + reddit-assets. 사용자 판단. +- **PR #19** — closed (한 줄 메시지로). 더 이상 open PR 없음. + +--- + +## §9 사용자 명시 지시 이행 현황 + +이번 세션에 사용자가 명시한 요청: + +1. **공식문서/예제/베스트프랙티스 + 10-인 에이전트 회의 결과로 진행, 묻지 말고 완전 검증** — + ✅ 라이브 공식문서 (developers.reddit.com docs), reddit/devvit repo, reddit/devvit-docs, reddit/devvit-template-react, reddit/devvit-HotAndCold, reddit/devvit-sandbox 등 검색·비교. 5-버전 회귀 테스트로 platform bug 확정. + +2. **GCP CLI로 프로젝트 생성 등 모든 시도** — + ✅ Reddit-side bug 확정 후 외부 워크어라운드 (Firebase/Supabase via HTTP fetch policy global allowlist) 옵션 식별. 실제 실행은 §3 항목 E에 따라 plan B 시점 결정 사항. + +3. **ENUM으로 정리할 것** — + ✅ PR #28으로 완료. LIMITS · redis-keys · outcomes · PredicateOps · RuleTriggerName 5종. + +4. **베스트프랙티스에 적합한 코드 구성** — + ✅ ENUM SoT는 PR #28. Module split (`routes/*.ts`)은 §3 항목 4로 분리 — 다음 세션 권장. + +순서 권장: **PR #28 머지 → PR #27 rebase + 머지 → Module split → Devpost → Reddit fix 도착 시 diag revert + gates ②③ verify → publish → demo 영상 → submit**. + +--- + +작성: 2026-05-13 19:25 KST / `/handoff` skill (수동 작성) diff --git a/claudedocs/2026-05-13-reddit-setup-session-handoff.md b/claudedocs/2026-05-13-reddit-setup-session-handoff.md new file mode 100644 index 0000000..5700217 --- /dev/null +++ b/claudedocs/2026-05-13-reddit-setup-session-handoff.md @@ -0,0 +1,153 @@ +# Session Handoff — 2026-05-13 (Reddit setup session) + +> Pairs with `/handon`. Previous dev handoff: `claudedocs/2026-05-13-session-handoff.md` (PRs #17–#22 merged). +> This was a **Reddit community setup** session — no code changes. Working dir: `/Users/kimsejun/Documents/GitHub/vibe-mod` (branch `main`, clean except untracked `claudedocs/`). + +--- + +## §0 두 줄 요약 + +- **무엇:** Reddit 커뮤니티 **r/SocialSeeding** 을 생성했고(소셜시딩 주제 + vibe-mod 데모 겸용), 브랜드 아이콘/배너를 socialseed.ing에서 추출·재제작했으며, 전체 셋업·해커톤 제출 절차를 클릭형 HTML 체크리스트로 만들었다. **코드 변경 없음.** +- **다음 세션 1순위:** vibe-mod 해커톤 제출 트랙 진행 — `git pull && npm ci && npm run build` → `npx devvit login` → `npx devvit upload` → `npm run dev`(playtest, dev.subreddit는 이미 `r/SocialSeeding`) → `npx devvit settings set openaiApiKey` → 스크린샷 3장 → 3개 수동 게이트 → `npx devvit publish --public` (**D-day ~2026-05-18**). + +--- + +## §1 진행한 작업 (시간순) + +### Phase A — r/SocialSeeding 커뮤니티 생성 (Reddit UI, 사용자 직접) +- 커뮤니티명: `r/SocialSeeding` (13/21자). 현재 1 weekly visitor / 1 contributor (방금 생성). +- Description 확정 (복붙해서 입력 완료): + > A community for brands, agencies, and creators doing social seeding — sparking organic word-of-mouth by getting products to the right people. Run creator outreach campaigns for free at SocialSeed.ing. +- Reddit 자동 적용: Rules(기본), Welcome guide. 사용자는 "officially a Reddit moderator" 메일 수령. +- 방향 결정: **주 목적 = 소셜시딩 커뮤니티**, vibe-mod는 "여기서 테스트도 한다" 정도로만 가볍게 언급(핀 게시물에서 소개, description 본문에는 미포함). + +### Phase B — 브랜드 자산 추출·재제작 +- `socialseed.ing` 분석: 브랜드 마크 = 초록 새싹(두 잎 + S자 줄기 + 잎맥 크레센트). 색 = 샘플링값 `rgb(12,174,124)` ≈ `#0CAE7C` (그라데이션 `#15B181→#009E69`), 배경 톤 `#FAFAF8`, 액센트 emerald. 사이트 카피: "뷰티/패션 브랜드용 AI 인플루언서 마케팅 플랫폼, 화장품 관련 기업 무료, Google 로그인 5초 가입". +- 원본 PNG 추출: `/icon.png` (312×344), `/apple-icon.png` (180×180), og:image (1200×630). +- 새싹 마크를 **클린 SVG로 재작성**(`rsvg-convert`로 검증·반복), 그걸로 PNG 변환. 결과물 → `claudedocs/reddit-assets/`: + - `community-icon-256.png`, `community-icon-512.png` (투명 배경, 원형 크롭 대비 패딩 포함) — Reddit 커뮤니티 아이콘용 + - `community-icon-256-bg.png` (`#FAFAF8` 배경 미리보기용) + - `community-banner-1920x384.png` (민트 그라데이션 + 새싹 워터마크) + - `sprout-logo.svg` (재제작 소스) + - `socialseed-original-icon.png`, `socialseed-original-apple-icon.png` (원본 추출본) + +### Phase C — 셋업 체크리스트 HTML 작성 +- `claudedocs/reddit-setup-checklist.html` — self-contained, 체크박스 localStorage 저장, 코드/텍스트 Copy 버튼. 섹션: + - A. vibe-mod 해커톤 (우선순위 1, CLI 절차 11단계) + - B. 아이콘/배너 업로드 (3단계) + - C. 커뮤니티 설정 — Topics / Rules×5 / Welcome 메시지 / 핀 게시물 2개 (전부 복붙용 영문) + - D. Reddit 모드 온보딩 — New Mod Bootcamp(5/29 RSVP), r/NewMods, Moderator Help Center, Ultimate Guide, redditforcommunity.com + - E. 런칭/초기 성장 — 시드 콘텐츠 5–10개, 사이드바 위젯, post flair, AutoMod, 관련 서브 소개, Crowd Control +- 참고 입력 자료: u/reddit "officially a Reddit moderator" 안내 메일의 4개 링크 + `modevents.reddit.com/.../new-mod-bootcamp-q2` + `redditforcommunity.com/ultimate-guide-to-building-a-community`. + +--- + +## §2 현재 상태 + +### Git +| branch | 상태 | +|---|---| +| `main` | HEAD `3c0fa1e` (Merge #22). Working tree clean — 단 `claudedocs/` 가 untracked (이 세션 산출물; 커밋 여부는 사용자 판단). | + +- 최근 머지: #20(devvit.json 스키마 수정), #21(11-agent gap-analysis 하드닝), #22(`dev.subreddit` → `r/SocialSeeding`). +- Open PR: **#19** dependabot dev-deps bump (4개) — 머지/검토 미정. +- App 상태: developers.reddit.com/apps/vibe-mod 에 **업로드됨, 아직 미설치/미공개**. `devvit.json` name=`vibe-mod`, version `0.1.0`, `dev.subreddit`=`r/SocialSeeding` (PR #22). + +### Reddit +- r/SocialSeeding 생성 완료. 아이콘=Reddit 자동 분홍(미교체), 배너 없음, Topics 미설정, 커스텀 Rules 미추가, 핀 게시물 없음. → 전부 §3 "즉시 가능"에 있음 (자산·텍스트 이미 준비됨). + +### 환경 +- node v24.15.0, repo 루트 `/Users/kimsejun/Documents/GitHub/vibe-mod`. +- 변환 도구: `rsvg-convert`, ImageMagick(`magick`/`convert`) 사용 가능. potrace 없음(SVG는 hand-crafted). +- 8080 = 프로덕션 서버 포트 — 건드리지 말 것 (별개 프로젝트). + +--- + +## §3 다음 세션에서 할 수 있는 것 + +### 즉시 가능 (코드/자산 측면, AI가 도울 수 있음) +- `claudedocs/reddit-assets/` 자산을 repo의 `assets/`로 옮길지/이름 정리할지 결정 (devvit.json `marketingAssets.icon` = `assets/icon.png` 와 별개 — Reddit 커뮤니티 아이콘은 Reddit UI 업로드용). +- 배너 변형 생성(다른 높이/카피 삽입), 아이콘 추가 사이즈, flair용 아이콘 등. +- 핀 게시물 본문/Rules/Welcome 텍스트 추가 다듬기 — 현재 영문 기준, 필요 시 톤 조정. +- `claudedocs/` 산출물을 git에 커밋 (사용자 승인 시). +- PR #19 dependabot 검토/머지 도움. + +### 사용자 입력/실행 필요 (인터랙티브 — AI 불가) +- **`npx devvit login`** (브라우저 인증) → `npx devvit upload` → `npm run dev` (playtest) → `npx devvit settings set openaiApiKey` (sk-… 입력) → `npx devvit publish --public`. ← **해커톤 제출 핵심 경로, D-day ~2026-05-18.** +- Reddit UI에서: 커뮤니티 아이콘/배너 업로드, Base color `#0CAE7C`, Topics(Marketing·Business·Social Media), Rules×5 추가, Welcome 메시지, 핀 게시물 2개 작성·고정. (전부 체크리스트 HTML에 복붙 소스 있음) +- 플레이테스트 중 스크린샷 3장(compose 폼 / dry-run 프리뷰 / audit·rollback 로그) 캡처 → README + Devpost placeholder에 삽입. +- New Mod Bootcamp(2026-05-29) RSVP, r/NewMods 가입. +- Devpost 최종 제출 ("Best New Mod Tool" 트랙). + +--- + +## §4 할 수 없는 것 (외부 변수) +- Devvit 인증·업로드·publish — 인터랙티브 로그인 필요 (지난 wizard 코드 만료됨). 사용자만 가능. +- Reddit App Review 통과 시점 — Reddit 측 심사. publish 후 대기. +- r/SocialSeeding 실제 구독자 증가 — 콘텐츠·시간 의존. +- 프로덕션 서버(8080) 관련 일체 — 사용자만 제어. +- 다른 팀원(Two-Weeks-Team) 작업과의 충돌 — PR 보드 확인 필요. + +--- + +## §5 추가로 필요한 것 (사용자 확인) +- `claudedocs/` (assets + checklist + handoff) 를 git에 커밋할지? 아니면 로컬에만 둘지? +- 재제작한 SVG 새싹 마크를 그대로 쓸지, 디자이너가 원본 벡터를 줄 수 있는지? (현재는 추출 PNG 기반 hand-crafted SVG) +- vibe-mod를 `r/SocialSeeding`에 바로 설치할지(현 `dev.subreddit` 설정대로), 아니면 별도 `r/vibemod_playtest`(<200 subs) 를 둘지 — 현재 1 subscriber라 r/SocialSeeding에서 playtest 가능하지만, 라이브 커뮤니티에 dev 빌드 노출이 싫으면 분리. +- OpenAI 키 출처/쿼터 정책 확정 (global secret, 일일 쿼터 enforce 중). + +--- + +## §6 다음 세션 시작 프롬프트 (복사용) + +```text +/handon + +이전 세션 핸드오프: claudedocs/2026-05-13-reddit-setup-session-handoff.md + +읽고 다음 결정 사항에 답한 뒤 진행하세요: +1. claudedocs/ 산출물(reddit-assets + checklist + handoff)을 git에 커밋할까요, 로컬에만 둘까요? +2. vibe-mod playtest/설치를 r/SocialSeeding에서 바로 할까요, 별도 r/vibemod_playtest(<200 subs)를 만들까요? +3. 이번 세션은 해커톤 제출 경로(devvit login→upload→playtest→publish) 도움 위주인가요, 아니면 자산/문서 보강인가요? +4. 재제작한 sprout-logo.svg를 최종으로 쓸까요, 디자이너 원본 벡터를 기다릴까요? + +D-day: 2026-05-18 (devvit publish --public) +``` + +--- + +## §7 핵심 자산 위치 reference + +| 항목 | 경로 | +|---|---| +| 셋업 체크리스트 (HTML, 클릭형) | `claudedocs/reddit-setup-checklist.html` | +| Reddit 커뮤니티 아이콘 (투명, 256/512) | `claudedocs/reddit-assets/community-icon-256.png`, `…-512.png` | +| 아이콘 미리보기(배경 포함) | `claudedocs/reddit-assets/community-icon-256-bg.png` | +| Reddit 배너 | `claudedocs/reddit-assets/community-banner-1920x384.png` | +| 재제작 SVG 소스 | `claudedocs/reddit-assets/sprout-logo.svg` | +| socialseed.ing 원본 추출 | `claudedocs/reddit-assets/socialseed-original-icon.png`, `…-apple-icon.png` | +| 이 핸드오프 | `claudedocs/2026-05-13-reddit-setup-session-handoff.md` | +| 직전 dev 핸드오프 | `claudedocs/2026-05-13-session-handoff.md` | +| 갭 분석 요약 (3개 게이트 상세) | `claudedocs/gap-analysis/00-SUMMARY.md` | +| Devvit 셋업 가이드 | `docs/devvit-setup-guide.md` | +| Devvit 진단 스크립트 | `npm run doctor` → `scripts/devvit-doctor.ts` | +| App 콘솔 | https://developers.reddit.com/apps/vibe-mod | +| repo | https://github.com/Two-Weeks-Team/vibe-mod | + +### 외부 링크 (이번 세션에서 참고) +- New Mod Bootcamp Q2 (2026-05-29 RSVP): https://modevents.reddit.com/events/details/reddit-mod-events-mod-events-presents-new-mod-bootcamp-q2/ +- Ultimate Guide to Building a Community: https://redditforcommunity.com/ultimate-guide-to-building-a-community +- Reddit for Community 허브: https://redditforcommunity.com/ +- Moderator Help Center: https://mods.reddithelp.com/ +- r/NewMods: https://www.reddit.com/r/NewMods/ +- SocialSeed.ing: https://socialseed.ing + +--- + +## §8 알려진 issue / open question +- **SVG 새싹 마크는 원본 PNG를 보고 손으로 재현한 것** — potrace 없어서 픽셀 단위 트레이스는 아님. 형태·색은 근접하나 디자이너 원본이 있으면 교체 권장. +- 배너에 텍스트를 안 넣음 (Reddit 크롭 변동 때문) — 카피가 필요하면 별도 요청. +- `claudedocs/` 전체가 untracked — `.gitignore` 상태 확인 후 커밋 정책 결정 필요. +- PR #19 (dependabot) 미처리. +- ~~`dev.subreddit` ↔ 체크리스트 불일치~~ → 해소됨: HTML A 섹션을 `dev.subreddit = r/SocialSeeding` 기준으로 수정함 (별도 소형 서브 쓰고 싶으면 `devvit playtest r/`로 덮어쓰는 방법 병기). §5 질문 2는 "라이브 노출 우려 시 분리할지" 선택지로만 남음. +- Devpost 제출 마감일은 메모리상 "~2026-05-18" (devvit publish 기준) — 실제 해커톤 공식 마감일은 hackathon 룰 페이지로 재확인 권장. diff --git a/claudedocs/2026-05-13-session-handoff.md b/claudedocs/2026-05-13-session-handoff.md new file mode 100644 index 0000000..7c85e60 --- /dev/null +++ b/claudedocs/2026-05-13-session-handoff.md @@ -0,0 +1,127 @@ +# vibe-mod — Session Handoff (2026-05-13) + +> `/handon` to load. Project plan: repo root `HANDOFF.md`. Prior session: `claudedocs/2026-05-12-session-handoff.md`. +> This file = *this session* + next action. + +## §0 Two-line summary + +- **What**: Reddit "Mod Tools & Migrated Apps Hackathon — Best New Mod Tool ($10K)" entry `vibe-mod` + (mod writes a rule in English → OpenAI gpt-5.4-mini compiles it to deterministic JSON; shadow/dry-run/rollback; + LLM is build-time only, never sees Reddit content). **This session** (main now `049c28e`, PRs #17–#21 all + merged): (a) wrote root README + `docs/devvit-setup-guide.md` + `docs/devpost-submission.md` (#18); + (b) tiny safe v0.2 fact increment + `__proto__` PBT flake fix (#17); (c) **fixed 4 `devvit.json` + schema-validation errors that blocked `devvit upload`** (#20) → user ran `devvit login` → + **`devvit upload` succeeded — app live at developers.reddit.com/apps/vibe-mod** (private, "In 0 + communities"); (d) spawned **11 expert gap-analysis agents** (`claudedocs/gap-analysis/00-SUMMARY.md` + + `01–11.md`) then shipped a **hardening pass** (#21): shadow logging now actually persisted, SET-NX + races fixed, guarded ban/mute un-deadcoded, `activatedAt` so shadow window starts at activation, + `safeParseBundle` so a bad bundle can't 500 every trigger, audit-hash TTL, fail-safe author defaults, + +4 tests (174 total). All green: 174+3 tests, tsc/lint/prettier, acceptance 4/4, doctor 0/0, `devvit upload` OK. +- **Next session 1st priority** = the user's playtest run: `git pull && npm run build && npx devvit upload && + npm run dev` (playtest — needs a <200-sub `r/vibemod_playtest` they create, or `npx devvit playtest `) + → `npx devvit settings set openaiApiKey` → the 3 manual gates in `docs/devvit-setup-guide.md` §6. If a + gate fails, get `npx devvit logs vibe-mod --since 5m --verbose` + the error → debug. Then + `devvit publish --public` by ~2026-05-18 (review ≈1wk). Deferred backlog → `claudedocs/gap-analysis/00-SUMMARY.md`. + +## §1 What happened this session + +- **PR #18 `docs/launch-and-submission`** (no code): `README.md` (root — repo had none; required by + `devvit publish`), `docs/devvit-setup-guide.md` (step-by-step: wizard → overlay this repo → `npm install`/ + `build`/`doctor`/`acceptance` → `devvit login`/`upload` → `devvit settings set openaiApiKey` → + `devvit playtest` + 3 manual gates → `devvit publish --public`; with the "only you can do vs. automatable" + table and the **D-9 = 2026-05-18 publish deadline**), `docs/devpost-submission.md` (7-section draft + + Project-Impact template + Built-With + pre-submission checklist). +- **PR #17 `feat/fact-layer-v0.2`**: 4 new content facts, all pure functions of the existing trigger payload + (no new Reddit API calls, no cache changes, no new failure modes): `content.wordCount`, + `content.nonAsciiRatio` (0..1, crude non-English signal), `content.isLinkPost` (true for link/image/video + submissions; false for comments), `content.imageCount` (was hardcoded `0` → best-effort image-URL count). + Touched `rule-schema.ts` (FactPaths), `fact-bag.ts` (helpers + both builders), `system-prompt.ts` + (hint block + one few-shot + refreshed stale `gpt-4o-mini`→`gpt-5.4-mini` header); evaluator/executor + unchanged (generic over fact paths). +2 fact-bag tests, `starter-rules.test.ts` FactBag literal extended. + **Also fixed a real flake**: `rule-schema.property.test.ts`'s ".strict rejects unknown top-level field" + could generate `"__proto__"`, which an object literal doesn't make an own enumerable prop and Zod strips + anyway → excluded. (This flake was intermittently failing the pre-push hook / CI — saw it twice this session.) + +## §2 Current state + +**git**: `main` at `1444c93` (unchanged — both new branches are PRs, not merged). 2 open PRs: **#17** (fact +layer), **#18** (docs). `claudedocs/` still untracked (analysis output — both session handoffs + the +hackathon-audit HTML; not committed by convention; note: `claudedocs/*.html` makes `prettier --check .` warn +locally but it's not in the committed tree so CI is unaffected). + +**metrics** (on `feat/fact-layer-v0.2`): 170 main tests + 3 `@devvit/test` (1 skipped = replay-runner) | `tsc +--noEmit` clean | ESLint 0-warning | Prettier clean (except untracked claudedocs html) | `npm run acceptance` +4/4 | `npm run doctor` 0 hard issues (2 warnings: not logged in, no `.devvit-app-id` — both normal pre-upload) +| `npx vite build` → `dist/server/index.cjs` ~2.1 MB | `npm run openai:smoketest` last run (prior session) 7/7. + +**env**: node v24.15.0 / npm 11.12.1 / `@devvit/cli` 0.12.23 (`.nvmrc`=22 for CI). `.env` has the user's OpenAI +key. No `.devvit-app-id` (wizard creates it). `npm ci` does **not** work (esbuild `EBADPLATFORM`) → use `npm install`. + +**today**: 2026-05-13. Hackathon deadline: **2026-05-27 18:00 PT — D-14** (firm). `devvit publish --public` +review ≈ 1 business week → start publish by **~2026-05-18 (D-9)**. + +## §3 Next session — what to do + +**User action required (can't be automated):** +1. Run the Devvit "Mod Tool" wizard at `developers.reddit.com/new` → overlay this repo → see + **`docs/devvit-setup-guide.md`** (PR #18) for the exact, official-docs-based procedure. It's the only + blocker on everything downstream. +2. `npm install` → `npm run build`/`doctor`/`acceptance` → `devvit login` → `devvit upload` → + `devvit settings set openaiApiKey` → `npm run dev` (playtest) → run the 3 manual gates in §6 of that guide. + If anything fails, grab `devvit logs vibe-mod --since 5m --verbose` + the error → that's the next + debugging session's input. +3. `devvit publish --public` by ~2026-05-18. Then (post-publish): demo video < 1 min **no BGM**, ≥3 screenshots, + fill the `<<...>>` placeholders in `docs/devpost-submission.md`, submit on Devpost (≥8h buffer). + +**Claude can do anytime (wizard not needed):** +- Merge PR #17 + #18 once CI is green (`gh pr merge --merge` — **never `--squash`**; this repo preserves + individual commit history). +- Fill in `README.md` screenshots / `devpost-submission.md` placeholders once the user provides them. +- Further v0.2 fact-layer work — the *next* increment is the stateful/API-backed facts deliberately left out + of PR #17: repost detection (Redis-tracked URL/title hashes within the sub), cross-sub spam patterns + (needs `reddit.getCommentsAndPostsByUser`), true `author.subJoinAgeHours`, `author.hasVerifiedEmail`, + per-sub recent-activity counts. Each needs Redis state + new API calls + dry-run-replay support + failure + handling — its own PR, larger, more risk; probably post-hackathon. +- Optional cleanup: `docs/README-vibe-mod.md` is now superseded by the root `README.md` and has a stale + "gpt-5.4-nano (default)" line — delete or fix it. `HANDOFF.md`'s "Step 1" wizard detail is now superseded + by `docs/devvit-setup-guide.md` (more precise) — could trim it / point to the guide. + +## §4 Can't do (external) + +- `devvit build` / `playtest` / `upload` / `publish` runtime — needs Reddit OAuth + app registration; no local + emulator. Logic is covered by the in-memory testkit + `@devvit/test` + `npm run replay`; Devvit's own + routing/payload-injection/RPC is only verified by `devvit playtest` (user only). +- App review (~1 week after `devvit publish --public`) — not controllable; fall back to an unlisted + `devvit install` link for the demo if it's slow. +- Demo video / screenshots — need the running app → after wizard + playtest. + +## §5 Key asset locations (delta from prior handoff) + +| Path | Note | +|---|---| +| `README.md` (root) | **NEW** (PR #18) — repo front page + `devvit publish` requirement | +| `docs/devvit-setup-guide.md` | **NEW** (PR #18) — the wizard→publish procedure; supersedes `HANDOFF.md` Step 1 detail | +| `docs/devpost-submission.md` | **NEW** (PR #18) — 7-section Devpost draft + checklist | +| `src/shared/rule-schema.ts` | FactPaths now includes `content.wordCount`/`nonAsciiRatio`/`isLinkPost` (PR #17) | +| `src/server/fact-bag.ts` | new pure helpers; `imageCount` is now real, not hardcoded 0 (PR #17) | +| (everything else) | as in `claudedocs/2026-05-12-session-handoff.md` §7 | + +## §6 Next session start prompt + +```text +/handon + +이전 세션 핸드오프: claudedocs/2026-05-13-session-handoff.md +프로젝트 전반 계획: HANDOFF.md (레포 루트) + +읽고 아래에 답한 뒤 진행하세요: +1. PR #17(fact-layer v0.2) + #18(docs)를 머지할까요? (CI green이면 gh pr merge --merge — squash 금지) +2. Devvit wizard를 진행했나요? (docs/devvit-setup-guide.md 절차대로 — yes면 어디서 막혔는지 + devvit logs / no면 가이드대로 진행 권유) +3. v0.2 fact-layer 다음 증분(repost/cross-sub/API-backed facts — Redis state + 새 API 필요, 큰 PR)을 지금 시작할까요? 아니면 해커톤 후로? +4. README 스크린샷 / Devpost 플레이스홀더 채울 자료가 있나요? + +D-day: 2026-05-27 18:00 PT (firm). publish 리뷰 ~1주 → ~D-9(2026-05-18)까지 devvit publish 시작. +``` + +--- +작성: 2026-05-13 / `/handoff` skill diff --git a/claudedocs/2026-05-14-openai-400-final-report.html b/claudedocs/2026-05-14-openai-400-final-report.html new file mode 100644 index 0000000..9ad0493 --- /dev/null +++ b/claudedocs/2026-05-14-openai-400-final-report.html @@ -0,0 +1,521 @@ + + + + + +vibe-mod OpenAI 400 디버깅 — 최종 보고서 + + + + + + + + +
+ +
+

vibe-mod OpenAI HTTP 400 디버깅 — 최종 보고서

+

SELECTION-array 버그가 9 PRs · 7 라운드 추측 fix를 거쳐 production verified 까지

+
+ 📅 2026-05-14 + ✅ Resolved (PR #39 + #40) + 🚀 Production v0.0.41 verified + ⏰ D-9 (publish ≤ 2026-05-18) + Generated by Claude Code +
+
+ +
+

1. Executive Summary

+ +
+
🎯 Root Cause (한 줄)
+

settings.get('openaiModel')은 SELECTION 타입 → string array 반환 (["gpt-5.4-mini"]). 우리는 그걸 as string으로 캐스팅해 OpenAI 요청 body의 "model" 필드에 array로 보냈고, OpenAI는 "We could not parse the JSON body" (단일 필드 type mismatch에 대한 generic wording) 으로 거부.

+
+ +
+
+
9
+
Total PRs (#32–#41)
+
+
+
7
+
Speculative Fix Rounds
+
+
+
2
+
Root-Cause PRs (#39, #40)
+
+
+
200
+
Final HTTP Status
+
+
+
+
Chrome E2E Verified
+
+
+
5
+
Days to D-9 publish
+
+
+ +

해결

+
    +
  • PR #39callOpenAI에서 SELECTION-array unwrap (root cause fix). MERGED 2026-05-13T14:55Z.
  • +
  • PR #40 — submit handler의 동일 버그 추가 unwrap (defense-in-depth). MERGED 2026-05-13T15:55Z.
  • +
  • PR #41 — postmortem doc + probe-branch cleanup record. MERGED.
  • +
+ +

Production 검증 (v0.0.41)

+
+ ✅ TOAST: Compiled rule "New-account posts to mod queue". Dry-run started — check Dashboard in 30s. +
+

Chrome 자동화 (browser_cookie3 + Playwright)로 사용자 Reddit 세션을 빌려 r/SocialSeeding의 vibe-mod: Compose rule 메뉴를 실제 클릭한 결과. 자세한 스크린샷은 §4.

+
+ +
+

2. Fix Attempt Timeline (v0.0.32 → v0.0.41)

+ + + + + + + + + + + + + + + + + +
버전PR가설실제 변경body charsHTTP 결과
v0.0.32(probe v3 deploy)multi-message + 7KB + nested escape가 transit corruptionprobe v3 6-stage 배포~7068400
v0.0.33#32multi-message → single user message로 합치면 transit OKmessages 10 → 1~7508400
v0.0.34#33source의 non-ASCII (—, ≈, →, ─) 가 wire 위에서 깨짐system prompt 모든 non-ASCII → ASCII 치환~7500400
v0.0.35#34reasoning_effort + verbosity 조합이 large body에서 trip두 필드 제거~7000400
v0.0.36#35content의 \n escape가 transit corruption모든 newline → space로 flatten~6800400
v0.0.37#36string body re-encode 가 corrupting → Uint8Array bypassbody: Uint8Array + Content-Length4401400
v0.0.38#37content의 \" escape density가 culpritJSON.stringify 제거, key=value 평문화4279400
v0.0.39#38callOpenAI default 모델이 production에서 미테스트model = 'gpt-5.4-nano' 하드코드 + console.log 추가4277200* (downstream toast: "unsupported action")
v0.0.40#39SELECTION array 진단 → unwrap + few-shot 복원Array.isArray(raw) ? raw[0] : raw6545200 ✓
v0.0.41#40submit handler에도 같은 unwrap 적용defense-in-depth6576200 + Chrome verified
+ +
+
⚠️ 결정적 단서: PR #38의 console.log
+

한 줄의 진단 로그가 7-round speculative loop를 끝냈다:

+
console.log('[vibe-mod] callOpenAI: openaiModel raw =', JSON.stringify(rawValue), 'unwrapped =', JSON.stringify(model));
+

Production 배포 후 첫 cron tick에서:

+
[vibe-mod] callOpenAI: openaiModel raw = ["gpt-5.4-mini"] unwrapped = "gpt-5.4-mini"
+

→ 즉시 root cause 식별. PR #39는 그 로그를 본 후 5분만에 작성됐고 PR #40까지 1시간 안에 완료.

+
+
+ +
+

3. Root Cause 상세

+ +

3.1 The bug

+

devvit.jsonopenaiModel 필드는 SELECTION 타입:

+
json{
+  "name": "openaiModel",
+  "type": "SELECTION",
+  "label": "OpenAI model for rule compilation",
+  "options": [
+    { "label": "gpt-5.4-mini (recommended)", "value": "gpt-5.4-mini" },
+    { "label": "gpt-5.4-nano (cheapest)", "value": "gpt-5.4-nano" },
+    { "label": "gpt-5.4 (full)", "value": "gpt-5.4" }
+  ]
+}
+ +

Devvit의 settings.get은 SELECTION 필드를 string array로 반환한다 (single-select여도). 우리 코드는:

+ +
typescriptlet model = 'gpt-5.4-mini';
+try {
+  model = ((await settings.get('openaiModel')) as string) || 'gpt-5.4-mini';  // ← BUG: cast 무시, runtime은 array
+} catch (err) { /* ... */ }
+
+const rawBody = JSON.stringify({
+  model,                                    // ← ["gpt-5.4-mini"] 그대로 들어감
+  response_format: { type: 'json_object' },
+  messages,
+  max_completion_tokens: 600,
+});
+ +

최종 wire body:

+
json{ "model": ["gpt-5.4-mini"], "response_format": {...}, "messages": [...], "max_completion_tokens": 600 }
+ +

3.2 OpenAI의 misleading 에러

+

OpenAI는 model 필드 type mismatch에 대해 다음과 같이 응답:

+
json{
+  "error": {
+    "message": "We could not parse the JSON body of your request. (HINT: This likely means you aren't using your HTTP library correctly. ...)",
+    "type": "invalid_request_error",
+    "param": null,
+    "code": null
+  }
+}
+ +
+
🚨 Wording trap
+

이 메시지는 구조적 JSON parse 실패(unterminated string, garbled bytes 등)처럼 들린다. 실제로는 단일 필드의 type mismatch인데도. 더 구체적인 "Invalid type for parameter 'model': expected string, received array" 같은 메시지였다면 1라운드에 잡혔을 것.

+

이 wording이 우리를 7 라운드 speculative fix loop에 가뒀다 — body shape, escape density, byte vs string, message count, size, Content-Length, JSON-syntax-in-content를 차례로 추적했지만 모두 hit miss.

+
+ +

3.3 왜 probes (a)/(b)/(d)/(e)/(f)는 모두 200이었나

+

진단 probe들은 model: 'gpt-5.4-nano' (string literal) 을 하드코드. 그래서 wire body의 model 필드는 항상 string. settings.get을 우회했기 때문에 array bug를 만난 적이 없다. "잘 작동하는 control"을 잘못 만든 케이스: probe가 production code path와 평행한 별도 fetch 호출을 했기 때문에 진단 가치가 제한됨.

+ +

3.4 The fix (PR #39 + #40)

+
typescriptconst DEFAULT_MODEL = 'gpt-5.4-mini';
+let model = DEFAULT_MODEL;
+try {
+  const raw = await settings.get('openaiModel');
+  let unwrapped: unknown = raw;
+  if (Array.isArray(raw) && raw.length > 0) unwrapped = raw[0];
+  if (typeof unwrapped === 'string' && unwrapped.trim()) model = unwrapped.trim();
+  console.log('[vibe-mod] callOpenAI: openaiModel raw =', JSON.stringify(raw), 'unwrapped =', JSON.stringify(model));
+} catch (err) {
+  console.warn('[vibe-mod] callOpenAI: settings.get(openaiModel) threw — using default:', describeErr(err));
+}
+

같은 fix를 submit handler line 477도 적용 (PR #40) — draft.llmModel 메타데이터에도 array가 들어가지 않도록.

+
+ +
+

4. End-to-End Production 검증

+ +

수동 click 없이 자율 검증 — scripts/chrome-reddit-v3.py (browser_cookie3 + Playwright):

+ +
    +
  1. browser_cookie3.chrome(domain_name="reddit.com") → 16 cookies 추출
  2. +
  3. Playwright Chromium에 storageState로 주입 (사용자 로그인 세션 그대로 활용)
  4. +
  5. r/SocialSeeding 진입 → Open overflow menu 버튼 클릭
  6. +
  7. vibe-mod: Compose rule 메뉴 (Lit shadow DOM) → page.mouse.click(1322, 436) 좌표 기반 클릭 (Playwright 표준 click은 visibility 판정 실패)
  8. +
  9. <faceplate-form> 모달 열림 → <textarea name="rule"> 채움: "Send any post from accounts less than 7 days old to the mod queue"
  10. +
  11. Submit 클릭 → 2.5초 후 toast 캡처
  12. +
+ +
+
🎉 Captured Toast
+
+ Compiled rule "New-account posts to mod queue". Dry-run started — check Dashboard in 30s. +
+

→ OpenAI compile 200 OK + valid JSON 출력 + draft 저장 + dry-run 스케줄 모두 확인.

+
+ +Chrome screenshot of Reddit toast: Compiled rule 'New-account posts to mod queue'. Dry-run started — check Dashboard in 30s. +

Chrome 스크린샷 — r/SocialSeeding 페이지 우측 하단의 success toast (모자이크 마스킹 없이 production raw)

+ +

Production logs (v0.0.41, 같은 click 트리거)

+
log[vibe-mod] mod check: getModerators → 2 mods: DragonfruitAfraid309,vibe-mod
+[vibe-mod] mod check: DragonfruitAfraid309 ∈ mods? true
+[vibe-mod] callOpenAI: settings.get(openaiApiKey) ok: { defined: 'string', len: 164 }
+[vibe-mod] callOpenAI: openaiModel raw = ["gpt-5.4-mini"] unwrapped = "gpt-5.4-mini"
+[vibe-mod] callOpenAI: body chars = 6576
+# (HTTP 400 line 없음 → success path)
+# (submit: callOpenAI threw 라인 없음 → 예외 미발생)
+
+ +
+

5. 외부 Contribution 정리

+ +

잘못된 root-cause 추정이 외부 이슈/PR에 흔적을 남겼음. 모두 수정 또는 close했다.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
위치종류문제액션결과
reddit/devvit#261우리가 연 issue본문에 OpenAI 400을 plugin RPC로 잘못 연결한 한 줄 + 일시 platform 문제 (현재 비재현)본문 수정 (상단 ⚠️ 경고 박스 + Retraction 섹션 + postmortem 링크) + not planned closeCLOSED ✅
reddit/devvit#258 (코멘트)다른 사람 issue에 단 코멘트시점상 정확한 reproduction이지만 현재 비재현 → reader 오해 가능follow-up 코멘트 추가 (24h+ 안정 + #261 retraction 링크). 원본 코멘트는 audit log 보존 위해 미수정.UPDATED ✅
reddit/devvit-docs#109우리가 연 PR본문 + 파일이 #261을 영구 platform 버그처럼 framing(1) PR 본문 수정 — transient failure defense로 reframe. (2) 파일 commit ecc77dd push — "Related issues" 섹션에서 #261 closed 명시. (3) 알림 코멘트.REFRAMED ✅ (OPEN, CLA pending)
vibe-mod#32–#38우리가 머지한 PR (잘못된 hypothesis fix)본문이 잘못된 가설을 명시git history는 immutable — postmortem PR #41 (docs/postmortems/2026-05-14-...)이 모든 시도를 한 곳에 정리POSTMORTEM ✅
+ +

5.1 정정 원칙

+
    +
  • 우리가 만들고 잘못된 것 → 수정 또는 close (#261, PR #109)
  • +
  • 다른 곳에 단 코멘트 (편집 시 audit log 노이즈) → follow-up 코멘트로 정정 (#258)
  • +
  • 머지된 git history → immutable, postmortem doc으로 정정 기록 (PR #41)
  • +
+
+ +
+

6. Lessons + 코드베이스 잔여 audit

+ +
+
💡 Top 3 Lessons
+
    +
  1. Always log resolved values of settings.get(...) — 진단 한 줄이 7 라운드를 절약. console.log('raw =', JSON.stringify(raw), 'unwrapped =', ...) 패턴을 모든 외부 설정 진입점에 추가하라.
  2. +
  3. as string casts are tech debt — runtime이 실제로 structured면 silent 거짓말. Devvit settings.get이 discriminated union (STRING | SELECTION_ARRAY | NUMBER)을 반환했다면 컴파일 타임에 잡혔을 것.
  4. +
  5. 진단 probe는 production code path를 mirror하라 — probe가 평행 fetch 호출을 만들면, code path 한쪽의 버그를 못 잡는다. 차라리 production 함수를 직접 호출하는 probe가 더 신뢰성 있음.
  6. +
+
+ +

6.1 코드베이스 잔여 SELECTION-array audit

+
bash# 다른 SELECTION 타입 settings 확인:
+$ grep -nE '"type":\s*"SELECTION"' devvit.json
+# → openaiModel only (이미 fix됨)
+
+# settings.get + as string 잔여 패턴:
+$ grep -rn "as string" src/server/ | grep "settings.get"
+src/server/index.ts:1364:    subKey = ((await settings.get('subredditOpenaiApiKey')) as string) ?? '';
+src/server/index.ts:1378:      const globalKey = ((await settings.get('openaiApiKey')) as string) ?? '';
+# → 둘 다 STRING 타입 (devvit.json 확인) — cast 정확. 추가 unwrap 불필요.
+ +

6.2 OpenAI 에러 wording 개선 제안

+

OpenAI에 별도 issue로 fired할지 검토 가능 (이번 세션에선 행동 안 함):

+
    +
  • 현재: "We could not parse the JSON body of your request" — 모든 type mismatch에 동일
  • +
  • 제안: "Invalid type for parameter 'model': expected string, received array"
  • +
+

이 wording 변경만으로 동일 카테고리의 디버깅 시간이 7 라운드 → 0 라운드로 단축됨.

+
+ +
+

7. 다음 단계

+ + + + + + + + + + +
액션마감상태
npx devvit publish --public 시작2026-05-18 (D-9)⏳ 5일 남음 (사용자 액션)
Reddit App Directory 리뷰 통과 (~1주)~2026-05-25⏳ Reddit team
데모 영상 (1분 미만, BGM 없음)2026-05-27 18:00 PT⏳ Compose flow 라이브 검증됐으니 진행 가능
Devpost 제출 (URL/팀 username/영상 placeholder 채우기)2026-05-27 18:00 PT⏳ 사용자 액션
reddit/devvit-docs CLA 서명 (PR #109 머지 위해)⏳ 사용자 액션 (선택)
+ +
+
ℹ️ D-day 14 일 남음
+

2026-05-27 18:00 PT firm. 오늘 = 2026-05-14 KST (D-13). publish 리뷰 ~1주 추정 → publish 시작 권장 마감 2026-05-18 (D-9), 5일 안에. compose flow가 라이브 검증되었으므로 publish + 영상 + Devpost 제출 모두 진행 가능.

+
+ +

7.1 검증 자산 위치

+ + + + + + + + + + +
자산경로
Postmortem docdocs/postmortems/2026-05-14-openai-400-selection-array.md
Chrome 자동화 스크립트scripts/chrome-reddit-v3.py
Toast 스크린샷playwright/.auth/v3-05-after-submit-1.png
이 보고서claudedocs/2026-05-14-openai-400-final-report.html
Production appdevelopers.reddit.com/apps/vibe-mod (v0.0.41)
Demo subredditr/SocialSeeding
+
+ +
+ + + + + + + diff --git a/claudedocs/2026-05-14-openai-400-resolved-handoff.md b/claudedocs/2026-05-14-openai-400-resolved-handoff.md new file mode 100644 index 0000000..7edeeec --- /dev/null +++ b/claudedocs/2026-05-14-openai-400-resolved-handoff.md @@ -0,0 +1,240 @@ +# vibe-mod — Session Handoff (2026-05-14 10:00 KST, OpenAI 400 RESOLVED + Chrome verified + 외부 정리 완료) + +> `/handon` 으로 로드. 직전 핸드오프: `claudedocs/2026-05-13-openai-probe-v3-handoff.md` (probe v3 6-stage 배포 직후). +> +> 이 파일 = OpenAI HTTP 400 의 진짜 원인(SELECTION-array 버그) 식별 + 9 PRs 머지 + Chrome 자동화로 production "Compiled rule" toast 캡처 + 외부 reddit-org contribution 모두 정리/철회 완료. + +--- + +## §0 두 줄 요약 + +- **무엇**: 9 PRs (#32~#41) 머지. **진짜 root cause = `settings.get('openaiModel')` SELECTION 타입이 `["gpt-5.4-mini"]` array 반환**. 7 라운드 speculative fix(body shape, escape density, byte vs string, message count, size, JSON-syntax-in-content) 모두 wrong direction. PR #38의 진단 console.log 한 줄이 culprit 노출 → PR #39+#40 fix → v0.0.41 production deploy → Chrome 자동 검증으로 toast `Compiled rule "New-account posts to mod queue". Dry-run started — check Dashboard in 30s.` 캡처. 외부 reddit-org 흔적 모두 cleanup (PR #109 close + gist로 옮김, #258 코멘트 둘 다 삭제, #261 close). +- **다음 세션 1순위**: D-9 (2026-05-18) 까지 4일 남음. `npx devvit publish --public` 실행 (사용자 액션) → Reddit App Directory 리뷰 시작 → 데모 영상(1분 미만, BGM 없음) 제작 → Devpost 제출 (D-day 2026-05-27 18:00 PT, 13일 남음). + +--- + +## §1 진행한 작업 (시간순) + +### Phase A — `/handon-goal` 자동 로드 + 13-tuple condition 합성 +- 직전 핸드오프 (`2026-05-13-openai-probe-v3-handoff.md`) 자동 로드 +- 5-condition `/goal` 시작 — probe 결과 수집부터 production menu-click 검증까지 자율 진행 directive + +### Phase B — Probe v3 결과 수집 + 7 라운드 speculative fix +- v0.0.32 probe 3 cycles 모두 동일 패턴: (a)/(b)/(d)/(e)/(f) 200, (c) 400. 단일 변수 isolation 시도: + - **PR #32** (v0.0.33): multi → single message → 400 + - **PR #33** (v0.0.34): source non-ASCII 제거 → 400 + - **PR #34** (v0.0.35): reasoning_effort + verbosity 제거 → 400 + - **PR #35** (v0.0.36): \n escape flatten → 400 + - **PR #36** (v0.0.37): Uint8Array body + 1 example → 400 (body bytes=4401, probe(f)의 5610 보다 작은데도) + - **PR #37** (v0.0.38): JSON-syntax 제거 (key=value 평문화) → 400 +- 매 라운드마다 local POST는 200, Devvit transit만 400 → "transit corruption" 가설로 잘못 좁아짐 + +### Phase C — 진단 console.log → root cause 식별 +- **PR #38** (v0.0.39): hardcode `gpt-5.4-nano` (probe-verified 모델) + log resolved model +- 첫 production tick에서 `[vibe-mod] callOpenAI: openaiModel raw = ["gpt-5.4-mini"] unwrapped = "gpt-5.4-nano"` 노출 +- → **SELECTION 타입은 string array 반환**. PR #32-#37 모두 body의 `"model"` 필드가 array였음. OpenAI는 이를 `"could not parse the JSON body"` (단일 필드 type mismatch에 대한 misleading wording)로 거부. + +### Phase D — Root cause fix + 검증 +- **PR #39** (v0.0.40): callOpenAI에서 `Array.isArray(raw) ? raw[0] : raw` unwrap + JSON.stringify(ex.assistant) few-shot 복원 +- **PR #40** (v0.0.41): submit handler line 477도 같은 unwrap (defense-in-depth) +- Production logs: `body chars = 6576`, `openaiModel raw = ["gpt-5.4-mini"] unwrapped = "gpt-5.4-mini"`, **HTTP 400 line 없음**, **submit threw 없음** = success +- **PR #41**: postmortem doc + probe-branch cleanup record (`docs/postmortems/2026-05-14-openai-400-selection-array.md`, 108줄) + +### Phase E — Chrome 자동화 검증 (browser_cookie3 + Playwright) +- `scripts/chrome-reddit-v3.py` 작성 +- 사용자 Chrome cookie 16개 → Playwright Chromium에 storageState 주입 +- r/SocialSeeding 페이지의 `Open overflow menu` 클릭 → Lit shadow-DOM `` 좌표 기반 mouse click (1322, 436) +- `` 모달의 `