English {#english}
Purpose
Single planning document for:
- Where tenant signup and orders are stored, how the control plane decides placement, and how provisioning runs through Pulumi (optional), Ansible (Docker / Frappe bench), Zuplo sync, Cloudflare DNS/KV, and
POST /internal/provision-complete. - Logpath: correlation IDs and which stores hold provisioning vs runtime traffic logs.
- Admin console: unify internal HTML dashboards and simulators under
prego-control-planebehindINTERNAL_API_KEY.
Canonical locations (repos)
| Area | Repository / store |
|---|---|
| Tenant master, jobs, traces (D1) | prego-control-plane |
| Product UI | Prego monorepo (apps/client-web, etc.) |
| Images (Docker Hub) | prego-docker (e.g. iamfork/prego-repo) |
| Instance provisioning | prego-ansible |
| Gateway secrets / routing | prego-zuplo |
| Infra | prego-pulumi |
Related docs {#onboarding-m-related-docs}
- m.pregoi.com admin-web UX implementation (OTP, mail); m company profile · footer/cookie (Next app and root
*.html: no cookie banner, no global footer strip); m onboarding step connector (step rail lines meet circles) - m.pregoi.com plans A–D: Stripe Pay & checkout · Funnel observability · Logpath full flow · Activate readiness & portals
- API Control Plane implementation plan
- Provision tenant workflow (5 steps)
- Tenant provisioning flow
- Tenant subdomain DNS design
- Tenant onboard resource allocation
- Resource observability gaps (sibling doc in
prego-control-plane):docs/resource-observability-and-cost-planning.md
Full step-by-step and Korean detail: see 한국어 below. Korean mirror of the m admin-web links in Related docs: here.
Implementation status (prego-control-plane)
- Unified dispatch:
POST /v1/tenantstriggers the same queue / GitHubworkflow_dispatchpath as the Stripe webhook. The 202 body includestrace_id(and on idempotent replay when stored) so callers can sendx-trace-idon gateway traffic and match Zuplo logs toGET /internal/trace/:trace_id. trace_idin queue + workflow:ProvisionQueueMessage.trace_idis forwarded as aworkflow_dispatchinput;provision-tenant.ymlpostspipeline_started,resolve_server_ok,tenant_dns_finished,ansible_provision_ok,zuplo_sync_okwhentrace_idis set (best-effortcurl).- Failed pipeline callback:
callback-failedpostsPOST /internal/provision-completewithstatus: Failed,error_message, andfailure_stage(not the non-existent/internal/provision-failed). - Audit: D1
allocation_decisions,trace_events(order_accepted, pipeline stages viaPOST /internal/trace-events,provision_completed/provision_failedon callback). - Console:
GET /internal/console(HTML),GET /internal/console/snapshot,GET /internal/allocation-decisions,GET /internal/tenants-directory,GET /internal/provision-jobs(list items includeorigin_urland optionalorigin_url_with_trace, same logic asGET /v1/jobs/:id). The console page surfaces quick-open links (prefersorigin_url_with_trace) beside the jobs JSON. Bearer in the browser is kept insessionStorageundercp_internal_key, shared with onboarding/metrics/AI-flow/RAG/placement-simulation/resource-pool-simulation/email-flow-dashboard internal pages (metrics also keeps legacyprego_dashboard_apikey). If the token field is cleared, Load snapshot… still uses storedcp_internal_keywhen present. - Billing test:
GET /internal/billing-testserves an HTML shell (no Bearer on document load); the table is loaded withGET /internal/billing-test-dataand the samecp_internal_key/ Bearer pattern as other internal UIs. Scripts may callbilling-test-datadirectly withAuthorization: Bearer. - Metrics dashboard:
GET /internal/dashboardprefills the key field fromcp_internal_keyor legacyprego_dashboard_apikeywhen the input is empty. - Shared nav strip:
internal-tools-nav-html.ts(INTERNAL_TOOLS_NAV_HTML) is embedded under the title on/internal/consoleand every other internal tool page; add new destinations there. Legacy HTML tools are also listed in prego-control-planesrc/cp-legacy-tools.ts(navStripHref,internalPath) — keep both in sync; Vitestcp-legacy-nav-coverage.test.tsassertsnavStripHrefappears in the strip. - Smoke script: Prego
scripts/smoke-verify-control-plane.shloopsPUBLIC_HTML:/internal/console,/internal/dashboard,/internal/onboarding-dashboard,/internal/placement-simulation,/internal/resource-pool-simulation,/internal/email-flow-dashboard,/internal/billing-test(200 without Bearer on each document GET), thenPUBLIC_CP_SPA(React bundle shells — canonical list prego-control-planesrc/cp-spa-smoke-paths.ts), thenGET /internal/billing-test-data(Bearer), thenGET /internal/trial-access-review/requests?page=1&pageSize=1(Bearer, manual trial approval list), plus R5/R7/R1. Add new public operator HTML paths there, ininternal-tools-nav-html.ts, and (legacy HTML tools) in prego-control-planesrc/cp-legacy-tools.ts. AI flow / RAG operator HTML lives on prego_aiprego-ai-operator(not CPPUBLIC_HTML). - Onboarding status:
GET /internal/onboarding-statusreturns the same additivetrace_id,origin_url,origin_url_with_tracefields when the job or tenant row resolves;GET /internal/onboarding-dashboard(HTML) shows a tenant open link usingorigin_url_with_tracewhen present. - Gateway logs (
prego-zuplo): Frappe forward, Assistant UI, demo, External/Agent API, Google OAuth errors, prompt filter, MCP (SSE + OAuth discovery/callback/token), email OTP, billing (e.g. Stripe portal errors), rate-limit and quota policies (Upstash Redis helper warnings; explicit warn on 429 denials for general rate limit, AI rate limit, and quota), and related handlers attachtenant_id(fromX-Tenant-Idwhen present),prego_company_id(path routing key), andcf_ray/http_request_id/ optionaltrace_id(fromx-trace-idwhen the client forwards the provisioning trace);cf_rayalso falls back to the Workersrequest.cfray id when thecf-rayheader is missing — viagateway-runtime-log-fields.tsfor Workers analytics / export. - Admin onboarding (
apps/admin-web): Persiststrace_idfrom free-tenant signup, sendsx-trace-idonGET /v1/jobs/:id, optionalprego_provision_*sessionStorage resume after refresh, and Open tenant usesorigin_url_with_tracefrom the job API when present (else appendsprego_trace). The sametrace_idis on the job JSON for welcome-email or other deep links. GET /v1/jobs/:id(prego-control-plane): Response includes canonicalorigin_url, optional additiveorigin_url_with_trace(same URL +prego_tracewhentrace_idis set), andtrace_idmay benullfor legacy rows.- Product app (
apps/client-web):ProvisionTraceCapture(root layout) reads?prego_trace=once intosessionStorage(prego_provision_trace_id), strips the query param, anddefaultApiHeaders,fetchCurrentUser, auth/onboarding flows, and profile gateway calls attachx-trace-idwhen present. - Regression checks (
prego-control-planerepo):npm run verifyrunstsc --noEmitandvitest(jobOriginFieldsFromRow/prego_traceURL shaping;internal-billing-test.test.ts;internal-tools-nav-pages.test.ts— shared nav on all smokePUBLIC_HTMLtool pages;cp-legacy-nav-coverage.test.ts—INTERNAL_TOOLS_NAV_HTMLvscp-legacy-tools.ts;internal-console-html.test.ts—Tool links섹션 미복귀)..github/workflows/ci.ymlrunsverify, thennpm ci --prefix admin,npm run lint --prefix admin, andnpm run admin:buildfor the React operator UI inadmin/, on push tomainand on pull requests. - Still operational: enabling Logpush (or equivalent) to your bucket and downstream SIEM remains a Zuplo / Cloudflare dashboard step, not defined in this repo.
Apply D1 migration 0022_allocation_decisions.sql after deploy.
한국어 {#korean}
문서 목적
- Free/Paid 신청부터 Frappe 런타임·Zuplo·Cloudflare까지 실제 코드베이스 기준 흐름을 단계별로 정리한다.
- logpath(추적 단위) 를 정의해 프로비저닝·환경 설정·런타임 트랜잭션 조사가 끊기지 않게 한다.
prego-control-plane에 통합 모니터링·관리자 UI 방향을 제시한다.
관련 문서 (admin-web / m.pregoi.com) {#korean-m-admin-related}
영문 Related docs 절과 같은 계열: m.pregoi.com admin-web UX implementation, m company profile · footer/cookie, m onboarding step connector. m.pregoi.com 기획 A–D: Stripe·Pay · 퍼널 관측 · logpath 전 구간 · Activate·포털.
저장 위치 (신청·사용자·주문)
플랫폼 정본: prego-control-plane의 Cloudflare D1
tenants_master— 테넌트 ID, 상태, 리전,plan_tier, 서브도메인,canonical_hostname, 회사·관리자·additional_users등provision_jobs—job_id,tenant_id,trace_id,status,infra_mode,plan_tier등- Stripe 멱등:
provider_events; 유료:billing_customers,subscriptions - 프로비저닝 완료 후:
tenant_runtime,secret_refs, KVTENANT_ORIGINS - 관측·할당 보조:
nodes,server_metrics,tenant_allocations, usage 스냅샷 테이블 등
Prego 모노레포의 UI 가입 화면과 D1 마스터는 연결은 HTTP 계약으로 유지한다 (repo responsibility matrix).
Free vs Paid
| 경로 | 설명 |
|---|---|
| Free | POST /v1/tenants → tenants_master + provision_jobs (Pending), 배치는 decidePlacement; 202 응답에 trace_id 포함(클라이언트가 x-trace-id 로 게이트웨이에 전달 가능) |
| Paid | POST /v1/checkout → Stripe → checkout.session.completed 웹훅 → 동일 계열 INSERT + billing. 메타데이터에 tenant_id가 없으면 웹훅에서 새 ID 생성할 수 있음 |
운영 주의: Free(POST /v1/tenants)·Paid(Stripe) 모두 동일한 triggerProvisionPipeline 경로를 쓰지만, PROVISION_QUEUE 또는 GITHUB_TOKEN+GITHUB_WORKFLOW_DISPATCH_URL이 없으면 D1에만 쌓이고 GitHub 워크플로는 실행되지 않는다.
리소스 할당 로직 (Control Plane)
decidePlacement:nodes+server_metrics(노드 푸시 스냅샷), 플랜(Enterprise → dedicated 등),PLACEMENT_MEMORY_PCT_MAX/PLACEMENT_MAX_TENANTS_PER_NODE등으로 기존 노드 vscreate_new_server결정.- 근거 로그:
allocation-decision-log.ts등 분석 로직은 있으나, 매 결정을 D1에 구조화해 남기는 것은 별도 마이그레이션·API로 보완하는 편이 좋다 (후보, 점수, 거절 사유, 정책 버전).
실행 파이프라인 (Docker Hub → 인스턴스 → Ansible → Zuplo → Cloudflare → 콜백)
- 트리거: GitHub Actions
workflow_dispatch또는prego-provision-queue소비 후 멱등workflow_dispatch(workflow_dispatch_log). - Pulumi(선택): 신규 서버 시 IP 확보 →
POST /internal/nodes등으로 노드 등록. - Ansible (
prego-ansible): Docker, MariaDB/Redis 역할,frappe_bench+frappe_site, 산출물artifacts/tenant_api_key.txt. - Docker Hub (
prego-docker): 커스텀 이미지 빌드·푸시; 인스턴스는docker pull로 수급 — “Hub 통해 Frappe 설치”는 이미지 공급 + Ansible 컨테이너 기동으로 이해한다. - Zuplo:
zuplo_sync등으로 테넌트 API 키 등록; 원문 키는 로그에 남기지 않음. - Cloudflare: 서브도메인·DNS 설계는 tenant-subdomain-dns-design 참고; KV 오리진은 provision-complete로 동기화. DNS 레코드 자동화 담당 컴포넌트는 trace stage로 명시하는 것이 안전하다.
POST /internal/provision-complete: D1·KV 최종 반영, BearerINTERNAL_API_KEY.
Logpath 기획
상관 ID
trace_id— 한 온보딩·프로비저닝 세션 (provision_jobs에 저장)job_id— 큐·GitHub 멱등·재시도 단위tenant_id— 장기 식별·과금·게이트웨이gh_run_id— Actions 로그와 조인 (trace_events등에 기록 권장 )
D1
traces/trace_events,audit_logs,workflow_dispatch_logGET /internal/trace/:trace_id로 조회
남는 갭: provision-tenant.yml은 trace_id가 있을 때 resolve·DNS·Ansible·Zuplo 단계를 trace_events에 남긴다. 수동 workflow_dispatch(빈 trace_id)이나 큐에 오래된 메시지(필드 누락)는 타임라인이 얇을 수 있다.
런타임(사용자 트랜잭션)
prego-zuplo: Frappe/Assistant UI/데모/External·Agent API·Google OAuth·prompt filter·MCP·email OTP·billing·rate-limit·quota(Redis) 등context.log에tenant_id·prego_company_id·cf_ray(헤더 또는request.cf)·http_request_id(선택)·x-trace-id→trace_id(선택)를 붙임(modules/gateway-runtime-log-fields.ts). 일반·AI rate limit 및 quota 429 거부 시 추가 warn 로그로 상관 가능. Logpush·버킷 적재는 운영에서 연결.- 모노레포 UI:
apps/admin-web— Free 온보딩에서trace_id보관, 잡 폴링 시x-trace-id, 새로고침 시prego_provision_*세션 복구, 완료 시GET /v1/jobs/:id응답의origin_url_with_trace(없으면 클라이언트에서prego_trace추가).apps/client-web— **ProvisionTraceCapture**가 **?prego_trace=**를 **prego_provision_trace_id**에 저장 후 URL에서 제거;defaultApiHeaders·인증·온보딩·프로필 Zuplo 호출에x-trace-id병합. - Zuplo Logpush, Workers 로그, Frappe/호스트 로그 등은 D1 밖 저장소가 주력 —
tenant_id·cf_ray/http_request_id·(클라이언트가 넘기면)x-trace-id로 D1trace_id와 조인 가능. - 노드 리소스:
POST /internal/server-metrics및 (기획) 시계열 스냅샷 테이블로 확장 (resource-observability-and-cost-planning 참고). - 온보딩 단계 조회:
GET /internal/onboarding-statusJSON에trace_id,origin_url,origin_url_with_trace포함(잡/테넌트 조인 가능 시);GET /internal/onboarding-dashboardHTML에서 테넌트 URL이 있으면 열기 링크 표시. - 내부 HTML Bearer: 브라우저
sessionStorage키cp_internal_key— 콘솔·온보딩·메트릭 대시보드·AI flow·RAG·placement/resource-pool 시뮬레이션·이메일 플로 대시보드 등에서 동기화(메트릭은 레거시prego_dashboard_apikey병행)./internal/console는 Bearer 입력란이 비어 있어도 저장된 키로 스냅샷·조회 요청 가능./internal/dashboard는 입력란이 비면 위 두 키로 API 키 칸을 프리필한다. - 내부 HTML 공통 네비:
internal-tools-nav-html.ts(INTERNAL_TOOLS_NAV_HTML)가/internal/console포함 모든 내부 도구 HTML 상단에 붙는다. 레거시 HTML 도구는 prego-control-planesrc/cp-legacy-tools.ts(navStripHref,internalPath)와 함께 갱신; Vitest **cp-legacy-nav-coverage.test.ts**가 스트립과 정합성 검사.
통합 모니터링 관리자 페이지 (prego-control-plane)
목표: /internal/billing-test, placement·resource-pool 시뮬레이션, 이메일/AI 플로 대시보드 등 분산 HTML을 한 네비게이션(예: /internal/console) 으로 묶는다.
탭 예시
- 홈: 신규/ Pending·Failed job, 포화 노드, 웹훅 건강도
- 테넌트 디렉터리:
tenants_master검색, billing·runtime·allocations - 잡/파이프라인:
job_id, dispatch 로그, (가능 시) GitHub run 링크 - Trace:
trace_id타임라인 - 시뮬레이터: 기존 placement / resource-pool (D1 비쓰기 모드 UI 표기 유지)
- 할당 근거: (향후)
allocation_decisions또는 표준화된 JSON
보안: 전 구간 INTERNAL_API_KEY; API Key·시크릿은 ref만 표시.
구현 우선순위 제안
1–4는 prego-control-plane + provision-tenant.yml 기준으로 반영됨(dispatch 통일, trace_events, allocation_decisions, /internal/console).
5. Zuplo Logpush 등 런타임 로그에 tenant_id 일관 주입은 prego-zuplo/관측 스택에서 진행.
Implementation status (prego-control-plane 레포)
| 기획 항목 | 상태 | 비고 |
|---|---|---|
Free/Paid 동일 Queue·workflow_dispatch | 적용됨 | POST /v1/tenants → triggerProvisionPipeline (provision-pipeline-trigger.ts) |
allocation_decisions + order_accepted trace | 적용됨 | 마이그레이션 0022_allocation_decisions.sql, provision-order-audit.ts |
POST /internal/trace-events | 적용됨 | 파이프라인 단계 append; 워크플로에서 trace_id 입력 시 단계별 POST |
Queue·dispatch에 trace_id | 적용됨 | ProvisionQueueMessage → GitHub inputs |
| 실패 콜백 | 적용됨 | provision-complete + Failed + failure_stage (구 /internal/provision-failed 제거) |
provision-complete → trace | 적용됨 | provision_completed / provision_failed |
/internal/console | 적용됨 | 스냅샷·할당·잡·테넌트·trace 조회 UI (internal-console-html.ts); 최근 잡에 대해 origin_url_with_trace 우선 빠른 열기 링크; Bearer 칸이 비어 있어도 cp_internal_key 로 폴백 |
/internal/console/snapshot | 적용됨 | 24h 실패 건수 필드 추가 |
| 테넌트 디렉터리·잡 목록 API | 적용됨 | GET /internal/tenants-directory, GET /internal/provision-jobs — 후자는 항목마다 origin_url / origin_url_with_trace (GET /v1/jobs/:id와 동일한 jobOriginFieldsFromRow) |
GET /v1/jobs/:id — origin_url_with_trace | 적용됨 | jobs.ts: prego_trace 보조 필드, trace_id는 null 가능(레거시 행) |
GET /internal/onboarding-status — origin·trace | 적용됨 | internal.ts: jobOriginFieldsFromRow 공유; 온보딩 대시보드 HTML 링크 |
| Zuplo 런타임 로그 필드 | 부분 적용 | prego-zuplo gateway-runtime-log-fields.ts — Frappe/Assistant UI·OAuth·prompt filter·MCP·email OTP·billing·rate-limit·quota(Redis) 등 + cf_ray(헤더 또는 request.cf)·http_request_id(선택)·trace_id(x-trace-id). Logpush는 포털·인프라 별도 연결 |
admin-web 온보딩 trace / 잡 폴링 / prego_trace | 적용됨 | Prego apps/admin-web — x-trace-id on job poll, session resume, Open tenant query handoff |
| client-web trace capture / gateway 헤더 | 적용됨 | Prego apps/client-web — ProvisionTraceCapture, api-client provisionTraceHeaders / gatewayJsonHeaders, defaultApiHeaders |
jobOriginFieldsFromRow 테스트 + GitHub CI | 적용됨 | npm run verify (lint + vitest); .github/workflows/ci.yml |
Placement·resource-pool·이메일 플로 등 내부 HTML — cp_internal_key | 적용됨 | /internal/placement-simulation, /internal/resource-pool-simulation, /internal/email-flow-dashboard에서 입력 필드 비어 있을 때 저장된 키로 Bearer 복원·재저장(콘솔·RAG 등과 동일 패턴) |
/internal/billing-test + billing-test-data | 적용됨 | HTML 껍데기는 인증 없이 제공; 테이블 데이터는 GET /internal/billing-test-data(Bearer). 브라우저는 cp_internal_key |
| 메트릭 대시보드 API 키 프리필 | 적용됨 | /internal/dashboard 입력란이 비면 cp_internal_key·prego_dashboard_apikey 로 채움 |
| 스모크 스크립트 (HTML 셸·billing·trial review) | 적용됨 | smoke-verify-control-plane.sh 의 PUBLIC_HTML 9경로(무인증 GET 200) + PUBLIC_CP_SPA 4경로(React /cp 셸) + /internal/billing-test-data(Bearer) + /internal/trial-access-review/requests?page=1&pageSize=1(Bearer) + R5/R7/R1; 신규 공개 HTML은 배열·internal-tools-nav-html.ts·(레거시 HTML 도구) src/cp-legacy-tools.ts 동시 갱신 |
billing-test* Vitest | 적용됨 | internal-billing-test.test.ts — Bearer 401/200, JSON { items }, HTML 셸에 billing-test-data·cp_internal_key 문자열 포함 |
| 내부 도구 HTML 공유 네비 Vitest | 적용됨 | internal-tools-nav-pages.test.ts — 스모크 PUBLIC_HTML 와 동일한 HTML 소스에 공통 스트립; cp-legacy-nav-coverage.test.ts — 스트립 vs cp-legacy-tools.ts; internal-console-html.test.ts — Tool links 섹션 미복귀 |
| 내부 HTML 공통 네비 | 적용됨 | internal-tools-nav-html.ts — /internal/console 및 기타 내부 HTML 상단 동일 스트립; 레거시 HTML 도구는 cp-legacy-tools.ts (navStripHref)와 함께 갱신 |
배포 시 D1에 0022 마이그레이션 적용 필요 (npm run db:migrate).
작성 기준: Prego 모노레포·prego-control-plane·prego-ansible·prego-docker 저장소 상태 (코드 변경 시 이 문서의 “갭” 단락을 갱신할 것).