DevPay 로컬 Control Plane
로컬 control plane은 애플리케이션 API가 아닌 개발 도구입니다. 별도의 SQLite 데이터베이스, Git worktree, 격리된 로컬 검증 ref를 사용합니다. 애플리케이션을 배포하지 않고, D1에 접근하지 않고, 원격에 발행하지 않고, 결제를 건드리지 않습니다.
초기화 및 태스크 생성
nougat-devpay/에서 실행. 초기화에서 불변 spec 리비전을 고정; 생성된 worktree는 동일한 Gem 루트와 커밋을 유지합니다.
python3 -m tools.devpay_control.cli --state .devpay-control init \
--repo .. --gem-root /path/to/nougat-gems \
--gem-revision 69b0707633a4afc0888ba1ce5092375386b630cd \
--base feat/pg-magician2
3
4
격리된 통합 ref는 기본 refs/devpay/validated/local이며 제공된 Base에서 부트스트랩됩니다. 절대 원격 푸시하지 않습니다. 로컬 Gate 1/2 증거 통과 후, 대상 브랜치로의 로컬 전용 fast-forward가 명시적으로 수행될 수 있습니다. 모든 소스 변경은 nougat-devpay/로 제한; 브랜치 생성, 스테이징, 커밋은 명시적으로 유지됩니다.
python3 -m tools.devpay_control.cli --state .devpay-control create product-work --owner SELLER_FE
python3 -m tools.devpay_control.cli --state .devpay-control submit <task-id>
python3 -m tools.devpay_control.cli --state .devpay-control gate1 <attempt-id>
python3 -m tools.devpay_control.cli --state .devpay-control integrate <attempt-id>
python3 -m tools.devpay_control.cli --state .devpay-control report --task <task-id>
python3 -m tools.devpay_control.cli --state .devpay-control cancel <unsubmitted-task-id> --owner BACKEND_INFRA2
3
4
5
6
읽기 전용 뷰어는 쓰기 API 없이 태스크, 시도, 게이트, 이벤트, 런타임 리스, 메모리 스냅샷, 저장된 아티팩트를 관찰합니다. control.sqlite를 mode=ro로 열고, GET/HEAD만 수용하고, 루프백만 바인딩(외부 노출은 이 로컬 도구가 제공하지 않는 TLS와 인증 필요), 파일시스템 경로를 드롭하는 필드 허용 목록으로 레코드를 투영합니다. 아티팩트는 읽기에서 재해시되고 JSON으로 제공; 런타임 로그는 상태 루트 내부에서만 nosniff와 text/plain으로 제공됩니다.
python3 -m tools.devpay_control.cli --state .devpay-control viewer --port 8090Gate 1은 제출된 SHA를 분리된 worktree에서 검사합니다. Gate 2는 그 정확한 SHA를 현재 로컬 검증 ref에 머지하고, 후보 프로필을 다시 실행하고, git update-ref <new> <expected-old>로 ref를 진행시킵니다. 후보 실패, Gate 1 실패, 머지 충돌, 낡은 Base는 검증 ref를 변경하지 않고 남깁니다. 재시도는 새 Attempt를 생성합니다. cancel은 원래 Base의 소유되고 깨끗한 미제출 Task로 제한; Attempt나 활성 런타임이 있는 Task는 거부합니다. 원격 발행은 의도적으로 미구현입니다.
프로필과 런타임
Diff 분류는 보수적입니다. Submit은 nougat-devpay/** 아래 경로와 정확한 리포 루트 경로 .github/workflows/yetdapay.yml을 수용; 다른 모든 루트 경로(다른 워크플로 파일 포함)는 거부됩니다. 빈, 비정규, 순회 포함 경로도 거부됩니다. DevPay 문서 전용 변경은 git diff --check 실행; control-plane 코드는 자체 unittest 스위트 실행; Axum 코드는 cargo test 필요; Worker 코드는 WASM 빌드 필요; 프론트엔드 코드는 타입검사 + 브라우저 검증 필요. 정확한 워크플로 경로는 git diff --check, control-plane unittest 스위트, .github/workflows/yetdapay.yml의 오프라인 Python/PyYAML 구조 검사(루트/잡 매핑과 필수 잡)를 실행; 스스로 프론트엔드나 브라우저 검사를 선택하지 않습니다. PyYAML은 이미 로컬에 있어야 함; 검사는 절대 의존성을 설치하거나 네트워크를 사용하지 않습니다. 이 구조 검사는 별도로 통과해야 하는 실제 GitHub Actions 검증의 대체가 아닙니다. 알 수 없는 경로는 모든 프로필로 에스컬레이션. 누락된 실행 파일, 의존성, 커버리지, 격리된 런타임은 PASS가 아닌 BLOCKED입니다.
Axum 시작은 기본으로 비시딩 유지. 데모 행이 필요한 테스트는 db::seed_test_fixture를 명시적으로 호출; 그 헬퍼는 APP_ENV=production과 OS 임시 디렉터리 외부의 데이터베이스를 거부합니다. 구성된 서버가 없을 때 PostgreSQL 전용 테스트 하나를 무시하고 전체 로컬 Axum 테스트 스위트가 통과합니다.
태스크 소유 Linux GUI 세션은 runtime-start, runtime-status, runtime-stop으로 관리될 수 있습니다. 각각 별도의 Xvfb 부모와 Xephyr 디스플레이, 브라우저 프로필, 홈, 아티팩트 디렉터리를 할당합니다. 호스트 DISPLAY와 XAUTHORITY는 상속되지 않습니다. Gate 브라우저 러너는 자체 동등한 Gate 소유 런타임을 시작합니다. 둘 다 headed Chromium과 로컬 임시 Axum SQLite 픽스처를 사용합니다. Rust/API/DB 전용 검사는 GUI를 시작하지 않습니다.
브라우저 게이트는 package.json과 pnpm-lock.yaml 지문이 Gate worktree와 일치할 때만 잠긴 FE 의존성 트리를 재사용하고, 그 트리를 Gate worktree에 복사하며 절대 패키지 라이프사이클 스크립트나 네트워크 설치를 실행하지 않습니다. UI 포트는 기존 Axum CORS 정책이 허용하는 두 개의 루프백 오리진을 사용합니다. 브라우저 외부 요청은 차단됩니다. 잠금 불일치, 사용 불가 캐시, Xephyr 실패, 시작 실패는 Gate를 차단합니다.
Gate 자식은 HOME, CARGO_TARGET_DIR, SQLite 픽스처, 브라우저 프로필을 Gate 런타임 내부에 유지하고 빌드는 오프라인(--offline, CARGO_NET_OFFLINE=true)입니다. 세 개의 호스트 캐시가 읽기 전용 공유 입력으로 전달됩니다 — 격리된 HOME에는 툴체인, 레지스트리 캐시, 설치된 브라우저가 없기 때문입니다: CARGO_HOME/RUSTUP_HOME($CARGO_HOME/$RUSTUP_HOME, 아니면 ~/.cargo/~/.rustup), PLAYWRIGHT_BROWSERS_PATH($PLAYWRIGHT_BROWSERS_PATH, 아니면 ~/.cache/ms-playwright), 지문이 Gate worktree와 일치한 후 $DEVPAY_FE_DEPS_SOURCE에서 가져온 FE 의존성 트리(아니면 정규 체크아웃의 nougat-devpay/fe/node_modules). 호스트 캐시에 아무것도 쓰지 않으므로 새 Gate worktree는 사전 설치된 node_modules가 필요 없고, 빌드 출력은 태스크 격리 CARGO_TARGET_DIR에 랜딩합니다. 누락된 툴체인, 브라우저 캐시, 의존성 캐시, 미검증 또는 잠금 불일치 의존성 트리, 비절대 DEVPAY_FE_DEPS_SOURCE는 검사를 스킵하는 대신 Gate를 차단합니다.
셀러 상품 seller.product.create와 seller.product.update 액션은 버전 관리되고 페이로드 검증됩니다. 그 Receipt는 API 명령 결과, 서버 읽기 후 쓰기, 렌더된 상세를 분리합니다. 브라우저 드라이버는 UI와 서버 행을 독립적으로 검증합니다. 두 액션 모두 정확한 Task/Attempt/Commit/Scenario 증거로 Gate 1/2를 통과합니다. 모든 필수 증거가 그 컨텍스트와 일치한 후에만 Evidence Bridge가 verified를 보고할 수 있습니다. 브리지는 증거를 읽고 대사만 하며; Git이나 큐 메서드가 없습니다.
상태와 신뢰 한계
SQLite는 태스크, 시도, 큐, 게이트, 이벤트, 런타임 리스, 불변 아티팩트 다이제스트 레코드를 저장합니다. Gate 아티팩트는 상태, 검사 ID, 기간, SHA, 런타임 아이덴티티, 허용 목록 액션 영수 증거를 포함; 명령 stdout/stderr와 원시 제품 페이로드는 폐기됩니다. 뷰어는 읽기 전용입니다.
구현은 단일 호스트 Linux입니다: flock 리스와 Git CAS가 로컬 동시성을 보호하지만, 이것은 분산 리스/펜싱 서비스가 아닙니다. 동일 OS 계정을 공유하는 상호 적대적 프로세스를 격리하지 않습니다. 복구는 실제 Git/런타임 상태를 검증해야 함; 중단된 레코드는 보존됩니다. 원격 발행, 크로스 호스트 스케줄링, 알림, 네이티브 OS 입력, 프로덕션 배포는 범위 밖으로 유지됩니다.