


OpenAPI 명세를 먼저 고쳐서 Go API와 React SPA 변경하기
Go 백엔드와 React SPA가 OpenAPI 명세를 함께 쓰는 저장소에서 사용합니다. 생성된 파일을 손으로 고치지 않고 명세를 고쳐 다시 생성하며, 설정·오류·로그인·다운로드·업데이트를 정해진 규칙대로 다루고, 테스트한 뒤 보고합니다.
이 AI 규칙을 사용하려면
## OpenAPI 명세를 기준으로 삼고 계층별 역할 나누기
- 라우트, 요청 및 응답 schema, 공유 모델에 대해 OpenAPI 명세를 단일 진실 공급원으로 취급합니다.
- 개발 중에는 Go backend와 React SPA를 서로 독립적으로 빌드할 수 있게 유지합니다.
- 운영 환경에서는 빌드된 SPA를 backend 바이너리에 포함하고 API와 UI를 동일 출처에서 제공하는 방식을 우선합니다.
- 개발 서버는 proxy 편의를 위해서만 사용하고, 개발 전용 port나 host가 운영 코드로 유입되지 않게 합니다.
- 소유권을 명확히 유지합니다. API 계층은 전송, service는 domain behavior, store는 persistence, frontend는 presentation을 담당합니다.
- 병렬 route, configuration store, event channel, state model을 새로 만들기 전에 기존 계약을 확장합니다.
- 범위가 정해진 요구사항과 아키텍처 결정 없이 명시적으로 연기된 subsystem을 추가하지 않습니다.
- 문서와 구현이 일치하지 않으면 영향을 받는 변경을 중단하고 진실 공급원 간 충돌을 먼저 해결합니다.
## 생성 파일은 손대지 말고 명세를 고친 뒤 다시 생성하기
1. 명세, 생성된 interface, 구현, test, 관련 운영 문서를 확인합니다.
2. 권위 있는 원본을 수정합니다. API 형태는 OpenAPI file, 설정은 configuration struct, UI text는 message catalog에서 변경합니다.
3. 저장소 명령으로 backend와 frontend 산출물을 다시 생성합니다.
4. 생성된 interface를 구현하고 직접 영향을 받는 service와 UI wrapper만 갱신합니다.
5. formatting, static analysis, focused test, 관련된 최소 runtime check를 실행합니다.
6. 생성된 diff를 검토하여 명세가 의도한 계약을 만들었는지 확인합니다.
- 생성된 file을 절대로 직접 수정하지 않습니다. 대신 명세나 generator configuration을 수정합니다.
- 생성된 server interface를 우회하는 임시 route를 절대로 등록하지 않습니다.
- operation identifier는 생성된 method name이 되므로 고유하고 안정적으로 유지합니다.
## 공유 스키마를 한곳에 두고 생성된 타입을 API와 맞추기
- path definition은 domain별로 나누되 공유 schema, parameter, response는 하나의 정규 components section에 둡니다.
- path file 사이에 거의 같은 schema를 복제하지 말고 shared reference를 재사용합니다.
- cross-file reference에 필요한 generator 전용 import mapping과 pruning control을 유지합니다.
- schema가 upstream library type을 반영하면 machine mapping과 JSON property를 동기화합니다.
- serialize되는 모든 field를 선언하고, 항상 존재하는 field는 Go 외 소비자를 위해 required로 표시합니다.
- nullable reference는 OpenAPI version과 generator가 요구하는 형식으로 표현하고 생성 결과를 test합니다.
- runtime fidelity가 중요하면 backend가 실제로 제공하는 API document에서 frontend type을 생성합니다.
- 저장소 정책이 diff를 계약 검토 증거로 사용한다면 생성된 API type을 commit합니다.
- component가 transport detail에 의존하지 않도록 API client를 domain wrapper 뒤에 둡니다.
## 내부 오류 문구를 노출하지 않고 구조화된 오류 반환하기
- 생성된 server interface를 통해 endpoint를 구현하고 handler method를 domain별로 묶습니다.
- shared JSON response helper 하나와 central error classifier 하나만 사용합니다.
- 안정적인 machine code, rendering kind, 안전한 optional field, request identifier를 가진 structured error를 반환합니다.
- raw internal error 또는 upstream error text를 client response에 절대로 복사하지 않습니다.
- field validation detail은 전용 map에 넣고, field와 관련 없는 failure를 위한 general key를 예약합니다.
- upstream failure를 안정적인 reason identifier로 변환하고 raw body는 보호된 log에만 보관합니다.
- dynamic text가 의도적으로 안전하게 표시될 수 있을 때만 typed user-facing error를 사용합니다.
- classifier 밖에서 wire error envelope를 만들지 않습니다.
- control flow에 panic을 사용하지 말고 status code와 JSON error를 반환합니다.
- panic recovery는 normal behavior가 아니라 구조화되고 기록되는 safety net으로 취급합니다.
## 설정은 한 번만 정의하고 재시작이 필요한 설정 구분하기
- typed configuration structure에서 setting을 한 번만 정의하고 default, validation, schema metadata, environment key를 파생합니다.
- default, settings file, environment override 순서로 일관되게 우선순위를 적용합니다.
- built-in default와 다른 value만 persistence합니다.
- 모든 새 setting에 대해 default, validation, UI category, sensitivity, visibility, restart behavior를 정의합니다.
- hot-reload 가능한 setting에는 live consumer 또는 reload hook이 있어야 합니다. 없으면 restart-required로 표시합니다.
- restart-required value는 즉시 persistence할 수 있지만 restart 전까지 effective runtime view에서 고정해야 합니다.
- API response에서 sensitive value를 mask하고 redacted placeholder를 update 시 no-op으로 취급합니다.
- program-only setting은 runtime API를 통해 read-only로 유지하고 hidden setting은 reflected UI schema에서 제외합니다.
- 저장된 configuration이 다음 boot를 망가뜨리지 않도록 startup과 persistence 전에 같은 validator를 실행합니다.
- setting을 atomic하게 기록하고 관리되는 relative path는 launch directory가 아니라 안정적인 data root에 고정합니다.
## 로그 형식을 통일하고 작업을 남기지 않고 서버 종료하기
- HTTP request, lifecycle event, background worker 전체에 하나의 schema를 적용한 structured logging을 사용합니다.
- request별 event를 중복 발행하지 말고 handler field와 error를 현재 request log에 첨부합니다.
- backend log는 하나의 운영 언어로 유지하고 UI localization은 frontend가 담당합니다.
- request ID, 실제 client address, request logging, panic recovery가 의존하는 middleware 순서를 유지합니다.
- request logging 시작 전에 field를 바꿔야 하는 경우가 아니면 새 middleware를 recovery boundary 안에 둡니다.
- shutdown 시 HTTP server를 drain하기 전에 long-lived event subscriber를 닫습니다.
- in-flight request에 제한된 drain deadline을 부여하고 deadline이 만료될 때의 동작을 명시합니다.
- restart 시 process를 교체하거나 생성하기 전에 service cleanup을 완료합니다.
- streaming handler는 hub close 또는 request cancellation 시 반환하게 합니다.
- 작업이 고립되지 않도록 모든 background worker를 같은 shutdown sequence에 등록합니다.
## 안정적인 쿼리 키를 쓰고 사용자가 바뀌면 캐시 비우기
- 이후 provider가 query, locale, authentication, event context에 의존한다면 provider order를 명시합니다.
- feature module에서 query-option factory를 노출하고 raw API call은 domain wrapper 안에 둡니다.
- identity를 변경하는 모든 parameter를 포함한 stable query key를 사용합니다.
- 이전 list data는 실제 page transition에서만 유지하고 filter, user, tab, search identity가 바뀌면 버립니다.
- authenticated session이 바뀌면 account-scoped cache를 비웁니다.
- entity의 cached copy 전체가 일관되도록 optimistic update, rollback snapshot, invalidation을 중앙화합니다.
- server event는 refresh hint로 취급하고 authoritative state는 계속 query 또는 state endpoint에서 가져옵니다.
- render path 안에서 reactive context를 통해 localized message function을 읽습니다.
- dynamic translation key는 explicit static resolver로 mapping하고 locale 간 placeholder shape를 동일하게 유지합니다.
- 사람이 읽는 configuration label은 backend schema가 아니라 frontend catalog에 둡니다.
## 로그인 세션을 따로 저장하고 일시적 실패로는 로그아웃시키지 않기
- session credential을 제한된 permission의 전용 state file 하나에 저장하고 general setting에 절대로 섞지 않습니다.
- refresh credential을 session authority로, short-lived access credential을 교체 가능한 값으로 취급합니다.
- expiration 직전에 proactive refresh하고 authentication failure 후에는 single-flight coordination으로 한 번만 reactive refresh합니다.
- transient failure에는 session을 유지하고 confirmed permanent rejection 후에만 지웁니다.
- authorization verifier는 short-lived, capacity-bounded, single-use로 유지합니다.
- 붙여 넣은 callback value를 upstream으로 보내기 전에 validate하고 localized actionable error를 반환합니다.
- proxy discovery result에서 credential을 제거하고 discovery를 read-only로 유지합니다.
- unauthenticated onboarding write를 보호하여 login 이후 setting을 변경하지 못하게 합니다.
- sensitive mutation endpoint를 authentication과 application-specific CSRF header로 보호합니다.
- upstream authentication body, credential, internal package error를 client에 절대로 노출하지 않습니다.
## 다운로드는 임시 파일을 거쳐 저장하고 앱이 만들지 않은 파일은 지우지 않기
- job을 transaction boundary로 삼고 의미 있는 모든 state transition을 atomic하게 persistence합니다.
- 매 tick마다 persistent state를 다시 쓰지 말고 high-frequency progress를 event로 보냅니다.
- payload를 고유한 이름의 temporary file에 쓰고 성공한 뒤에만 rename하며, cancel 또는 failure 시 partial file을 제거합니다.
- bounded exponential backoff로 retryable failure만 재시도합니다.
- cleanup 전에 application이 각 final file을 만들었는지 기록하고, 예상 이름만을 근거로 path를 절대로 제거하지 않습니다.
- disk content와 in-flight reservation 모두에 대해 collision을 해결하고 bounded fallback strategy를 둡니다.
- completed payload가 이미 destination을 소유한 경우를 제외하고 restart recovery 중 path를 다시 해결합니다.
- startup 시 path template을 parse하고 configuration change를 수락하기 전에 다시 validate합니다.
- 모든 path segment를 sanitize하고 explicit root를 보존하며 filename은 byte 단위로 제한하고 user-visible truncation은 character 단위로 셉니다.
- hot-reloaded setting이 진행 중인 작업을 바꾸지 않도록 conversion 및 naming decision을 job별로 고정합니다.
## 재연결 시 놓친 이벤트를 다시 보내고 불가능하면 재동기화하기
- persistent state를 authoritative하게 유지하고 event는 optimization으로 취급합니다.
- event에 stable ID, timestamp, topic, type, reconstructible payload를 부여합니다.
- reconnect 시 buffer에 남아 있다면 client의 last known ID 뒤 event를 replay합니다.
- replay할 수 없으면 resynchronization event 하나를 발행하고 client가 authoritative state를 다시 가져오게 합니다.
- intermediary가 idle stream을 조용히 닫지 못하도록 keepalive를 사용합니다.
- job-level event만 aggregate job status를 설정하게 하고 task event는 server-owned rule을 다시 계산하지 않고 task state만 갱신합니다.
- reconnect 후와 resynchronization signal 후에 affected view를 refresh합니다.
- progress notification을 throttle하되 terminal transition과 final progress update는 throttling을 우회합니다.
- 작은 publisher interface를 통해 event transport를 domain manager와 독립적으로 유지합니다.
## 프록시 대상을 제한하고 서명을 확인한 뒤 앱 업데이트하기
- exact host policy로 proxy target을 허용하고 server-side request forgery를 막기 위해 redirect마다 다시 검사합니다.
- 같은 resource에 대한 concurrent cache miss를 공유하고 oversized body는 caching 전에 거부합니다.
- filesystem을 cache truth로 취급하고 periodic하게 그리고 write 후에 oldest-accessed eviction으로 size를 조정합니다.
- cache storage를 relocatable하게 유지하고 durable user state와 분리합니다.
- release build 시 update-feed location과 trusted signing key를 주입하고 trust material이 없으면 fail closed합니다.
- version, URL, checksum, release note field를 신뢰하기 전에 signed manifest를 검증합니다.
- binary를 교체하기 전에 downloaded archive를 signed manifest와 대조해 검증합니다.
- update application을 single-flight로 실행하고 development build에서는 거부하며 authentication과 CSRF protection을 요구합니다.
- replacement executable을 deterministic하게 해결하고 ambiguous archive를 거부합니다.
- verified replacement가 준비된 후에만 service를 drain하고 restart합니다.
## 실행 중인 앱으로 확인하고 릴리스나 운영 환경 변경 전에 멈추기
- 실패한 step을 건너뛰지 말고 generation, implementation, focused test, direct runtime verification을 끝까지 수행합니다.
- health endpoint, served OpenAPI document, affected API behavior, directly affected UI path를 검증합니다.
- 변경이 관련되면 format, static analysis, race-sensitive backend test, frontend check, production build를 실행합니다.
- focused test로 atomic persistence, restart semantics, secret masking, error non-disclosure를 확인합니다.
- release 작업에서는 strict semantic versioning, signed-feed continuity, checksum, platform archive resolution을 검증합니다.
- 현재 요구사항이 명시적으로 범위에 포함하지 않는 한 deferred feature는 계속 미룹니다.
- unrelated refactor, parallel state store, duplicate error system, speculative infrastructure를 피합니다.
- 명시적 authorization이 없으면 external release, destructive migration, credential change, production mutation 전에 멈춥니다.
- 변경 사항, 검증 사항, 범위 밖에 남긴 사항, unresolved contract conflict를 보고합니다.
