이 페이지는 제품 화면을 바꾸라는 문서가 아닙니다. 로그인 UI, API 키, 과금 ID만 Daven 쪽으로 옮기고, 학습 흐름과 브랜드·카피는 앱이 그대로 가집니다.
한 줄
앱이 학습 UX와 결과물을 소유한다. Hub가 사람과 교실을 소유한다. Daven이 AI 실행과 학교 지갑을 소유한다. 브라우저는 키와 payer를 모른다.세 층
@davenai/sdk/edu(브라우저)와 @davenai/sdk/server(BFF)가 두 층을 연결합니다. 앱 코드에 login()을 만들지 마세요. 공개 이름은 session입니다.
불변 조건
아래를 깨면 다른 앱으로 복제하지 마세요.- Hub 시작은
{hostedUrl}?session=&proxy=&locale=를 새 탭으로 연다.locale은 Hub 경로와 같다 (en|kr). 앱 라우트가ko이면kr을ko로 받는다. 교실 기본 실행을 대시보드 iframe으로 두지 않는다. - 학생·교사는 Hub에서만 로그인한다. 앱에 Daven / Google / 자체 학생 코드 로그인을 Edu 기본 흐름으로 두지 않는다.
- 브라우저와 공개 번들에 Daven 키, MCP 키,
OPENAI_API_KEY, 학교 payer ID를 넣지 않는다. - 클라이언트가
studentId,schoolId,classroomId, payer를 고르지 않는다.GET /api/session/context만 믿는다. - Edu 런타임의 AI는 proxy만 탄다. 세션이 없거나 만료되면
EDU_RELAUNCH_REQUIRED로 닫고, 운영자 provider 키로 fallback하지 않는다. - 사용자 동작마다 안정적인
operationId를 한 번 만든다. 재시도는 그 ID에서 결정적으로 파생한다. 본문이 다른 요청에 같은 멱등키를 쓰지 않는다. - 앱 안 “저장 / 공개 / 다운로드”와 Hub 최종 제출은 다른 버튼이다. 제출
externalArtifactId는 앱 작품 ID다. 같은 ID 재제출만 기존 Hub 행을 갱신한다. shouldRelaunchEducationApp(error)가 true면 작업은 유지하고 Hub에서 다시 열라고 안내한다. 자동 로그인 갱신은 기본값이 아니다.- 18세 미만 학생 경로에 Gemini 등 약관상 막힌 provider를 쓰지 않는다.
- 도메인 프롬프트·검증·화면은 앱에 남긴다. SDK로 옮기는 것은 transport뿐이다.
- 탭 파비콘은 Classroom Hub 마크
/brand/classroom-logo.png다. 탭 제목은 앱 이름이다. Next / Vercel 기본 아이콘을 남기지 않는다.
실행 모드
이미 운영 중인 앱은 한 플래그로 경로만 갈라야 합니다. 기본값을 운영 중에 바꾸지 마세요.
모드가 하나뿐이어도 코드에는 이 이름을 쓰세요. 나중에 다른 앱이 같은 플래그를 찾습니다.
로컬·Playground는 개발자 계정이 냅니다. 교실은 학교 관리형 지갑이 냅니다. 두 경로를 한 요청에서 동시에 차감하지 마세요.
책임 경계
- 앱이 유지
- Hub가 가져감
- Daven이 가져감
- 제품 이름, 카피, 학습 루프
- 도메인 프롬프트와 결과 검증
- 초안·작품·진행 상태 (앱 DB 또는 브라우저 저장)
- 앱 고유 교사 도구 (콘텐츠 검수, 진행 보드)
- 내보내기 포맷 (PDF, PPTX, ZIP)
edu_user_id를 기존 user_id / daven_id 컬럼에 넣지 않습니다.
최소 코드
브라우저:session + proxy로 @davenai/sdk/server를 만듭니다. SDK에 payer ID를 넘기지 않습니다.
도메인 route는 gateway만 봅니다.
인증 브리지
앱에 이미 사용자 테이블과 RLS가 있으면 Reading Passport처럼 합니다.1
Launch
Hub가
{hostedUrl}?session=&proxy=를 새 탭으로 엽니다. SDK가 query를 헤더로 옮기고 주소에서 지웁니다.2
Bootstrap
앱 서버가
GET {proxy}/api/session/context로 서명 세션을 재검증합니다. JWT를 클라이언트가 디코드해 믿지 않습니다.3
로컬 principal
edu_actor_id → 결정적 앱 내부 ID. 기존 FK/RLS를 깨지 않으려면 로그인 불가 로컬 principal만 발급합니다. 이것은 별도 계정이 아닙니다.4
쿠키
session과 proxy는 HttpOnly. 현재 요청의 Edu
actorId와 서버가 가진 edu_actor_id는 항상 같습니다.actorId를 런타임 키로만 쓰고, 초안은 브라우저에 둬도 됩니다. Hub 제출 URL만 영구 HTTPS여야 합니다.
이름·학생 코드·이메일로 Edu 사용자를 합치지 마세요. 매칭 키는 actorId뿐입니다.
teacher_manage는 학생으로 impersonation하지 않습니다. 교사 전용 화면이 있을 때만 카탈로그에 supportsTeacherManage를 켭니다.
AI 어댑터
1
경계 하나
모든 생성은 gateway를 통과합니다. route에서 OpenAI/Gemini SDK를 직접 부르지 않습니다.
2
런타임 확정
요청 앞에서 Edu인지 legacy인지 정합니다.
after() / worker에도 그 런타임을 명시적으로 넘깁니다. 백그라운드에서 쿠키가 사라져도 Edu 작업이 운영자 키로 내려가면 안 됩니다.3
견적 → 생성 → 폴링
이미지는
quote → media/create → media/get. 텍스트는 text/create. 고정 크레딧과 다른 숫자를 UI에 지어내지 않습니다.4
실패
provider가 명시적으로 실패하면 예약을 해제합니다. 세션 만료는 저장본을 유지하고 재실행을 안내합니다.
제출
같은 작품을 다시 내면 그 행만 갱신합니다. 다른 ID는 새 행입니다. 생략하면
__default__ 한 줄이 됩니다.
제출 전에 생성 이미지를 만료되지 않는 저장소로 복사하세요. 7일 cleanup 버킷 URL을 primaryUrl에 넣지 마세요.
호스트와 탭 아이콘
다음 앱에도 그대로 복사합니다.- 공개 URL은
https://{slug}.edu-app.daven.ai입니다. DNS는*.edu-app.daven.ai→cname.vercel-dns.com한 장입니다. 앱을*.edu.daven.ai에 두지 마세요. 그 존은 Hub /api/dev입니다. - Vercel 프로젝트에
{slug}.edu-app.daven.ai만 추가합니다. 와일드카드 DNS는 이미 있습니다. - 탭 파비콘은 Classroom Hub 마크입니다. Hub 파일
dv-edu/apps/web/public/brand/classroom-logo.png를public/brand/classroom-logo.png로 복사하고, metadataicons.icon/icons.apple에 넣습니다. - 탭 제목은 앱 이름입니다. 예: Image Studio. 파비콘은 Edu입니다. 앱 자체 로고, Vibe, Next/Vercel 기본 아이콘을 탭에 쓰지 마세요.
- 운영 Vercel에는
DAVEN_API_KEY를 넣지 않습니다. 교실은 Hub session만 씁니다. 키는 로컬next dev와 Playground 개발 모드 전용입니다. - Playground Submit → Master Approve.
appSlug,appName,hostedUrl을 맞춥니다. 교사 전용 화면이 없으면supportsTeacherManage를 끕니다. - 카탈로그 Approve 전에 학생 세션으로 텍스트 1번, 이미지 1번이 실제로 나와야 합니다. 래핑·배포·등록을 한 번에 끝내면 아래 오류가 교실에서 처음 드러납니다.
Image Studio에서 깨진 것
한 번에 올리면 같은 순서로 다시 깨집니다. 다음 앱은 여기부터 막으세요.견적 VALIDATION_ERROR — slug or code is required
견적 VALIDATION_ERROR — slug or code is required
Hub
POST /api/quote body는 { "media_type": "image" | "text" }뿐입니다. 이걸 daven.ai/api/mcp/quote로 넘기면 PHP가 slug/code를 요구하고 VALIDATION_ERROR 422를 줍니다. 앱은 그걸 “Could not make the picture”로 보여 줍니다.Hub quote는 학교 풀 고정 크레딧을 로컬에서 돌려야 합니다. 이미지 10, 텍스트 1, billing: school-pool. Daven MCP 모델 견적이 아닙니다.견적은 숫자를 보여줄 뿐입니다. 지금 운영 차감은 text/create / media/create가 2xx일 때 학교 remaining_credits에서 됩니다. 견적 422로 생성을 멈추지 마세요. Reading Passport는 견적을 건너뜁니다.모델 생략 = nano-banana-2 (학생 Gemini)
모델 생략 = nano-banana-2 (학생 Gemini)
media/create에 model을 안 넣으면 Daven /image/create 워크플로 기본값이 nano-banana-2 입니다. 문서의 gpt-image-2가 자동으로 오지 않습니다.교실 기본 모델은 gpt-image-2.5 입니다. 앱과 Hub 둘 다 이 값을 넣으세요. 학생 경로에 Gemini·nano-banana를 쓰지 마세요.gpt-image-2.5 quality는 fast | quality입니다. medium은 클램프되어 fast가 됩니다. Hub는 quality를 /image/create로 넘겨야 합니다.설명 다듬기 EMPTY_RESULT
설명 다듬기 EMPTY_RESULT
text/create가 200인데 data.text가 ""이면 앱은 EMPTY_RESULT 502를 냅니다. gpt-5-mini + reasoning_effort: minimal + 작은 max_output_tokens(예: 900)면 추론에 토큰을 다 씁니다.교실 텍스트는 gpt-5.6-luna, 출력 토큰 1600 이상, reasoning을 기본으로 넣지 마세요. 빈 본문이면 파생 operationId로 한 번 더 치세요. 200 + 빈 텍스트는 실패로 취급하세요.그림은 나왔는데 5MB에서 버림
그림은 나왔는데 5MB에서 버림
Daven 결과 PNG는 5MB를 넘는 경우가 많습니다. 생성·과금 뒤에
EDU_IMAGE_TOO_LARGE로 버리면 학생은 502만 봅니다. 다운로드 한도는 약 25MB로 두거나, 받은 뒤 압축하세요.제출 Too Large — 페이지 data URL을 한 번에 POST
제출 Too Large — 페이지 data URL을 한 번에 POST
완성 페이지 PNG를 data URL로 모아
POST /api/studio/submit 하면 Vercel 함수 본문 한도(약 4.5MB)에 걸립니다. 학생은 413 FUNCTION_PAYLOAD_TOO_LARGE 또는 “Too Large”를 봅니다.페이지는 Hub upload-url로 한 장씩 올린 뒤, 제출 본문에는 HTTPS primaryUrl + assets[]만 넣으세요. 브라우저가 storage에 PUT하고, CORS가 막히면 한 장짜리 JPEG BFF로만 우회합니다. Hub 교사 제출물 탭이 그 URL을 엽니다.운영 키와 Vercel 함정
운영 키와 Vercel 함정
운영 Vercel에
DAVEN_API_KEY / OPENAI_API_KEY를 넣지 마세요. 교실은 Hub session만 씁니다. 로컬 next dev + 개발자 키가 된다고 학생 그림이 되는 게 아닙니다.프로젝트 Framework는 Next.js여야 합니다. Other면 첫 배포가 404입니다. 도메인은 {slug}.edu-app.daven.ai만 붙입니다.motion.button에 Framer onDrag가 남으면 React 19 타입 때문에 프로덕션 빌드가 깨집니다. 드래그 props를 빼세요.session JWT를 쿠키에 그대로 넣으면 EDU_SESSION_TOO_LARGE가 납니다. 쿠키는 짧게, 본문은 서버에서만 읽으세요.단계 (다른 앱에 그대로 복사)
1
0. 계약
expectedAppSlug, session v2 purpose, OpenAPI, 크레딧(text 1 / image 10)을 확인합니다.2
1. 인증
/edu/bootstrap 또는 동등한 서버 진입. 로그인 화면을 Edu 모드에서 숨깁니다. 관리자 전용 로그인이 있으면 교실 역할과 분리해 유지해도 됩니다.3
2. 비-AI 루프
launch → 저장 → 닫기 → 다시 열기. AI 없이 데이터가 남는지 먼저 확인합니다.
4
3. 텍스트 AI
낮은 위험 호출부터 gateway로 옮깁니다. 그다음 structured output.
5
4. 이미지 AI
model: gpt-image-2.5를 명시합니다. reference, 비율, job polling. 학생 경로에 Gemini / nano-banana 금지.6
5. 학생 스모크
Hub Start로 텍스트 1번, 이미지 1번.
VALIDATION_ERROR / EMPTY_RESULT / 5MB 폐기가 없는 뒤에만 카탈로그 Approve를 합니다.7
6. 제출
영구 URL + 안정
externalArtifactId + 명시 버튼. 자동 제출 없음.- 네트워크 탭에
daven.ai/app.daven.ai브라우저 호출이 없다. - 키 없이 같은 generate를 치면 401 또는 relaunch이지, 운영자 키가 끼어들지 않는다.
- 세션을 지워도 초안이 남고, AI만 막힌다.
- 교사 preview 세션으로는 제출이 거절된다.
하지 말 것
- Hub를 앱 안에 iframe으로 넣거나, 앱을 Hub 대시보드 iframe이 기본이 되게 하기
- Daven 마케팅 화면, 프로필, 결제,
auth.daven.ai를 학생에게 보여 주기 - 앱을 SEED Classroom Hub처럼 다시 그리기 — Hub만 SEED다
- Vibe 워드마크·Braid 그라디언트를 교육앱 브랜드로 쓰기
- Next / Vercel 기본 파비콘을 탭에 남기기 — Edu
classroom-logo.png를 씁니다 - “플랫폼 / 솔루션 / AI 키”를 학생 버튼에 쓰기
