diff --git a/docs/admin-console.md b/docs/admin-console.md index de89dfd0..11c986fc 100644 --- a/docs/admin-console.md +++ b/docs/admin-console.md @@ -4,6 +4,11 @@ Gateway 관리자 콘솔은 `/gateway/admin`에서 시작합니다. 공개 로 역할 또는 `admin.*` 범위 권한이 확인된 사용자에게만 진입 링크를 표시하며, 실제 조회와 변경 권한은 각 Gateway API가 다시 검사합니다. +플레이 행위·국가 통계·NPC 결정의 감사는 +[프로필별 플레이 감사 설계](./design/play-audit.md)에서 별도로 정의합니다. +현재는 설계 단계이며 각 profile의 `/play-audit`와 game-api가 화면·조회를 +소유할 예정입니다. 아래 `/gateway/admin/audit`는 기존 관리자 조치 원장입니다. + ## 화면 구성 좌측 메뉴는 관리 책임을 다음과 같이 분리합니다. diff --git a/docs/design/play-audit.md b/docs/design/play-audit.md new file mode 100644 index 00000000..156c4ce3 --- /dev/null +++ b/docs/design/play-audit.md @@ -0,0 +1,439 @@ +# 프로필별 플레이 감사 설계 + +## 문서 상태와 사용법 + +**설계 기준: 2026-09-16. 제품 기능은 미구현이다.** 이 문서는 관리자 플레이 +감사의 구현 goal과 완료 판정 기준이다. 문서 작성 완료는 기능 구현 완료가 아니다. +후속 작업은 아래 요구사항 ID, 단계와 증거 표를 유지하며 진행 상태를 갱신한다. + +사용자 결정으로 고정한 범위: + +- 감사 화면은 각 profile의 `/play-audit`에 둔다. Gateway 감사 화면으로 통합하지 않는다. +- 매월 원본 상태를 기록하고 그래프는 6개월 요약을 기본으로 한다. +- 플레이 감사는 현재 기수만 보존한다. 종료 후 다음 초기화 전까지 조회할 수 있다. +- 공통 계정 조사 기록은 최근 30일 보존하며 원본 IP 대신 비교용 식별자를 사용한다. +- 모든 NPC의 개인턴·수뇌턴 선택 과정을 기록한다. +- 자동 탐지·자동 제재보다 관리자 검색과 수동 조사를 먼저 구현한다. +- 국방·NPC 정책 이력의 수뇌 공개는 구조·권한 설계까지 포함하고 화면은 후속 범위다. +- 기존 컴포넌트를 재사용하고, 필요한 기능이어도 DB 읽기·쓰기 비용을 다시 검토한다. + +공통 계약은 [요청·턴·저장](../developer/request-turn-persistence.md), +[패키지 경계](../architecture/package-boundaries.md), +[변경 journal](../architecture/realtime-change-journal.md), +[테스트 정책](../testing-policy.md)을 따른다. 기존 관리자 기능은 +[관리자 콘솔](../admin-console.md)을 참고한다. Ref 대응은 상위 작업공간의 +`docs/ref-core2026-mapping.md`가 소유한다. + +## 1. 현재 기반과 추가 작업 + +다음은 Core `f4aabec1fa13a0ae136b8ba96e8aa5dfa9a8e79e`의 정적 조사 결과다. +Ref 조사 checkout은 `ng_compare@2239ff667b3e841c9fed69b53539437a79203806`, +제품 기준선은 `devel@6c7f774fa1d49774a5924780516c88b8888cc1d7`이다. +후속 구현 시작 시 현재 소스와 다시 대조한다. 이 표는 live DB/runtime 검증이 아니다. + +| 기반 | 확인한 source와 의미 | 감사 구현에서 보완할 점 | +| ----------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | +| 관리자 조치 | `app/gateway-api/src/adminAudit.ts`, `AdminAuditEvent` | 기존 관리자 조치 원장은 유지하고 게임 플레이 기록과 구분 | +| 입력 원장 | `app/game-api/src/inputEventBoundary.ts`, `InputEvent` | API 요청 payload는 식별용 digest인 경로가 있음. 이를 실제 입력 내용으로 간주하지 않음 | +| 변경 알림 | `packages/common/src/realtime/changeJournal.ts` | entity/domain revision용이며 필드별 전후 값이나 원인 기록이 아님 | +| 턴 저장 | `app/game-engine/src/turn/inMemoryWorld.ts`, `databaseHooks.ts` | dirty state와 같은 transaction을 재사용하되 최종 dirty 상태만으로 개별 사건 순서를 복원하지 않음 | +| 월별 연감 | `yearbookHandler.ts`, `YearbookHistory` | 기존 지도·국가·로그와 겹치는 값은 재사용. 상세 장수·세율 적용 실제 수입은 별도 필요 | +| 국가 재정 | `incomeHandler.ts`, `monthlyNationStatsHandler.ts` | 이미 계산한 수입·지급·통계에서 수집. 감사용 재계산·추가 난수 소비 금지 | +| 장수·도시 | `nation/endpoints/getBattleCenter.ts`, `getSecretGeneralList.ts`, `world/index.ts` | 현재 조회를 활용하되 장수 없는 관리자와 과거 snapshot용 읽기 경계 추가 | +| 외교 | `Diplomacy`, `DiplomacyLetter`, `router/diplomacy/index.ts` | 현재 국가쌍 상태와 문서 체인만으로 변경 당시 상태가 모두 보존되지는 않음 | +| NPC 선택 | `ai/generalAi/core.ts`, `reservedTurnHandler.ts` | 일부 환경변수 stdout trace·action hook이 있으며 전체 과정의 내구성 기록은 없음 | +| NPC 정책 | `npcPolicyMutation.ts`, `router/npc/index.ts` | 현재 정책·마지막 변경 정보를 버전 이력과 연결 | +| 접속 | `GeneralAccessLog`, `TrafficPeriodGeneral` | 최신 활동·누적 통계이며 모든 가입·로그인·장수 생성 시도의 사건 원장이 아님 | + +위 engine source의 생략한 prefix는 `app/game-engine/src/turn/`, game router는 +`app/game-api/src/router/`다. Prisma 모델은 `packages/infra/prisma/game.prisma`와 +`gateway.prisma`에 있다. + +Ref `hwe/_admin5.php`의 국가·평금쌀·병종 숙련 통계, `_admin7.php`의 전체 +장수 로그, `_admin8.php`의 외교정보는 **계승할 정보·조사 목적**의 근거다. +월별 상세 보존, 결정 과정, 조사 연결과 profile별 관리자 UX는 **의도적 제품 차이**다. +공통 계정 상관 조사와 알려진 버그 검색 프리셋은 **비교 불가인 Core 신규 기능**이다. +감사 기능을 추가하면서 Ref 계산이나 기존 일반 유저 권한을 변경하지 않는다. + +## 2. 화면·소유권·조회 계약 + +### 2.1 Profile 화면 + +현재 대상 URL은 `/che/play-audit`, `/hwe/play-audit`다. route는 각 frontend의 +base prefix를 사용하며 `/image/*`나 API URL을 root 배포 기준으로 하드코딩하지 않는다. +추가 profile의 공개 여부·동일 frontend 사용 여부는 실제 배포 설정으로 확인한다. + +탭은 `국가 추이 / 장수 / 도시 / 외교 / 정책 / 조사`다. NPC 결정은 장수 상세에서 +열고 관련 정책·실행으로 이동한다. 조사는 5절의 여섯 도구를 선택하는 진입 화면이다. +기간·국가·도시·장수·조사 종류는 URL에 유지한다. 민감한 계정·접속 식별자는 URL에 +넣지 않는다. 직접 진입, 새로고침, 뒤로가기에서도 선택한 공개 필터가 유지되어야 한다. + +각 frontend는 자신에게 맞는 화면과 조회 조합을 선택한다. 공통 계약은 데이터 의미, +권한, 식별자, 시간과 페이지 조회이며 모든 frontend에 동일한 대형 응답을 강제하지 않는다. +이번 구현은 현 CHE/HWE frontend에서 완료하고, 존재하지 않는 frontend를 새로 만들지 않는다. + +재사용 후보는 `GeneralInformationPanel`, `GeneralRecordPanels`, +`GeneralDirectoryTable`, `MapViewer`, `MapCityDetail`과 기존 외교 문서 표현이다. +인증·store·API가 결합된 view를 복제하지 않고 필요한 표시 부분만 분리한다. +현재 game frontend 내부에서 재사용 가능한 컴포넌트를 위해 선제적으로 공용 package를 +만들지 않는다. 차트는 공통 표시 wrapper에 숨기고 표로 같은 값을 조회할 수 있게 한다. + +### 2.2 API·권한 + +game-api가 `playAudit` 읽기 영역을 소유한다. 다음은 추가할 capability/API의 설계명이며 +현재 존재하는 endpoint가 아니다. 입력은 공통으로 대상·기간·cursor를 받고 상세는 별도 조회한다. + +| 조회 영역 | 최소 계약 | +| ----------------------------------- | ----------------------------------------------------------------------------------- | +| `capabilities`, `coverage` | 허용된 기능, 기수 identity, 수집 시작·누락 범위, 현재 조회 기준 시점 | +| `nationSeries` | 국가·기간·월/반기 해상도, 월별 집계에서 만든 시계열 | +| `generals`, `generalDetail` | 현재/월말, 국가·도시·종류·정렬, 목록과 선택 장수 상세 | +| `cities`, `cityDetail` | 현재/월말, 지도·도시 상태와 국가별 주둔 인원 | +| `diplomacy`, `policyHistory` | 국가쌍/국가·기간, 당시 문서/정책 버전과 변경 사건 | +| `npcDecisions`, `npcDecisionDetail` | 장수·기간·개인/수뇌턴, 선택 요약과 전체 과정 | +| 조사 A~F 조회 | 각 도구의 필터와 결과. 모든 source를 합치는 무제한 범용 검색 endpoint는 만들지 않음 | + +게임 감사에는 `admin.playAudit.read:` 범위를 추가한다. 공통 계정 자료는 +별도 `admin.playAudit.accounts` 권한과 해당 profile 감사 권한을 함께 요구한다. +권한 catalog·role 부여·game token·서버 검증과 권한 변경 시 flush를 함께 연결한다. +일반 운영 권한이나 인게임 직책만으로 감사 권한을 추론하지 않는다. 기존 superuser의 +명시적 전권 규칙은 기존 capability resolver를 따른다. + +인증 session에서 actor와 profile을 결정한다. `/play-audit`의 frontend와 backend 모두 +장수 보유를 요구하지 않는다. 기존 `getCurrentCity` 등의 장수 필수 경로를 우회 호출하는 +방식으로 권한을 구현하지 않는다. 게임 입장 제재 등 기존 인증 정책은 그대로 검사한다. + +Gateway는 계정 source와 권한 catalog만 소유한다. 공통 계정 자료는 인증·권한·대상 범위를 +전달하는 내부 읽기 adapter로 한정해 제공하며, Gateway에서 각 game DB를 대신 읽지 않는다. +일반 profile 감사 권한으로 타 profile의 장수나 접속 자료를 조회할 수 없어야 한다. +공통 계정 추가 권한은 조사 A에서 명시적으로 사용하며 조회 actor·대상·기간을 감사한다. + +향후 수뇌용 API는 기존 국가 권한 resolver로 자국 정책 이력만 반환한다. 계정·접속 정보, +외국 정책, 관리자 진단과 NPC 내부 trace를 같은 응답에 섞지 않는다. 수뇌 공개의 정확한 +직책·과거 가입 전 열람 정책과 UI 구현은 후속 기능에서 확정하며 이번 완료 조건에 넣지 않는다. + +## 3. 국가·장수·도시의 기록 의미 + +### R1. 국가별 재정·숙련 추이 + +국가 금·쌀·기술력, 적용 세율, 실제 수입·지급액, 유저/NPC별 금·쌀 총량·평균, +병종별 숙련도 `dex1..dex5`의 평균과 표본 장수 수를 기록한다. 경험·공헌은 숙련도와 +별도 표시한다. 장수가 없어도 국가는 목록에서 사라지지 않는다. + +분류는 당시 `npcState`와 owner 관계를 보존한 뒤 기존 게임의 의미로 결정한다. +기본 집계는 유저 장수 `npcState < 2`, NPC `npcState >= 2`이며 부대장 NPC +`npcState = 5`는 별도 집단으로 분리한다. 원본 상태 code와 빙의/소유 관계도 상세에 +남겨 유저 자동턴과 NPC를 실행 방식만으로 재분류하지 않는다. 재야는 국가와 별도 묶음이다. +각 평균은 해당 시점 집단의 합계/인원이며 표본이 없으면 null이다. + +수입은 `incomeHandler`의 세율·특성·도시 상태가 반영된 실제 계산 결과를 수집한다. +계산 수입, 실제 지급액, 국가 자원의 확정 전후 값을 구분한다. 최종 정수화·최저 자원 +보정·급여와 국가 잔액 차이를 세금 수입으로 오인하지 않는다. 정산이 없는 달의 실제 +수입은 0이며 이전 수입 metadata를 매월 새 수입처럼 누적하지 않는다. + +### R2. 모든 장수의 감찰 + +모든 국가·재야 장수를 검색·필터·정렬한다. 자원, 능력, 숙련, 장비·특기, 병력, +훈련·사기, 위치, 최근 전투·행동과 로그를 기존 감찰부 정보 수준으로 연결한다. +현재 예약 명령은 현재 데이터로, 과거 예약 변경과 실제 실행은 사건 이력으로 표시한다. +로그 일부 조회 실패가 다른 허용된 기록까지 숨기지 않게 각각 실패·재시도 상태를 둔다. + +### R3. 현재·과거 도시 + +도시의 인구/최대, 농업·상업·치안·성벽·수비와 각 최대값, 민심, 보급과 소유국을 +보존한다. 주둔 장수는 도시 소유국과 별개로 국가별로 묶고 병력·훈련·사기를 표시한다. +지도 선택 → 도시 상세 → 장수 상세에서 같은 월을 유지한다. + +### 공통 시간·집계 + +- 월말 snapshot은 기존 `beforeMonthChanged`의 지난달 마감 경계에서 수집한다. + 새로운 달의 월간 mutation과 섞지 않고 저장 transaction 완료 후 조회 가능하게 한다. +- 현재 조회는 확정 DB 상태를 같은 읽기 기준으로 읽고 `asOf`와 game tick을 반환한다. + 현재 화면의 여러 요청은 기준 시점 차이를 숨기지 않는다. +- 마감 전에 끝난 기수는 종료 시 최종 표본을 추가하며 정규 월말과 구분한다. +- 6개월은 1~~6월/7~~12월이다. 수입·지출은 기간 합, 보유량·기술력·장수 평균은 + 마지막 월말 값으로 표시한다. 월별 모드에서 각 원본 점과 표본 수를 확인한다. +- 월말 주둔은 그달의 모든 방문자를 뜻하지 않는다. 월중 이동은 조사 C의 행위 이력으로 본다. +- 당시 이름·소속·소유권과 기수 identity를 저장해 사망·멸망·개명 후에도 조회한다. +- 0, 표본 없음, 수집 누락, 도입 이전, 현재 미마감과 부분 반기를 구분한다. + 미래/범위 밖 연월은 입력 오류, 범위 안 미수집 월은 coverage가 있는 빈 결과다. + +## 4. 외교·NPC·정책의 사건 기록 + +### R4. 외교 연혁 + +국가쌍별 문서 제안·수정·서명·수락·취소/철회·복구와 실제 상태 전이를 기록한다. +문서의 버전/이전 문서 관계와 변경 주체를 연결한다. 선전포고, 개전, 불가침 파기, +종전과 월간 기간 만료도 대상이다. API 즉시 외교와 engine 월간 처리 경계를 모두 조사한다. +현재 `Diplomacy`를 월별로만 복사해서 같은 달의 여러 전이를 잃지 않는다. + +원문은 불변 버전을 한 번 저장/참조하며 조회 가능한 기존 불변 문서를 중복 저장하지 않는다. +원본이 갱신·삭제되는 경로는 필요한 당시 내용을 별도 보존한다. 일반 유저에게 숨겨진 +취소 문서도 관리자에게 당시 상태와 함께 반환한다. HTML은 기존 정화·출력 정책을 따른다. + +### R5. NPC의 전체 선택 과정 + +모든 NPC 개인턴·수뇌턴을 결정 단위로 기록한다. 유저 자동턴이 같은 AI 경로를 쓰면 +실행 주체를 별도 표기하며 계측 경로를 공유한다. 절차별 최소 내용은 다음과 같다. + +1. 게임 시각, 장수·국가·도시, 실행 종류, 정책 버전과 실행 코드 버전. +2. 실제 평가한 절차 순서와 우선순위, 정책으로 건너뛴 절차와 조기 반환 이유. +3. 평가한 후보·조건의 관측값/기준, 탈락·차단 이유와 사용한 난수 결과. +4. 최종 선택, 예약 우선 적용·대체 행동·휴식 등 선택 사유. +5. 요청 명령과 실제 실행 명령, 성공·실패·대체 결과, 관련 변경 사건 ID. + +평가되지 않은 경로는 실행된 것처럼 기록하지 않는다. 조건이나 RNG를 다시 호출해 +설명을 만들지 않고 원래 실행의 값을 관찰한다. 비밀 seed·token을 관리자 응답에 넣지 않는다. +선택 당시 필요한 값만 기록하며 매 후보마다 전체 world·정책·debug 객체를 복제하지 않는다. +결정 내 순번은 반드시 보존한다. 길다는 이유로 내용을 조용히 truncate하거나 일부 NPC만 +샘플링하지 않는다. 목록은 요약만 반환하고 큰 상세는 순번을 보존해 나누어 읽을 수 있다. + +### R6. 정책·국방 변경 이력 + +NPC 국가 정책, 국가/장수 우선순위, 국방 설정의 변경 전후와 actor·당시 소속/직책, +게임 시각, 실제 처리 시각, 관련 요청과 적용 버전을 남긴다. 초기 상태도 기준 버전으로 +기록한다. 변경 없는 저장은 새 정책 버전을 만들지 않으며 거부된 시도는 적용 이력과 구분한다. +CAS 충돌·실행 시점 권한 재검사와 기존 merge/clamp 계약을 그대로 유지한다. +NPC 결정은 적용한 불변 정책 버전을 참조하고 현재 정책으로 과거 설명을 덮어쓰지 않는다. + +## 5. R7: 질문별 여섯 조사 도구 + +도구들은 대상·기간·관련 사건 연결을 공유하지만 개별 source의 무제한 합집합 화면으로 +만들지 않는다. 아래의 질문, 필터, 결과, 추가 기록과 fixture는 각 도구의 완료 조건이다. + +### A. 계정·장수 생성 시도 + +**질문:** 같은 계정·접속지에서 어떤 생성을 반복했고 무엇이 허용/거부되었는가? + +- 입력: 계정/장수, 대상 profile, 실제 시간 범위, 가입·로그인·진입·생성 종류, + 성공/거부 사유. 공통 계정은 추가 권한을 확인한 뒤 선택한다. +- 결과: 접속지 비교 식별자별 계정과 시도 시각·횟수, 허용/거부 근거, 생성된 장수, + 같은 요청 재시도 여부. 해당 계정의 현재 기수 행위로 이동한다. +- 추가: 기존 기록에 없는 대상·결과·사유와 correlation만 수집한다. 원본 IP, 비밀번호, + token, OAuth credential, 무제한 request body는 저장하지 않는다. 페이지 조회마다 쓰지 않는다. +- 접속지 식별자는 신뢰된 proxy 경계에서 확인한 주소를 서버 비밀키로 HMAC 처리한다. + client 전달 IP나 단순 IP hash는 사용하지 않는다. key version을 보존하고 서로 다른 + key의 값을 동일하다고 단정하지 않는다. 키는 운영 secret이며 DB·응답에 넣지 않는다. +- fixture: 정상 생성, 생성 정책 거부, 요청 재전송, 다른 계정의 동일 접속지, + profile 범위 위반. 동일 접속지는 연관 근거이며 다중 계정 확정 표시가 아니다. + +### B. 자원 이동·재정 조사 + +**질문:** 금·쌀이 누구에게서 누구에게 왜 이동했으며 국가 재정 감소는 어디에서 발생했는가? + +- 입력: 계정·장수·국가, 금/쌀, 금액 범위, 기간, 명령/정산 종류와 상대방. +- 결과: 수입·급여·포상·몰수·수송·매매·경매 등 원인, 요청/실제 금액, 상대방, + 자원 전후와 실행 결과. 선택 기간의 상대방별 합계는 요청할 때만 계산한다. +- 추가: mutation이 이미 계산한 감사 대상 자원 차이와 원인·상대방. 장수/국가의 모든 + 자원 쓰기 경로를 inventory해 기존 기록으로 충분한 경로와 추가 수집 경로를 구분한다. +- fixture: 수입과 급여 동시 발생, 자원 부족으로 부분 적용/거부, NPC 정상 지급, + 반복 수송·포상, 경매와 취소/환급, 정수화·최저 보유량 보정. +- 기간 합계와 잔액 비교에는 수집 coverage를 표시한다. 세금·비용·보상·반올림을 + 구분하고 근거 부족 차이는 '설명되지 않은 차이'로 표시한다. 자동 버그 판정은 하지 않는다. + +### C. 명령·국가 운영 조사 + +**질문:** 누가 명령·정책을 바꿨고 이후 위치·병력·국가 운영이 어떻게 변했는가? + +- 입력: 수행자, 대상 장수·국가·도시, 명령 종류와 기간. +- 결과: 예약 변경 → 실행, 수동/NPC/유저 자동턴/관리자 구분, 당시 권한·대상과 + 이동·징병·출병·포상·몰수·인사·국방 설정 결과. +- 예: 도시 방비가 약해진 월을 선택해 이전/이후 주둔, 병력 변화, 이동·발령과 + 국방 정책 변경을 연결한다. 손실 자체를 트롤링으로 표시하지 않는다. +- 추가: 현재 큐에 남지 않는 예약 변경, 명령 출처·실행 연결, 영향받은 위치·병력 등 + 한정된 필드 차이. 모든 API 입력과 entity 전체 snapshot을 매번 저장하지 않는다. +- fixture: 수동 예약 교체, 수뇌 발령, NPC 대체 명령, 실패하여 이동하지 않은 명령, + 도시 점령·장수 소속 변경과 관리자 조치. 당시 원인을 찾을 수 있어야 한다. + +### D. 외교 분쟁 조사 + +**질문:** 합의는 언제 어떤 내용으로 성립했고 실제 전쟁·파기·종전은 언제 발생했는가? + +- 입력: 국가쌍, 게임 기간, 문서·상태·관련 명령 종류. +- 결과: 문서 버전·서명·수락·철회와 선전포고·개전·파기·종전을 한 시간축으로 비교. + 사건 직전의 유효 문서와 직후 상태, 관련 전투로 이동한다. +- 추가: R4의 기록을 그대로 사용한다. 별도 '분쟁 원장'에 문서와 사건을 복제하지 않는다. +- fixture: 같은 달 제안·수정·수락, 취소/복구, 기간 만료, 개전과 종전, + 문서 내용과 시스템 상태가 다른 사례. 어느 쪽도 다른 쪽의 값으로 대체하지 않는다. + +### E. 실행 실패·상태 불일치 조사 + +**질문:** 요청은 어디에서 실패했고 재시도가 중복 적용됐으며 결과와 상태 변화가 맞는가? + +- 입력: 요청/실행 식별자, 수행자, 명령, 오류 종류와 기간. +- 결과: 접수 → 처리 시도 → 확정 성공/실패 → 상태 변화·로그. 전송 재시도, + engine 재처리, 업무 거부, transaction rollback을 구분한다. +- 추가: 기존 `InputEvent`, `ErrorLog`, `LogEntry`의 연결 정보와 필요한 실행 결과만 + 보완한다. API digest는 원문이 아니고 최종 dirty state는 개별 실행 이력이 아니다. +- 선택한 실행·기간에서 요청/적용 금액과 기록된 상태 변화 연속성을 비교한다. + 예전 실패를 현재 상태로 재판정하지 않는다. 입력 전 인증 거부와 journal 이후 실패도 구분한다. +- fixture: 정상 성공, 업무 거부, 전송 재시도, lease 상실/rollback 후 재실행, + 같은 요청 ID의 입력 충돌, 오류 로그는 있으나 상태 변화가 없는 사례. + +### F. 알려진 버그 사례 조회 + +**질문:** 확인된 버그의 조건에 해당하는 실행이 있었고 수정 전후 결과는 무엇인가? + +- 개발자가 사례 ID·설명·영향 코드 버전·관련 명령·입력 조건·필요 증거를 선언한다. + SQL 문자열 대신 검증된 기존 조사 필터 조합을 사용한다. +- 관리자가 사례·기간을 선택할 때만 후보를 조회하고 실제 입력·변경·오류·NPC 근거로 + 이동한다. 후보 일치, 버그 발생 확인과 악용 의도 판단을 분리한다. +- 등록할 실제 사례는 현재 코드/회귀 fixture에서 증명된 것만 선택한다. 알려진 버그가 + 없으면 빈 목록과 원인을 표시하며 임의로 활성 버그 사례를 만들지 않는다. +- fixture에는 테스트 전용 확인 사례를 사용해 정상/해당/버전 범위 밖/자료 부족을 검사한다. + 수정 버전 이후도 명시적으로 비교할 수 있고 버전 누락은 '판정 자료 부족'으로 표시한다. +- 임의 SQL 실행기, 사용자 규칙 편집기, 상시 탐지 worker, 자동 제재와 새 사건관리 + workflow는 이번 범위가 아니다. 조치는 기존 관리자 제재 절차를 사용한다. + +## 6. 기록·실패·보존 계약 + +### 6.1 공통 식별자와 내구성 + +월별 상태, 정산/행위 사건, 외교 전이, 정책 버전과 NPC 결정은 공통으로 profile, +불변 기수 identity, 게임 연월/tick, 실제 기록 시각, 대상, 원인 요청/실행 ID를 연결한다. +기수 표시 번호·연월·장수 ID만으로 기수를 식별하지 않는다. 같은 ID를 쓰는 다음 기수와 +구분하도록 현재 `serverId`의 생성·RESET 계약을 검증하고 감사 key에 사용한다. +동일 tick의 여러 사건에는 실행 순번을 보존한다. + +engine은 기존 메모리 상태·mutation 결과에서 수집해 pending 감사 자료를 +`EngineStateManager` rollback과 dirty acknowledgement에 포함한다. gameplay flush와 +감사 insert가 같은 PostgreSQL transaction에서 commit되고 실패 시 둘 다 rollback된다. +행위/월별 snapshot/정책 버전에는 실행 ID와 종류·순번 기반 unique key로 중복을 막는다. +알림은 commit 뒤 보내며 `ChangeJournal`/Redis를 감사 원장으로 재구성하지 않는다. + +API 즉시 mutation도 해당 transaction에서 사건을 기록한다. 인증 전 거부·가입 실패와 +rollback된 시도의 진단은 별도 시도/오류 기록으로 남기되 gameplay 성공 사건으로 만들지 +않는다. 외부 transaction rollback 전송 실패까지 '모든 시도가 반드시 기록됨'으로 +주장하지 않는다. 관측하지 못한 구간은 coverage/운영 오류로 표시한다. +확정 gameplay 감사 저장 실패는 성공으로 숨기지 않고 기존 transaction 실패·복구 경계를 따른다. + +### 6.2 초기 도입·종료·초기화 + +기존 DB에 migration만 적용해 과거 상세 이력을 조작하지 않는다. 초기 현재 상태와 정책 +기준 버전, 수집 시작 시점을 고정한다. 기존 연감/문서는 실제 보유 범위만 조회하고 새 +snapshot과 source를 구분한다. migration은 빈 DB·증분·재실행 no-op을 모두 검증한다. + +기수 종료 후 감사는 다음 초기화까지 읽을 수 있다. RESET은 새 기수 identity를 활성화해 +이전 기수 조회를 즉시 차단하고, 이전 감사 자료를 key 기준으로 제한된 batch 정리한다. +정리 실패는 재시도 가능해야 하며 새 기수 행을 삭제하지 않는다. 별도 장기보존 archive나 +기존 연감·계정 원장의 보존 정책은 바꾸지 않는다. 정리 전 backup·현재 RESET 보호 절차를 +따르고, 이 문서 구현이 임의 운영 데이터 삭제 권한을 뜻하지 않는다. + +공통 계정 신규 조사 기록은 wall clock 기준 30일이다. 조회에서도 만료된 행을 제외하고 +삭제 worker는 작은 batch로 정리한다. 기존 계정/관리자 원장은 그대로 둔다. 과거 링크의 +대상이 만료·초기화·미수집이면 그 이유를 반환하며 '사건 없음'으로 표시하지 않는다. + +## 7. DB 비용 검토와 조회 제한 + +### 7.1 수집 inventory와 예상 비용 + +구현 전에 각 행을 실제 source·필드 allowlist·SQL count·평균 bytes·삭제 방식으로 +구체화한다. 아래 비용은 목표 구조의 식이며 측정값이 아니다. +`G/C/N`은 해당 월의 장수/도시/국가 수, `M`은 월 수, `D`는 AI 결정 수, +`E`는 감사 대상 사건 수, `P/L/A`는 정책/외교/계정 사건 수, `B`는 batch 크기다. + +| 자료 / 목적 | 기존 근거·추가할 값 | 수집 빈도와 DB 목표 | 보존량·조회 경로 | +| ----------------- | ------------------------------------------------- | ------------------------------------------------------- | ---------------------------------------- | +| 월별 상태 / R1~3 | 메모리 world, 기존 연감과 중복 제외한 필요한 필드 | 월말 1회, 추가 전체 SELECT 0, 종류별 batch insert | `O(M×(G+C+N))`, 월·국가·도시로 좁혀 조회 | +| 국가 월집계 / R1 | 같은 snapshot 순회에서 합계·분모·정산 합계 | 국가마다 장수 재조회하지 않음, batch 저장 | `O(M×N)`, 6개월 집계는 여기서 계산 | +| 정산·행위 / B,C,E | 기존 요청·로그 참조 + 실제 변경 필드/상대방 | 실행당 compact 사건, 처리 batch당 `O(ceil(E/B))` insert | `O(E)`, 대상·기간·종류·원인 ID | +| NPC trace / R5 | 절차에서 이미 관측한 값 + 정책 버전 | 결정 단위 payload, 조건·후보별 SQL 0 | `O(D×평균 trace bytes)`, 목록/상세 분리 | +| 정책 / R6 | 기존 현재 정책 + 변경 전후와 actor | 실제 변경 시에만 버전 1회 | `O(P)`, 국가·버전·기간 | +| 외교 / R4,D | 기존 불변 문서 참조 + 상태 전이 | 전이 시 기록, 원문 중복 제외 | `O(L)`, 국가쌍·기간 | +| 계정 / A | 기존 인증 처리 결과 + 비교용 식별자·사유 | 가입·로그인·진입·생성 시도, 일반 페이지 조회 0 | 최근 30일 `O(A)`, 대상/비교 식별자·시간 | +| 사례 / F | B~E의 필터와 자료 | 새 gameplay write 0 | 요청한 사례·기간만 조회 | + +NPC trace의 정책 참조와 사건 연결 외에 매 턴 전체 world를 serialize하지 않는다. +월별 수집은 국가별 전체 목록 filter 반복을 피하고 `O(G+C+N)` 한 순회로 묶는다. +기존 원본의 수명이 충분하면 복사 대신 FK 또는 안정 참조를 쓰고, 원본 만료로 조사 근거가 +깨지는 경우에만 최소 불변 내용을 보존한다. batch 크기는 parameter/payload 제한을 +측정해 정하고 한 번의 거대한 INSERT로 DB 왕복만 줄였다고 완료하지 않는다. + +### 7.2 읽기 계약 + +- 탭 진입에는 그 탭의 요약만 조회한다. 로그·NPC 과정·문서 원문은 상세를 열 때 읽는다. +- 목록은 기본 50건, 최대 200건이며 안정 순서 `(시각, ID)`의 cursor pagination을 쓴다. + 전체 count와 상대방별 합계는 기본 응답에서 제외하고 요청할 때만 계산한다. +- 조사 기본 기간은 최근 게임 1개월, 공통 계정은 최근 24시간이다. 명시적으로 현재 기수 + 또는 30일까지 확장할 수 있다. 기간·대상·사건 종류로 먼저 좁히고 무제한 본문 검색은 제외한다. +- 장수별 N+1 쿼리나 화면 진입 시 전체 source 합치기를 금지한다. 한 페이지의 관련 ID는 + 묶어 조회한다. 선택한 범위 밖의 원문과 trace를 목록 응답에 포함하지 않는다. +- 마감 월은 profile·기수·권한 projection·schema version을 포함한 key로 캐시한다. + 권한 검사는 cache hit에도 적용하며, 다른 actor의 비공개 응답을 공유하지 않는다. +- 현재 자료는 수동 새로고침이 기본이다. 백그라운드 탭 polling과 감사 화면용 신규 + 상시 스캔 worker를 만들지 않는다. 기준 시각과 오래된 자료 상태를 표시한다. +- 실제 WHERE/ORDER BY에 맞춘 인덱스만 추가한다. JSON 전체에 범용 GIN을 자동으로 + 붙이지 않는다. 장기 SELECT가 gameplay row lock을 잡지 않도록 읽기 transaction을 제한한다. +- 쿼리 timeout/범위 과다에는 오류와 기간 축소 안내를 반환한다. timeout을 빈 결과로 + 처리하거나 일부 결과를 완전한 기간 합계처럼 표시하지 않는다. + +### 7.3 비용 검증 gate + +대표 scenario의 정상 진행, NPC 많은 진행, churn·전쟁, 기수 말 많은 기록, 월간 동시 +정산을 대상으로 동일 입력의 계측 전/후를 비교한다. 실제 크기와 명령은 실행 보고서에 고정한다. + +측정은 SQL 왕복 수·읽은/쓴 행, 감사 payload·테이블·인덱스 bytes, WAL, transaction +시간, 월 경계 소요, 조회 p50/p95, retained heap과 최대 pending trace를 포함한다. +실행 계획은 격리 DB의 실제 조회로 확인한다. 기수 저장량은 측정된 평균 bytes와 +위 식으로 추정하고, 30일 계정 기록도 별도로 산정한다. 수치는 측정 전 확정하지 않는다. + +필수 gate는 추가 전체 재조회 0, 후보별 SQL 0, 목록 N+1 없음, 기수 전체 raw trace +전송 없음, bounded 정리, RNG/상태 일치다. 월별 snapshot·NPC trace·행위 원장·계정 +원장을 추가할 때마다 기존 자료 재사용 및 더 작은 기록 형태를 한 번 더 검토하고 결과를 +비용 표에 남긴다. 비용이 크면 중복·반복 query·직렬화·batch/index를 먼저 개선한다. +자료 샘플링·생략·보존기간 축소는 성능 최적화로 몰래 처리하지 않고 명시적 설계 변경으로 다룬다. + +## 8. 단계·검증·goal 인계 + +### 8.1 순차 구현 체크리스트 + +각 단계는 코드·mapping·fixture·보고서·관련 commit을 포함한다. 병렬 branch나 +별도 goal을 자동 생성하지 않는다. 완료한 단계가 전체 구현 완료를 대신하지 않는다. + +- [ ] P1. source/쓰기 inventory, 비용 표, 기수 identity와 수집 시작, 권한·API 계약, + migration·rollback·보존 정리 경계를 구현한다. +- [ ] P2. 월별 상태·국가 집계와 R1~R3 profile 화면을 구현한다. +- [ ] P3. R4/R6 외교·정책 버전과 조회, 수뇌 공개용 projection 경계를 구현한다. +- [ ] P4. R5 모든 NPC 경로 계측, 행위·자원 변화와 요청/실행 연결을 구현한다. +- [ ] P5. 조사 A~F, 공통 계정 adapter·30일 정리, 권한별 UI를 구현한다. +- [ ] P6. 아래 검증을 수행하고 비용·coverage·미검증 범위를 보고해 전체 완료를 판정한다. + +### 8.2 요구사항별 증거 + +모든 상태는 현재 **미구현**이다. 후속 보고서에 명령·artifact·commit을 연결해야만 체크한다. + +| ID | 합격 기준 | 필요한 증거 | +| ------- | ------------------------------------------------------------- | -------------------------------------------------------------------------- | +| R1 | 월/반기, 실제 정산, 집단 분모·0/null·부분 기간 정확 | 정산 fixture, PostgreSQL reload, 그래프와 표 값 비교 | +| R2 | 모든 국가·재야 장수 현재/과거 상세, 허용 로그의 독립 표시 | 권한 HTTP matrix, 사망/개명 fixture, Chromium | +| R3 | 당시 소유·내정·국가별 주둔과 병력/훈련/사기 | 월중 이동·점령·월말 fixture와 DB, 지도/장수 drill-down | +| R4 | 같은 달 복수 외교 전이와 당시 유효 문서 | API 즉시 처리·engine 만료/개전·취소/복구 DB fixture | +| R5 | 전체 개인/수뇌 AI 경로의 판정·선택·실행 연결 | handler inventory, 정책 차단·조기 반환·fallback·실패 trace, fixed seed A/B | +| R6 | 실제 적용 버전·actor·전후, CAS 거부 분리 | 충돌/무변경/직책 변경 fixture, 과거 결정의 정책 참조 | +| R7-A~F | 5절 각 질문을 정상·해당·근거 부족 사례로 조사 가능 | 도구별 API/DB fixture와 Chromium 탐색 artifact | +| AUTH | no-general 관리자 허용, 다른 profile/일반 유저/수뇌 비밀 차단 | 실제 HTTP token·scope·권한 revoke·cache matrix | +| DURABLE | gameplay/audit 원자성, 중복 방지·rollback·재시작·RESET 분리 | 실제 PostgreSQL migration 전체/증분/no-op, 실패·복구·정리 fixture | +| COST | 7절 필수 gate와 계측 전후 비용 검토 완료 | SQL count·실행계획·WAL/bytes·p95·heap 측정 보고 | + +계측 전후 fixed seed 비교는 명령뿐 아니라 RNG 소비 순서, state, 기존 로그와 +side effect까지 포함한다. 계승 계약에 영향을 준 경로는 Ref 차등을 추가하고 감사 UI 자체는 +신규 Core 계약으로 검증한다. 현재 문서 작업에서 Ref 실행·DB·Chromium을 수행한 것은 아니다. + +GUI는 같은 Chromium·viewport·DPR·zoom·font·fixture 조건에서 실제 `/che/play-audit`, +`/hwe/play-audit`의 direct URL·refresh·asset/API·필터·기간 이동·뒤로가기·상세 열기와 +desktop/mobile geometry를 확인한다. screenshot·DOM·computed style·측정 artifact를 남긴다. +실제 공개 HTTPS 검증은 배포가 별도 승인·수행된 경우에만 보고하며 fixture 결과와 구분한다. + +### 8.3 후속 goal에 사용할 지시문 + +> `core2026/docs/design/play-audit.md`의 확정 계약을 기준으로 프로필별 플레이 감사를 +> 구현한다. R1~~R7-A~~F와 AUTH/DURABLE/COST 전체를 완료하며 P1~P6를 순서대로 진행한다. +> 사용자 수정사항이 문서보다 우선한다. 현재 Git/source를 재확인하고 기존 dirty 작업을 +> 보존한다. 기능마다 조사 근거, 수집 coverage, DB 비용 재검토와 실제 검증 artifact를 +> 남긴다. 문서·일부 UI·unit test만으로 전체 goal을 완료하지 않는다. 관련 변경은 저장소별로 +> commit하고 push·배포는 별도 요청 범위로 둔다. 자동 탐지/제재, 수뇌 화면, 지난 기수 +> 장기보존이나 다른 frontend 신규 개발로 범위를 확대하지 않는다. + +문서 수정은 결정 이유와 영향을 받는 요구사항·비용·검증을 함께 변경한다. +실행 일자·결과·미검증·commit은 상위 `report/`에 기록한다. 이 문서의 체크박스는 +단순 계획·시도·의도로 완료 처리하지 않는다. diff --git a/docs/developer/index.md b/docs/developer/index.md index a7f847a4..8eebe818 100644 --- a/docs/developer/index.md +++ b/docs/developer/index.md @@ -2,6 +2,10 @@ ## 읽기 순서 +신규 관리자 플레이 감사의 확정 범위, 수집·조회 비용과 후속 구현 체크리스트는 +[프로필별 플레이 감사 설계](../design/play-audit.md)에 있습니다. 해당 기능은 +아직 미구현이며 아래의 현재 구조 설명과 상태를 구분합니다. + | 작업 | 문서 | 코드 시작점 | | --------------------- | --------------------------------------------------------------- | ------------------------------ | | 전체 구조 | [아키텍처 개요](../architecture/overview.md) | `app/`, `packages/`, `tools/` | diff --git a/docs/index.md b/docs/index.md index 1178f2e3..921db3cc 100644 --- a/docs/index.md +++ b/docs/index.md @@ -39,6 +39,8 @@ Gateway 배포는 [릴리스 운영 매뉴얼](./release-operations.md)을 따 상단 링크와 dropdown을 바꾸는 JSON 형식과 복구 경계를 설명합니다. 관리자 화면의 메뉴와 권한·운영 경계는 [관리자 콘솔](./admin-console.md)에서 확인할 수 있습니다. +[프로필별 플레이 감사 설계](./design/play-audit.md)는 현재 미구현인 국가·장수·도시· +외교·NPC 감사와 질문별 조사 도구의 구현 기준 및 DB 비용 검토를 정의합니다. 게임 진행 시각과 운영 벽시계의 경계는 [게임 시계](./architecture/game-clock.md)에 설명합니다. [패키지와 파일 경계](./architecture/package-boundaries.md)는 source import와 @@ -51,6 +53,7 @@ Gateway 배포는 [릴리스 운영 매뉴얼](./release-operations.md)을 따 - `architecture/`: 현재 runtime, action module, scenario와 차등 검증 계약 - `developer/`: 파일 위치, 도메인 조립, 요청·저장 흐름 - `user/`: 화면, 시간, 국가 기능과 생성된 command catalog +- `design/`: 확정한 신규 기능의 구현 목표·비용·검증 계약과 미구현 상태 - 루트 문서: 통합 테스트, Chromium 비교, Caddy, DB 이관과 운영 절차 작업 이력은 상위 작업공간의 `report/`에 보존합니다. ref PHP와 core2026의