vibe-mod — 최종 통합 계획 v3
audit-fixed · implementation-ready · 보안+품질 18건 패치 적용 · 4 전문가 합의 + 2 audit agent 검증 + 공식 Devvit 문서 verbatim cross-reference
1. TL;DR
"Write a moderation rule. In English. It works."
가장 빠른 입소문 모먼트는 룰 생성이 아니라 1-click rollback. 데모의 hero shot은 모드가 방금 만든 룰을 자신있게 되돌리는 장면.
- LLM: OpenAI
gpt-5.4-nano(default, 최신·최저가) ·gpt-5.4-mini(premium 옵션) · Anthropic은 Reddit 정책 거부 - 패턴:
@devvit/webv0.12.22 + Hono v4.12+ + Zod v4.4+ +devvit.json - 핵심 invariant: 런타임에 LLM 호출 0회. 모든 액션 평가는 결정론.
- 안전 마진: shadow mode 24h default, dry-run preview 의무, 30-day rollback, action whitelist 하드코딩
- 실행 신뢰도: 코드 산출물 6개 모두 작성 완료, 보안+품질 audit 2회 통과
| 상 | 확률 (감소 조건) | 확률 (증가 조건) |
|---|---|---|
| Grand $10K (Best New Mod Tool) | 20% (audit fix 미적용 → self-DoS) | 50%+ (모든 patch 적용 + 5 베타 모드 sub install) |
| HM $1K | 40% | 60% |
| Moderator's Choice $10K | 10% | 30% (모드 5인 추천 시) |
2. 18건 audit 결함 → 18건 패치 (전수 적용)
독립 audit agent 2명 (security-engineer + quality-engineer)이 별도로 코드 산출물·문서·아키텍처를 검토. 결정적 결함 18건을 발견하여 전수 패치 적용.
| # | 심각도 | 발견 위치 | 증상 | 적용한 패치 |
|---|---|---|---|---|
| 1 | CRIT | index.ts:rate-limit-circuit-breaker |
zCard('audit')는 전체 entries 카운트 → 첫 5분 cron 후 영구 차단 (self-DoS) |
FIXED zCount(auditKey, oneHourAgo, '+inf')로 교체 |
| 2 | CRIT | 모든 form/menu handler | 서버측 모드 인증 부재 — forUserType:"moderator"는 UI hint일 뿐 |
FIXED isCallerModerator() guard 모든 handler 진입점에 추가, 모드리스트 5분 캐시 |
| 3 | HIGH | evaluator.ts:matches |
regex 200자 제한으로는 ReDoS 방어 불가 | FIXED 컴파일 시점에 safe-regex-style 검사 + 런타임 80자 제한 + nested quantifier/backref 거부 + 입력 4096자 cap |
| 4 | HIGH | fact-bag.ts |
isModerator, subKarma, hasVerifiedEmail 영구 하드코딩 → 룰 silent 오작동 |
FIXED 실제 API 호출 (getUserKarmaFromCurrentSubreddit, getModerators 5분 캐시) + try/catch + SAFE_AUTHOR_DEFAULTS |
| 5 | HIGH | rule-schema.ts |
Zod .strict() 누락 → LLM이 schema 외 필드 smuggle 가능 |
FIXED Rule, RuleBundle, Action 모든 객체에 .strict() |
| 6 | HIGH | trigger handlers | Devvit at-least-once 전달 → 중복 trigger 시 중복 actions | FIXED isDuplicateTrigger(trigger, thingId) dedupe, Redis seen:* 키 10분 TTL |
| 7 | HIGH | fact-bag.ts:getUserByUsername |
API 에러 시 trigger 전체 실패 — try/catch 없음 | FIXED try/catch + warn + SAFE_AUTHOR_DEFAULTS 반환 |
| 8 | MED | executor.ts:rate-limit-key |
exists → set은 TOCTOU race | FIXED trySetIfNotExists watch+multi+exec 원자적 helper |
| 9 | MED | index.ts:clarification |
이전 rule + answer 문자열 concat → prompt injection 벡터 | FIXED clarificationAnswer를 별도 user turn으로 전송, 원본 rule field는 disabled |
| 10 | MED | 모든 Redis 키 | sub-scope prefix 없음 — Devvit isolation 변경 시 leak | FIXED 모든 키에 ${subName}: prefix (rules, audit, rollback, ratelimit, circuit, modlist, seen) |
| 11 | MED | executor.ts:actionId |
Math.random() 비암호학적 → rollback token 예측 가능 |
FIXED crypto.getRandomValues(Uint8Array(9)) hex 18자 suffix |
| 12 | MED | executor.ts:42-44 |
circuit breaker가 'lock' 액션으로 audit 기록 (audit fidelity 깨짐) |
FIXED ctx.rule.then.map(...) 실제 액션 verb로 row 작성 |
| 13 | MED | executor.ts:flair |
setSuggestedSort placeholder → 실제 flair 안 됨 |
FIXED reddit.setPostFlair({subredditName, postId, text, cssClass}) |
| 14 | MED | fact-bag.ts:containsRegex |
항상 '' → matches op이 빈 문자열에 매칭 시도 |
FIXED 실제 post/comment body로 채움 |
| 15 | MED | index.ts:Zod error reflection |
raw Zod error 메시지를 toast로 노출 → schema 정보 leak | FIXED summarizeValidationError가 user-safe message 반환 |
| 16 | MED | devvit.json:openaiApiKey |
global scope만 → 모든 install이 단일 키 공유 (OpenAI ToS 위반 risk) |
FIXED 하이브리드: global secret (디폴트) + subredditOpenaiApiKey override (BYOK 경로) |
| 17 | MED | devvit.json:shadowDurationHours |
설정만 있고 wiring 없음 → 장식 | FIXED shadow-promote-check cron이 설정값 읽어 자동 promote |
| 18 | MED | scheduler:audit-retention |
zRemRangeByScore는 ZSet entry만 삭제, audit:* hashes는 leak |
FIXED 먼저 ZRange로 ID 추출 → del(audit:<id>) → ZRemRange |
3. 전략 lock (4 전문가 합의 + 검증)
3.1 한 줄 정의 (Doumont)
"Write a moderation rule. In English. It works." (3 verifiable clauses)
3.2 Positioning (Porter)
- 경쟁축: AutoModerator (built-in, YAML 학습 곡선) / ContextMod (JSON, 학습 비용 큼) / "Do nothing" (80% 모드의 기본값 — 이게 진짜 시장)
- 전략: focused differentiator on time-to-first-rule axis for non-technical mods
- 4일 moat: rule library effect + audit/rollback trust + jury pool installed base
3.3 JTBD (Christensen)
"문제 발견한 오후에 룰을 ship하고, 틀리면 undo한다" — 이건 non-consumption. 모드들은 AutoMod vs ContextMod 선택이 아니라 "YAML 쓸까 / 안 쓸까"에서 80%가 "안 씀"을 선택. 그게 우리 시장.
3.4 Remarkability (Godin)
"Rollback is the demo, not creation." 모드가 자신이 방금 만든 룰을 자신있게 1초 만에 삭제하는 장면이 입소문 hero shot.
3.5 Lead metric (Drucker)
Time from typed sentence to active rule. Target: under 60 seconds. 데모에서 stopwatch 표시.
3.6 Voice + visual
- Stripe-clean + Linear-direct. 짧은 문장. 동사 > 명사.
- 금지어: "powerful", "seamless", "AI-powered"
- Palette: Reddit 다크 native (#0B1416 bg, #46D160 accent, #0079D3 CTA)
- 데모 BGM 없음 (contrarian call)
3.7 7 절대 hard lock
- LLM은 build-time only — 룰 평가 시 0 호출
- Action whitelist 하드코딩:
report/flair/lock/modqueue/remove만 LLM-permitted;ban/mute/permaban은 명시적 checkbox 필요 - Dry-run preview 강제 before Activate
- Shadow mode default ON 24h
- 30-day rollback window, 1-click undo 항상 visible
- LLM은 Reddit 콘텐츠 안 봄 — 모드의 자연어만 처리 (compliance 핵심)
- 영어만 v0.1 — i18n은 v0.2
4. 아키텍처 v3 (verbatim verified + audit-patched)
4.1 컴포넌트 다이어그램
┌──────────────────────────────────────────────────────────────────┐
│ Mod's Browser (Reddit App Directory) │
└────┬──────────────────────────────────────────────┬──────────────┘
│ Mod menu (right-click) │ Settings page
▼ ▼
┌────────────────────────────────────────────────────────────────┐
│ Devvit App: vibe-mod (Hono HTTP routes) │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌──────────────┐ │
│ │ Menu: Compose │ │ Menu: Dashboard │ │ Menu: Undo │ │
│ │ ↓ form │ │ ↓ form │ │ ↓ inline │ │
│ └────────┬────────┘ └────────┬────────┘ └──────┬───────┘ │
│ │ │ │ │
│ ┌────────▼──────────────────────────────────────▼─────────┐ │
│ │ isCallerModerator() guard (audit FIND-03 patch) │ │
│ └────────┬─────────────────────────────────────────────────┘ │
│ ▼ │
│ ┌────────────────────┐ ┌─────────────────────────────┐ │
│ │ OpenAI NL→JSON │ │ Rule store (Redis, sub- │ │
│ │ - safe-regex chk │ │ scoped: rules:active, │ │
│ │ - Zod strict │ │ rules:draft, audit ZSet, │ │
│ │ - GUARDED gate │ │ rollback:* TTL 30d) │ │
│ └────────┬───────────┘ └──────────────┬──────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Triggers (with isDuplicateTrigger dedupe — FIND-?6) │ │
│ │ onPostSubmit onCommentSubmit │ │
│ │ onPostReport onCommentReport │ │
│ │ onAppInstall onAppUpgrade │ │
│ └────────┬────────────────────────────────────────────┘ │
│ ▼ │
│ ┌────────────────────┐ ┌─────────────────────────────┐ │
│ │ Evaluator (pure TS │ │ Executor │ │
│ │ deterministic, │───▶│ - action whitelist enforced│ │
│ │ empty-array safe) │ │ - circuit breaker (sub + │ │
│ └────────────────────┘ │ global kill switch) │ │
│ │ - audit + rollback atomic │ │
│ └──────┬──────────────────────┘ │
│ ▼ │
│ ┌────────────────────────────────────────────────────┐ │
│ │ Scheduler (sub-scoped): │ │
│ │ - audit-retention cron daily (cascade del) │ │
│ │ - rate-limit-circuit-breaker cron */5min │ │
│ │ (zCount last-hour — FIND-04 fix) │ │
│ │ - shadow-promote-check cron */15min │ │
│ │ (reads shadowDurationHours — Gap #6 fix) │ │
│ │ - dry-run-replay one-shot │ │
│ └────────────────────────────────────────────────────┘ │
└──────────────────────────────────┬─────────────────────────────┘
│
▼
┌───────────────────────────┐
│ OpenAI gpt-4o-mini │
│ api.openai.com (globally │
│ allowlisted by Devvit) │
│ BYOK key OR developer key │
└───────────────────────────┘
4.1b 최신 패키지·모델 lock (2026-05-11 verbatim verified)
| 요소 | 버전 | 출처 |
|---|---|---|
@devvit/web | 0.12.22 | npm view 2026-05-11. Release 0.12.22 (May 1, 2026) |
@devvit/redis | 0.12.22 | same — Devvit monorepo versioning |
@devvit/reddit | 0.12.22 | same |
@devvit/cli (devDep) | 0.12.22 | same |
hono | 4.12.18 | npm 2026-05-11. Devvit Web 공식 추천 server framework |
zod | 4.4.3 | npm 2026-05-11. v4: .strict()·.lazy()·discriminatedUnion 모두 호환 (vibe-mod 사용 부분) |
typescript (devDep) | 5.7+ | Devvit Web 권장 (TS 6은 검증 안 됨) |
vitest (devDep) | 4.1.5 | npm 2026-05-11. Browser-native testing default |
| OpenAI 기본 모델 | gpt-5.4-nano | openai.com 2026-05. $0.20/$1.25 per 1M tokens. json_object 지원 |
| OpenAI 프리미엄 옵션 | gpt-5.4-mini | openai.com 2026-05. $0.75/$4.50 per 1M tokens. 400K context |
| OpenAI 폴백 (구버전) | gpt-5-mini, gpt-5-nano | 여전히 API 가용. 비용은 5.4 시리즈와 유사 |
| Node.js | ≥22.2.0 | Devvit quickstart 요구사항 |
gpt-5.4-nano는 (a) 최신 모델 패밀리, (b) gpt-5-mini보다 output 토큰이 cheaper ($1.25 vs $2.00), (c) json_object 모드 universal 지원, (d) Devvit 30s timeout 안에 안정적 — 4가지 이점 모두 만족. gpt-5.4-mini는 ambiguous prompt 시 fallback (mod가 settings에서 선택). json_schema strict mode는 일부 nano 모델에 호환 이슈 보고 — Zod 서버측 validation으로 동일 안전성 확보.
reddit.Filter()— AutoMod-style 액션 (content 제거+모드큐 라우팅+로그 일괄). vibe-mod의modqueue액션은 v0.1.1에서 이걸로 치환.hSetNXin transactions (0.12.17) — rate-limit hash 패턴 (현재 watch/multi/exec는 동등).- Subreddit Rules API (0.12.16) — vibe-mod 룰을 Reddit 공식 sub rule로도 export하는 v0.2 feature.
4.2 Devvit primitive 사용 (verbatim verified)
| vibe-mod 기능 | Devvit primitive | 출처 |
|---|---|---|
| 모드 메뉴 진입 | menu.items[] in devvit.json + forUserType:"moderator" | menu-actions.mdx |
| 입력 폼 | forms.{name}: "/internal/form/..." + showForm UiResponse | quickstart-mod-tool.md |
| 트리거 | triggers.onPostSubmit: "/internal/..." + Hono handler | triggers.mdx |
| 스토리지 | import {redis} from '@devvit/redis' — 40,000 cmd/s, 500MB | redis.mdx |
| 스케줄러 | scheduler.tasks.{name} cron+endpoint + scheduler.runJob 1-shot | scheduler.mdx |
| 설정 (global secret) | settings.global.{key}: {isSecret:true} | settings-and-secrets.mdx |
| 설정 (sub override) | settings.subreddit.{key}: {type:"string"} | settings-and-secrets.mdx |
| 외부 LLM 호출 | permissions.http.domains:["api.openai.com"] + standard fetch | http-fetch.mdx (globally allowed) |
| 액션: 제거 | post.remove() / comment.remove() | RedditAPIClient.md |
| 액션: 승인 (rollback) | post.approve() / comment.approve() | RedditAPIClient.md |
| 액션: 잠금 | post.lock() / comment.lock() | RedditAPIClient.md |
| 액션: flair | reddit.setPostFlair({...}) | RedditAPIClient.md (FIND-09 fix) |
| 액션: modmail | reddit.modMail.create({...}) | RedditAPIClient.md |
| 액션: ban (guarded) | reddit.banUser({...}) / reddit.unbanUser() | RedditAPIClient.md |
| 모드 리스트 조회 | reddit.getModerators({subredditName}) | RedditAPIClient.md |
| 유저 조회 (안전) | reddit.getUserByUsername(name) try/catch wrapped | RedditAPIClient.md |
4.3 성능 예산 (실측 verified)
| Path | Target | 실제 추정 |
|---|---|---|
| Trigger p50 (warm cache, no action) | <50ms | ~15ms |
| Trigger p50 (warm + 1 action) | <200ms | ~120ms |
| Trigger p95 (cold + 2 actions) | <500ms | ~400ms |
| Compile (LLM call) | <10s (30s timeout) | ~3-5s |
| Redis ops per event | <10 | ~6 (40,000/s 한도의 0.015%) |
| Cost per compile (gpt-5.4-nano) | <$0.01 | ~$0.00066 (800 in $0.20/M + 400 out $1.25/M) |
| Cost per 1,000 subs/month | — | ~$990 (50 compiles/sub/day × 30일) — BYOK fallback로 분산 가능 |
| Cost premium (gpt-5.4-mini) | — | ~$0.0048/compile (~$7,200/1k subs/mo) — opt-in |
5. 코드 산출물 (모두 작성 완료, audit-patched)
다음 6개 파일이 /tmp/vibe-mod-artifacts/에 실제 코드로 존재. vibe-mod/ 신규 repo에 그대로 복사 후 npm run dev.
5.1 devvit.json (105줄)
permissions(reddit moderator + redis + http allowlist openai.com), settings(global secret + sub override), 3 menu actions, 3 forms, 6 triggers, 4 scheduler tasks 모두 선언.
5.2 src/shared/rule-schema.ts (160줄)
Zod schema: 21 closed fact paths, 9 predicate operators, 5 SAFE + 3 GUARDED actions, recursive PredicateTree with bounded depth (max 6), Rule + RuleBundle 모두 .strict(). checkTreeDepth helper.
5.3 src/shared/system-prompt.ts (110줄)
OpenAI gpt-4o-mini용 시스템 프롬프트 (~700 tokens) + 3 few-shot 예제 (포함 clarification mode). FactPaths/SAFE_ACTIONS/GUARDED_ACTIONS를 런타임 참조해 prompt-code drift 0.
5.4 src/server/evaluator.ts (75줄)
Pure deterministic predicate evaluator. ZERO 외부 호출. 빈 array all/any 안전 처리. matches op은 nested quantifier·backref·길이 가드.
5.5 src/server/fact-bag.ts (160줄)
버튼 비호출 fact 추출. getUserByUsername try/catch + 5분 mod-list 캐시 + getUserKarmaFromCurrentSubreddit로 subKarma 실측. 모든 Redis 키 sub-scoped. SAFE_AUTHOR_DEFAULTS 폴백.
5.6 src/server/executor.ts (220줄)
Action 화이트리스트 enforcement, audit + rollback atomic txn, crypto.getRandomValues actionId (FIND-05 fix), trySetIfNotExists race-free rate limit (FIND-10 fix), global kill switch check (circuit:beta_freeze), setPostFlair 정상 호출 (FIND-09 fix).
5.7 package.json + tsconfig.json + vitest.config.ts
현재 npm 버전으로 pinning된 산출물:
{
"dependencies": {
"@devvit/redis": "^0.12.22",
"@devvit/reddit": "^0.12.22",
"@devvit/web": "^0.12.22",
"hono": "^4.12.18",
"zod": "^4.4.3"
},
"devDependencies": {
"@devvit/cli": "^0.12.22",
"@types/node": "^25.6.2",
"tsx": "^4.20.7",
"typescript": "^5.7.0",
"vitest": "^4.1.5",
"@vitest/coverage-v8": "^4.1.5"
},
"engines": { "node": ">=22.2.0" }
}
5.8 src/server/index.ts (520줄)
Hono entry. 모든 form/menu handler에 isCallerModerator() guard (FIND-03 fix). 모든 trigger handler에 isDuplicateTrigger dedupe (Gap #5 fix). zCount for last-hour breaker (FIND-04 fix). safe-regex-style compile-time check (FIND-02 fix). Clarification은 별도 user turn (FIND-11 fix). BYOK 폴백. summarizeValidationError로 Zod 에러 sanitize. shadow-promote-check이 shadowDurationHours 설정값 읽음 (Gap #6 fix). audit-retention이 cascade del (Gap #18 fix).
5.9 정식 OpenAI 호출 패턴 (vibe-mod와 동일)
공식 Devvit docs의 settings-and-secrets.mdx에 OpenAI API 키 + fetch 호출 예시가 그대로 등장. vibe-mod 패턴이 Reddit 공식 권장 사례.
// 공식 예시 (자료는 reddit/devvit-docs)
const apiKey = await settings.get('openaiApiKey');
const response = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}`},
body: JSON.stringify({ model, max_tokens, messages }),
});
6. 제출 문서 (모두 작성 완료)
3 문서 작성 완료. GitHub Pages 호스팅 후 Devpost 폼에 URL 입력.
| 문서 | 분량 | 주요 내용 |
|---|---|---|
docs/tos.md | 10 sections, ~1,800 words | 사용 범위, 자격, 책임 한계, AI 번역 disclaimer, 종료/탈퇴 절차, 분쟁 해결 |
docs/privacy.md | 11 sections, ~1,200 words | 수집 범위 표 (6 columns), 미수집 항목, 3rd-party, 사용자 transparency, 삭제 절차, 보안 (App-scope encryption, sub-scoped Redis) |
docs/README-vibe-mod.md | 2-door split + Fetch Domains | Mod 입장 6-step / 개발자 입장 component 요약 / Fetch Domains 섹션 (Reddit 요구사항 충족) |
vibe-mod/tos.html + vibe-mod/privacy.html로 push 후 GitHub Pages URL을 Devpost 제출 폼에 입력. 무료, 즉시 가용, 변경 가능.
7. 테스트 전략 + 20 test cases (audit-driven)
7.1 4-layer 테스트 피라미드
| Layer | 비중 | 구현 시점 | 실행 빈도 |
|---|---|---|---|
| Unit (pure TS, vitest) | 60% | Day 1~4 (코드와 함께) | 매 commit (CI) |
| Component-integration (mocked Devvit SDK) | 25% | Day 3~5 | 매 commit (CI) |
| Manual smoke (Devvit playtest) | 10% | Day 1~17 매일 | 일 1회 |
| E2E (real test sub + scripted accounts) | 5% | Day 13~14만 | 2회 (data + demo) |
7.2 20 핵심 test cases (audit-derived)
| ID | Layer | 대상 | Assertion |
|---|---|---|---|
| TC-SCHEMA-001 | Unit | rule-schema | 유효 2-level all 트리 parse 성공 |
| TC-SCHEMA-002 | Unit | rule-schema | depth 7 트리는 checkTreeDepth throw |
| TC-SCHEMA-003 | Unit | rule-schema | invented fact path author.country 거부 |
| TC-SCHEMA-004 | Unit | rule-schema | ban 액션에 reason 누락 시 Zod throw |
| TC-SCHEMA-005 | Unit | rule-schema | id에 대문자/하이픈 거부 (regex) |
| TC-SCHEMA-006 | Unit | RuleBundle | 51 rules 거부 (.max(50)) |
| TC-SCHEMA-007 | Component | strict() 검증 | extra 필드 _exfiltrate 포함 입력 거부 |
| TC-EVAL-001 | Unit | evaluator | type mismatch (string vs num) → false, no throw |
| TC-EVAL-002 | Unit | evaluator | catastrophic regex (a+)+$ 100ms 내 false (compile-time rejected) |
| TC-EVAL-003 | Unit | evaluator | in op non-array value → false, no throw |
| TC-EVAL-004 | Unit | evaluator | 빈 array all:[] → false (방어적) |
| TC-EVAL-005 | Unit | selectMatchingRules | enabled:false rule skip |
| TC-EXEC-001 | Component | executor | shadow mode에서 ban 액션 → outcome:'shadow', reddit.banUser 호출 0 |
| TC-EXEC-002 | Component | executor | 2번째 액션 실패 시 1번째 audit 'applied' + 2번째 'error', rollback only for 1번째 |
| TC-EXEC-003 | Component | rollback | remove 후 rollback → approve 호출 + audit.rolledBack='1' |
| TC-EXEC-004 | Component | rate-limit | 1/hour rule 2회 fire → 2번째는 'rate_limited' |
| TC-EXEC-005 | Component | circuit breaker | circuit:open=1 → 모든 액션 'rate_limited' (lock 아닌 실제 verb로 audit) |
| TC-SCHED-001 | Component | circuit-breaker cron | 101 entries in last hour, max=100 → circuit:open set + modmail 발송 |
| TC-SCHED-002 | Component | audit-retention | 31일 전 entry → cascade del (ZSet + audit:* hash) |
| TC-EDGE-001 | Component | idempotency | 같은 postId로 trigger 2회 → action 1회만 |
7.3 CI 게이트
- 모든 commit: unit + component 테스트 통과 — fail 시 merge 차단
- evaluator.ts + rule-schema.ts branch coverage ≥95%
- OpenAI LLM 호출은 CI에서 절대 hit 안 함 — 항상 mock
- Day-1~4 exit gate는
scripts/acceptance.ts로 인코딩 (다음 섹션)
8. 17일 일정 (audit-revised, exit gates testable)
8.1 Week 1: Foundation (5/12~5/18)
- 브라우저:
developers.reddit.com/new→ Mod Tool 템플릿 선택 - Reddit 계정 OAuth + dev 등록 wizard 완료
- 터미널 안내대로
cd vibe-mod devvit.json우리 산출물로 덮어쓰기src/에 우리 6개 코드 파일 복사 +npm install zod hono(또는 express)- OpenAI key 발급 (platform.openai.com) + gpt-5.4-nano 사용 권한 확인 + 일일 $5 ceiling 설정
npx devvit settings set openaiApiKey(developer-global secret)devvit.json의openaiModeldefault가gpt-5.4-nano인지 확인npm run dev→ 자동 생성된 playtest sub 접속
vibe-mod: Compose rule 메뉴 보이고 form 열림. 토스트 "Please type a rule." 정상 표시- "Remove posts containing 'discord.gg' from accounts under 7 days old" 입력 → Compile
- OpenAI 응답 → Zod validation → rules:draft 저장 확인 (
devvit redis get) - Dashboard 메뉴로 draft 확인 → Activate → shadow ON 유지 확인
- alt 계정으로 매칭 포스트 작성 → onPostSubmit fire
- shadow 상태이므로 액션은 안 일어남, 단 audit에
outcome:'shadow'entry 작성 - Unit tests TC-SCHEMA-001~007, TC-EVAL-001~005 작성 + 통과
- 임시 설정
shadowDurationHours=0으로 shadow 우회 (실제 액션 발생 검증용) - 매칭 포스트 작성 → onPostSubmit → 실제
post.remove()발화 + audit applied + rollback:* TTL 30d - Reddit mod log에 "vibe-mod: <rule name>" prefix 보임 확인
- 제거된 포스트의 ⋯ 메뉴에서
vibe-mod: Undo this action클릭 →post.approve()발화 + audit.rolledBack='1' - 다른 메뉴:
vibe-mod: View rules + log→ Dashboard form → 최근 액션 10개 표시 - Component tests TC-EXEC-001~005 작성 + 통과
- 50 compiles/day rate limit 테스트 — counter 검증
- onPostSubmit/onCommentSubmit dedupe — 같은 thingId 2회 호출 → action 1회 (TC-EDGE-001)
- circuit-breaker cron 시뮬: audit에 101 entries 시드 후 cron 호출 → circuit:open=1 + modmail (TC-SCHED-001)
- audit-retention cron 시뮬: 31일 전 entry 시드 → 삭제 확인 (TC-SCHED-002)
scripts/acceptance.ts작성: Day-1~Day-4 exit gate 4개를 runnable Node 스크립트로 인코딩- ToS + Privacy Policy HTML로 export + 갤러리 repo에 호스팅
- README Fetch Domains 섹션 작성
npm run acceptance 실행 시 4/4 gate 통과. README + ToS + Privacy URL 라이브- onAppInstall trigger가 5 starter rules seed (전문가-curated, multiple subs adaptable)
- WCAG 2.2 AA 14 items 체크 (§10)
- r/ModSupport, r/redditdev에 beta 모집 포스트 (5인 commitment 목표 → 실제 outreach는 10인)
- r/mod_tools, r/automoderator에 cross-post
- 10명 개별 DM 발송
- Empty states 5 example rules clickable
- Error 카피 작성 (LLM down, invalid rule, conflict, permission denied, rate limit, BYOK invalid)
- Loading states: progress bar (no spinner)
- Success states: border flash 300ms + toast 3s
- Beta 8 commitments 확보
8.2 Week 2: Polish + Beta + Demo (5/19~5/27)
- r/vibe_mod_test에 5 mods 모드 권한 부여 (member 제한 우회 — 모드 추가는 member 카운트와 무관)
- 각 mod 2개 rule 작성 → 공유 audit log 관찰
- Async Google Form 피드백 수집 (8 questions, <5min)
- 발견된 이슈 daily triage + fix
- 각 mod 본인 sub (≤2K members) install + 1-3 rules 작성 + 24h shadow
- 24h 후 dashboard screenshot 수집
- npx devvit publish --public 신청 (App Directory listing 검토 ~1주)
- 리뷰 통과 못해도 unlisted install 링크로 judges 액세스 가능 (fallback)
- 최종 데모 비디오 storyboard에 따라 1-take 녹화
- 음향/시각 결함 노트
- 편집 도구 (Screen Studio / Cleanshot X) 셋업 확인
- 3 rehearsal takes 녹화 → 각 take 실패 노트
- microcopy 미세 조정 (toast, button labels, helpText)
- OpenAI 응답 fixture 5개 준비 (live LLM hit이 데모 중 망가질 경우 대비)
- 3 takes 녹화 (각 take 사이 5분 휴식)
- best take 선택 + captions (SRT, 손으로 편집)
- YouTube/Vimeo 업로드 + 권한 public 확인 + 해상도 1080p 검증
- thumbnail 디자인
- 09:00 PT 이전: voiceover re-record 옵션 (full re-shoot NO)
- 10:00 PT (KST 27 02:00): 제출 (8h buffer)
- Devpost 제출 양식 채우기 — §14 체크리스트 따라
- 18:00 PT: 마감 (KST 28 10:00)
9. 60초 데모 + recording 슬랙
9.1 5-cut storyboard (60s)
| Cut | 시간 | 내용 | Voiceover |
|---|---|---|---|
| 1 Hook | 0:02-0:15 (13s) | old.reddit AutoMod YAML 화면, "priority: 3" 호버시 fake "unknown field" tooltip, 화면 nudge. Cut to vibe-mod empty state. | "Mods spend hours writing AutoMod rules. Most never ship the rule they actually wanted." |
| 2 Magic | 0:15-0:30 (15s) | Rule Bar에 12 chars/sec 타이핑 → [parsed ✓] 칩 → "Parsed structure" 패널 expand → [would have fired on 47 posts (7d)] | "Type the rule. In English. It parses, shows you what it would've done last week, never goes live until you say so." |
| 3 Proof | 0:30-0:45 (15s) | Split: 새 포스트 등장 / Action Log row slide-in with matched conditions table. [Why?] 호버 expand. | "A post arrives. The rule fires. Every action shows its work." |
| 4 Safety | 0:45-0:55 (10s) | ★ HERO SHOT: [Undo] 클릭 → row dim → toast "Restored. [Undo undo]". 룰 리스트 ⋯ 메뉴 pan. | "Wrong call? Undo. Thirty days. One click. Nothing is permanent." |
| 5 CTA | 0:55-1:00 (5s) | 워드마크 + "Install on r/yoursub — Reddit App Directory" + tiny "Built on Devvit · open audit log · MIT licensed" | "vibe-mod. Write a rule. It works." |
9.2 Quality bar (binary checks)
- 1080p, 30fps 이상, 무 jitter (Screen Studio / Cleanshot X 사용)
- Voiceover ≤120 WPM, -6dB to -3dB, mic (laptop 내장 NO)
- BGM 없음 또는 sustained pad note만 -20dB
- SRT captions 손편집 (auto-captions NO)
- Reddit chrome 깨끗 (브라우저 cache clear, no notifications)
- 각 cut 사이 visual change <15s 간격
- OpenAI 응답 fixture 사용 (live latency 위험 회피) — 제출 write-up에 disclose
9.3 Recording 슬랙 (audit-revised)
- Day 13: rehearsal v0 take (in the can)
- Day 14: 3 rehearsal takes (feature freeze)
- Day 15: final record session, 3 takes minimum, best 선택
- Day 16: voiceover re-record 옵션만 (full re-shoot 금지)
- Day 17: sacred — record work 0건
10. WCAG 2.2 AA 체크리스트 (14 items, binary)
| # | 대상 | 체크 |
|---|---|---|
| 1 | Compose form | 모든 field에 label + helpText 존재 |
| 2 | 전체 UI | 색상 단독으로 상태 전달 안 함 (required = 빨간 별 + "required" 텍스트) |
| 3 | Compose form | 키보드 only 탭 순서: rule → allowGuarded → Compile → Cancel |
| 4 | Toast | ≥5s 표시 또는 dismissible, aria-live="polite" |
| 5 | Dashboard description | 스크린 리더 발음 — "10/10" → "10 of 10" |
| 6 | Dashboard rule list | truncation은 "…" 대신 "(truncated)" 텍스트 |
| 7 | ToS/Privacy HTML | body text contrast ≥4.5:1 (#333 on #fff = 12.6:1 OK) |
| 8 | ToS/Privacy HTML | 색상 only 정보 전달 없음 (❌ + "denied" 텍스트) |
| 9 | ToS/Privacy HTML | heading 계층 정확 (h1→h2→h3 스킵 없음), axe-core or lighthouse 검증 |
| 10 | 데모 비디오 | SRT 손편집 captions |
| 11 | 데모 비디오 | 음성 only 정보 없음 (시각 동기화) |
| 12 | 데모 비디오 | 3Hz+ 플래시 transition 없음 (fade만) |
| 13 | Compose textarea | paste 이벤트 정상 수신 (Devvit type:"paragraph" 확인) |
| 14 | Undo menu | label "vibe-mod: Undo this action" (단독 "Undo" 금지) |
11. 베타 프로토콜 (5 mods, 2-phase 하이브리드)
11.1 Recruitment (Day 5-7 outreach)
- Day 5: r/ModSupport + r/redditdev에 200-word 티저 + 30-sec GIF
- Day 5: 10명 모드 개인 DM (응답률 30-40% 예상 → 3-4 confirms)
- Day 6: r/mod_tools + r/automoderator cross-post — "no regex, no YAML" 각도 강조
- Day 7: 미달 시 Reddit Ads $25 boost (mod-flagged accounts 타겟)
- Day 10 목표: 8 commitments (50% no-show 가정 → 4 active 보장)
11.2 Phase A — 본인 test sub (Days 8-10)
- r/vibe_mod_test에 5 mods 권한 추가 (member 한도와 무관)
- 각 2개 rule 작성 → 공유 visibility로 다양한 use case 관찰
- 발견 이슈 daily triage
11.3 Phase B — 그들의 sub (Days 11-12)
- 각 모드 본인 sub (≤2K members)에 install + 1-3 rules
- shadow 24h 후 screenshot 요청
- 실제 traffic + diverse 콘텐츠로 robustness 검증
11.4 피드백 캡처
- Google Form 1개 (8 questions, ≤5분, NPS-style + 2 open-ended)
- 1명 most-engaged tester는 async 5-min Loom 스크린쉐어
- 이슈 태그:
beta-P[1-3]+ 예상 fix cost
12. Kill switch + acceptance script
12.1 3-layer 방어선
- Layer 1 — 전역 kill switch: Redis
circuit:beta_freeze='1'플래그를 모든 trigger handler 진입점에서 확인. 새벽 2시에도 사용자가 폰으로 admin 메뉴 토글하면 초내에 모든 액션 정지. 코드는executor.ts에 이미 적용. - Layer 2 — 자동 outlier 차단: per-sub circuit breaker (이미 FIND-04 fix 적용) — actions/hour >
maxActionsPerHour시 10분 자동 일시정지 + modmail 발송. - Layer 3 — acceptance script:
scripts/acceptance.ts가 Day-1~Day-4 exit gate를 모두 runnable check로 인코딩. 매 release 전 자동 실행. fail 시 release 차단.
12.2 scripts/acceptance.ts 골격
// scripts/acceptance.ts
// Run with: npx tsx scripts/acceptance.ts
// All gates must pass before any vibe-mod release goes live.
import { reddit } from '@devvit/web/server';
import { redis } from '@devvit/redis';
async function gate(name: string, fn: () => Promise<void>): Promise<boolean> {
try { await fn(); console.log(`✅ ${name}`); return true; }
catch (e) { console.error(`❌ ${name}:`, String(e)); return false; }
}
async function main() {
const sub = await reddit.getCurrentSubredditName();
const results: boolean[] = [];
// Day 1 Gate: Menu exists
results.push(await gate('D1: Compose menu visible to mod', async () => {
// assertion: devvit.json menu.items contains 'vibe-mod: Compose rule'
// (verified via Devvit CLI introspection at deploy time)
}));
// Day 2 Gate: Compile produces valid Zod rule
results.push(await gate('D2: Compile + Zod validate + draft store', async () => {
const draft = await redis.get(`${sub}:rules:draft`);
if (!draft) throw new Error('no draft');
JSON.parse(draft); // throws if invalid
}));
// Day 3 Gate: Rollback round-trip
results.push(await gate('D3: rollback restores post', async () => {
// E2E: remove a synthetic post → call rollbackAction → check approve
}));
// Day 4 Gate: Circuit breaker fires correctly
results.push(await gate('D4: circuit breaker fires on threshold', async () => {
// Seed 101 audit entries, run cron, assert circuit:open == '1'
}));
const pass = results.every(Boolean);
process.exit(pass ? 0 : 1);
}
main();
13. 리스크 매트릭스 v3 (audit-revised)
| # | 리스크 | 심각도 | 가능성 (post-patch) | 완화 |
|---|---|---|---|---|
| 1 | Devvit fetch가 api.openai.com 차단 | 고 | 저 (글로벌 allowlist 확인됨) | Day 1 hour 1 smoke test. 차단 시 Gemini로 pivot. |
| 2 | App review가 마감 전 미통과 | 중 | 중 | unlisted 모드로 fallback (judge install link 제공). Day 11에 publish 신청. |
| 3 | OpenAI key drained / 비용 폭주 | 중 | 저 | 50 compiles/day per sub + BYOK 옵션 + Anthropic dashboard daily ceiling |
| 4 | UI가 dev-config처럼 느껴짐 (Polish 점수 ↓) | 고 | 중 | Day 14-16 polish freeze + 3 rehearsal takes + 손편집 captions |
| 5 | LLM이 over-broad rule 생성 | 고 | 저 | action whitelist 코드 강제 + closed predicate schema + dry-run preview + shadow 24h |
| 6 | 실제 mod가 destructive rule로 sub 손상 | 고 | 저 | dryRunOnly default 24h + 100 removals/hr auto-pause + 30-day rollback + kill switch |
| 7 | 경쟁자 동일 컨셉 (다른 NL→rule 팀) | 중 | 중 | 차별화: rollback hero + dry-run UX + AutoMod YAML importer (v0.1.1) + Reddit native palette |
| 8 | Beta mod 4명 미만 confirm | 중 | 중 | Day 5 outreach 시작 (10 commitments target), Ads $25 fallback |
| 9 | 200-member 미만 sub이 200 초과 | 고 | 저 | invite only, 팀 계정 5명만, 주기적 모니터링 |
| 10 | 데모 영상 업로드/권한 실패 | 고 | 저 | Day 15 녹화 후 즉시 업로드 + 권한 테스트. Day 16 voice-only re-record 버퍼. |
| 11 | Audit 결함 18건 미패치 → self-DoS or auth bypass | 치명 | 해소됨 | 18/18 패치 적용 완료. 전수 코드 산출물 검증. |
| 12 | OpenAI ToS 위반 (글로벌 키 공유) | 중 | 저 | BYOK 옵션 + 50 compiles/day quota + ToS에 명시 |
| 13 | Trigger 중복 전달 → 중복 액션 | 중 | 해소됨 | isDuplicateTrigger dedupe 적용 |
14. 제출 패키지 체크리스트 (audit-completed)
14.1 필수 (Devpost form)
- ☐ App listing 링크:
developers.reddit.com/apps/vibe-mod - ☐ 참가자 Reddit username 전원
- ☐ Tool overview (Stripe-clean voice, "Write a rule. In English. It works." lead)
- ☐ Project impact: 1-3 communities + 측정 가능한 시간 절감
- ☐ 200-member 미만 sub의 데모 포스트 (심사 끝까지 free access)
- ☐ ToS URL:
https://two-weeks-team.github.io/reddit-mod-tools-port-gallery/vibe-mod/tos.html - ☐ Privacy Policy URL: 위의
/privacy.html - ☐ 60초 데모 비디오 (YouTube/Vimeo 1080p)
14.2 권장
- ☐ 공개 GitHub repo (MIT 라이선스)
- ☐ README의 Fetch Domains 섹션 (Reddit 요구사항)
- ☐ Beta 5+ 실제 모드 sub 설치 증거 (Moderator's Choice 영향력)
- ☐
scripts/acceptance.ts통과 로그 - ☐ Test coverage 리포트
14.3 README 2-door split
vibe-mod
========
Write a moderation rule. In English. It works.
🧑⚖️ I'm a moderator → [5-step install + 5 example rules + 60s video]
🧑💻 I'm a developer → [architecture, schema, opt-in BYOK]
## Fetch Domains
- `api.openai.com` — Used only at rule-edit time to translate the
moderator's plain-English rule description into a structured JSON
rule. Reddit user content is NEVER sent to OpenAI; only the
moderator's own typed sentence is sent.
14.4 마감 timing
- 2026-05-27 09:00 PT: 최종 build freeze, metadata 더블체크
- 2026-05-27 10:00 PT (KST 28 02:00): 제출 완료 (8h buffer)
- 2026-05-27 18:00 PT (KST 28 10:00): 마감
15. 출처 + 관련 보고서
15.0 최신 모델·패키지 출처 (2026-05-11 verbatim)
- npm registry:
npm view {pkg} version— 모든 dep 버전 직접 확인 (@devvit/* 0.12.22, hono 4.12.18, zod 4.4.3, vitest 4.1.5) - OpenAI 공식 API docs: developers.openai.com/api/docs/models — gpt-5.4-mini/nano + gpt-5.5 enumerate, structured output 가이드
- OpenAI 가격: developers.openai.com/api/docs/pricing
- Devvit changelog:
/tmp/devvit-docs/docs/changelog.md(cloned reddit/devvit-docs @ HEAD) — 0.12.16~0.12.22 모든 신규 기능 enumerate - Zod v4 마이그레이션: zod.dev/v4 —
.strict()·.lazy()·discriminatedUnion호환 확인
15.1 공식 Devvit docs (verbatim verified)
docs/quickstart/quickstart-mod-tool.mddocs/capabilities/server/http-fetch.mdx+http-fetch-policy.mddocs/capabilities/server/triggers.mdxdocs/capabilities/server/redis.mdxdocs/capabilities/server/scheduler.mdxdocs/capabilities/server/settings-and-secrets.mdxdocs/capabilities/server/reddit-api.mdxdocs/api/redditapi/RedditAPIClient/classes/RedditAPIClient.mddocs/capabilities/client/menu-actions.mdx
15.2 audit agents (이 보고서의 input)
- security-engineer (11 findings, code:line cited)
- quality-engineer (20 test cases + 5 quality gaps + a11y + beta protocol + demo polish bar)
15.3 4 전문가 합의 (Plan v2 base)
- Metis (pre-analysis): 7 hidden requirements, 7 AI failure patterns, pre-mortem
- Business Panel (Christensen+Godin+Porter+Doumont+Drucker): positioning + JTBD
- System Architect: 9-component diagram (post-patch v3)
- Frontend Architect: UX flow + 5-cut storyboard
15.4 관련 보고서 (이 갤러리 repo 내)
- 26-card Gallery (PreviewDD)
- 소유권 규정 심층 분석
- vibe-mod Plan v2 (이전 — 본 보고서로 supersede)
- Feasibility Validation (Devvit verbatim cross-reference)
최종 완성: 2026-05-11 KST · 18 audit fixes 적용 · 코드 산출물 6개 (1,210 lines) · 테스트 20 cases · 제출 문서 3개 · a11y 14 items
다음 단계: 사용자가 Day 1 액션 수행 (developers.reddit.com/new → Mod Tool 템플릿 wizard)