Files
core2026/docs/design/play-audit.md
T

440 lines
37 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 프로필별 플레이 감사 설계
## 문서 상태와 사용법
**설계 기준: 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:<profileName>` 범위를 추가한다. 공통 계정 자료는
별도 `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/`에 기록한다. 이 문서의 체크박스는
단순 계획·시도·의도로 완료 처리하지 않는다.