중복 문서를 통합하고 완료된 구현 계획을 정리한다
This commit is contained in:
@@ -40,7 +40,6 @@ export default defineConfig({
|
||||
{ text: '독자별 읽기 순서', link: '/developer/' },
|
||||
{ text: '기초 구조 안내', link: '/developer/first-steps' },
|
||||
{ text: '경험자를 위한 시스템 읽기', link: '/developer/system-walkthrough' },
|
||||
{ text: '구조 문서 찾아보기', link: '/developer/reference-map' },
|
||||
{ text: '아키텍처 개요', link: '/architecture/overview' },
|
||||
{ text: '런타임 아키텍처', link: '/architecture/runtime' },
|
||||
{ text: '관리자 콘솔', link: '/admin-console' },
|
||||
|
||||
@@ -1,716 +0,0 @@
|
||||
> 2026-09-26 정책 변경: 아래 단계별 검증 기록의 감사 실패 시 gameplay rollback은 당시 계약이다.
|
||||
> 현재는 [기록·실패 계약](./play-audit.md#_6-1-공통-식별자와-내구성)에 따라 감사 savepoint만
|
||||
> 취소하고 게임을 계속한다. 이력 누락·업데이트 경계의 단절을 허용하며 기존 원장은 수정하지 않는다.
|
||||
|
||||
# 플레이 감사 구현 기록과 수집 inventory
|
||||
|
||||
[확정 설계](play-audit.md)의 P1~P6 구현 기록이다. 현재 월별 상태·국가 시계열,
|
||||
장수/도시 상세와 로그, 외교 이력, 정책 버전과 프로필별 관리자 진입을 사용할 수 있다.
|
||||
NPC 결정 trace와 조사 A~F의 완성, 전체 종료 경계 및 COST gate는 남아 있다.
|
||||
2026-09-16 사용자는 주간 한도 약20%를 남기고 사용 가능한 기능과 DB migration을
|
||||
정리·통합·push하는 중간 전달을 요청했다. 전체 설계 완료와 이 전달을 구분한다.
|
||||
실제 적용 방법과 제한은 [운영 안내](../play-audit-operations.md)를 따른다.
|
||||
|
||||
## 현재 구현
|
||||
|
||||
### NPC 저장 비용 probe
|
||||
|
||||
`app/game-engine/test/playAuditDecisionCost.integration.test.ts`는 별도
|
||||
`PLAY_AUDIT_COST_DATABASE_URL`이 있을 때만 실행한다. schema는 정확히
|
||||
`play_audit_cost_decision_fixture`여야 하며 그 schema의 결정/chunk를 비운 뒤 측정한다.
|
||||
운영 URL을 사용하지 않는다. 정식 game migration을 먼저 적용하고 다음 명령을 실행한다.
|
||||
|
||||
```sh
|
||||
pnpm --filter @sammo-ts/game-engine test playAuditDecisionCost.integration.test.ts --no-file-parallelism
|
||||
```
|
||||
|
||||
201개 결정×302step을 실제 persistence로 저장해 200개 batch 경계를 넘긴다.
|
||||
201header/603chunk/60,702step을 검사하고 JSON bytes, pg_column_size 합계,
|
||||
테이블·TOAST 포함 할당과 index bytes, 저장 transaction 시간, warm 목록30회
|
||||
p50/p95와 EXPLAIN ANALYZE BUFFERS를 `/tmp/play-audit-decision-cost.json`에 기록한다.
|
||||
비밀·payload 본문·DB URL은 출력하지 않는다. 반복 실행에서는 이전 할당 공간이 남을 수 있다.
|
||||
|
||||
2026-09-16 격리 PG 결과는 JSON12,271,836B, header행101,304B/chunk행666,924B,
|
||||
저장677.56ms, 목록51건 p50 0.76ms/p95 1.01ms였다. 월별 장수 index scan으로51행을
|
||||
읽었다. 반복 RNG 합성 fixture는 압축률이 높으므로 평균 운영 trace 크기의 근거가 아니다.
|
||||
추가 query event 계측에서는 동일201결정의 저장에 INSERT6회(헤더2/chunk4),
|
||||
SELECT2회(hash 확인), COMMIT1회가 관측됐다. BEGIN 등 driver 내부 통신은 event에
|
||||
나타나지 않으므로 네트워크 왕복 전체로 해석하지 않는다. test는 INSERT/SELECT 수를
|
||||
assert하며 SQL/params 원문은 저장하지 않는다. 관측60,702단계마다 SQL을 쓰지 않고 batch당
|
||||
저장·hash 조회를 확인한 범위다. WAL·retained heap, 실제 scenario 계측 전후와 전체
|
||||
COST gate는 여전히 남는다.
|
||||
|
||||
### 정책·외교 사건의 요청 처리 상태
|
||||
|
||||
`playAudit.requestState`는 현재 기수의 정책 버전 또는 외교 사건 ID만 받는다.
|
||||
참조된 requestId와 inputSequence가 모두 실제 input_event와 일치할 때 현재 상태,
|
||||
처리 시도 횟수·접수/처리 tick·시계 버전·시각과 결과/오류 존재 여부를 반환한다.
|
||||
기준 기록의 연결 없음, 불완전 참조, 삭제된 요청과 순번 불일치를 구분한다.
|
||||
임의 requestId 검색, payload/result/error 원문·계정·lease owner 조회는 제공하지 않는다.
|
||||
|
||||
권한 검사 뒤 읽기 transaction 안에서 world, 사건 한 행, unique request_id 한 행을
|
||||
최대3회 읽는다. SQL은 필요한 scalar와 존재 여부만 투영하며 쓰기·COUNT·전역 검색이 없다.
|
||||
정책·외교 상세의 공통 component에서 버튼을 눌러 조회하며 재시도는 이 요청만 반복한다.
|
||||
다른 버전으로 바꾸면 결과를 지우고 진행 중 응답을 무시한다. 자동 조회/polling은 없다.
|
||||
R7 E의 연결 기반 일부이며 전체 실패·시도별 이력, 실제 mutation 횟수와 replay가 아니다.
|
||||
F의 알려진 버그 조사 preset은 아직 구현하지 않았다. schema migration58은 그대로다.
|
||||
|
||||
### 당시 합성 정책 관측
|
||||
|
||||
DECISION_START.effectivePolicy(schemaVersion1)는 생성자에서 이미 합성된
|
||||
General/Nation 정책 객체의 명시 필드를 복사한다. 서버→국가 설정, 사용자 자동턴 옵션,
|
||||
NPC 상태와 기술력/병종/시나리오 기반 파생 기본값이 반영된 실제 priority/flags,
|
||||
20개 국가 수치와 전투/지원/내정 부대 편성을 보존한다. 원문 meta 전체를 복사하지 않는다.
|
||||
can()/조건/RNG를 재평가하지 않고 후보당 SQL도 추가하지 않는다. 결정당 상세에1회씩
|
||||
기록하며 목록 summary에는 넣지 않는다. 기존 chunk/batch/transaction과 hash 계약을 따른다.
|
||||
|
||||
API는 허용 필드로 투영하고 UI는 기존 정책 한글 label을 재사용한다. details를 펼쳐도
|
||||
추가 query가 없으며 이전 DECISION_START에 field가 없으면 미수집으로 표시한다.
|
||||
현재 정책으로 과거를 채우지 않는다. runtime의 동적 지급 상한·개별 후보 수치·별도
|
||||
자동화 권한의 판정 입력은 여전히 후속 관측이다. 초기 비용 probe의 반복 RNG fixture는
|
||||
이 신규 정책 payload를 포함하지 않으므로 그 bytes를 완성된 결정의 평균으로 쓰지 않는다.
|
||||
|
||||
### NPC 결정 조회 API와 화면
|
||||
|
||||
`playAudit.decisionHistory/decisionDetail`은 같은 프로필 감사 권한·현재 기수 경계를 따른다.
|
||||
장수별 조회는 선택 월과 phase, `(tick,id)` 내림차순 cursor를 사용한다. 월을 지정하지
|
||||
않으면 현재 기수·장수·phase의 가장 최근 결정이 있는 월을 인덱스로 1건 찾는다.
|
||||
명시한 과거 월은 비어 있어도 다른 월로 바꾸지 않는다. API는 실제 조회 `month`,
|
||||
현재 게임 `currentMonth`, `selection`을 구분하며 더보기는 최초 조회 월에 고정한다.
|
||||
화면에는 턴 실행 후 저장되는 시점, 최근 기록 월/현재 월, 이전·다음 월과 최근 결정
|
||||
조회 버튼을 제공한다. 월말 표본과 달리 NPC 결정은 매 턴의 game flush에 저장된다.
|
||||
목록은50건 기본/200건 상한으로 요약만 읽고, 상세는 명시 선택 시128 event chunk를
|
||||
기본1개/최대4개 읽는다. header의 stepCount로 다음 chunk를 판단해 추가 본문이나 COUNT를
|
||||
읽지 않는다. summary/step은 허용 필드만 투영하며 seed/raw metadata를 반환하지 않는다.
|
||||
같은 tick의 개인·수뇌 결정 및 사망 장수의 현재 기수 기록도 조회한다.
|
||||
|
||||
migration58은 기존 장수 인덱스를 `(server, general, year, month, tick, id)`로 교체한다.
|
||||
월 조건 밖의 기수 기록을 훑지 않으며 인덱스 개수는 늘리지 않는다. UI는 기존 장수 상세와
|
||||
버튼·표 스타일을 재사용한다. 열기/상세 선택/더 보기는 명시적으로 수행하고 polling하지
|
||||
않는다. 결정 URL 복원과 상세 재시도는 상위 장수 목록을 다시 읽지 않는다.
|
||||
절차 coverage와 미수집 코드 버전을 표시하며 전체 후보 조건 관측은 남는다.
|
||||
당시 정책 참조는 공용 `AuditPolicyVersion`으로 연결했다. 기존 정책 이력의 버전 표시·
|
||||
필드 한국어 이름·실패 재시도를 재사용하며 클릭 시 버전1건만 읽는다. 결정의 참조 ID에
|
||||
포함되지 않은 URL policy는 해당 결정의 정책으로 읽거나 표시하지 않는다.
|
||||
국가의 저장 설정과 NPC별 합성 유효 값은 구분하며 현재 설정으로 보충하지 않는다.
|
||||
|
||||
### NPC 실행 단계와 대체 명령
|
||||
|
||||
선택 이후 `runAction`이 실제 수행한 인자/조건/재사용 대기/실행 문맥 검사를
|
||||
`EXECUTION_ATTEMPT`로 상세 chunk에 기록한다. depth별 요청·해석·실행 명령과 결과,
|
||||
대안 명령, 준비 term/total을 남긴다. 검사 함수를 다시 호출하지 않으며 실제로 건너뛴
|
||||
검사는 추가하지 않는다. 준비·블럭으로 명령을 실행하지 않았으면 executedAction은 null이다.
|
||||
원래 resolve 결과의 alternative를 따라간 순서와 공유 RNG를 그대로 유지한다.
|
||||
|
||||
저장 순번은 선택 관측과 실행 시도를 하나의 증가 순서로 매긴다. 기존 외부 AI observer의
|
||||
원래 sequence는 변경하지 않는다. `DECISION_END`는 선택 종료이며 새 저장 기록은 실행
|
||||
시도 뒤 종료한다. `executionCoverage=ATTEMPTS`로 기존 선택만 있는 기록과 구분하고,
|
||||
이 flag가 있는데 실행 시도가 없으면 persistence가 거부한다. 과거 hash/행을 재작성하지 않는다.
|
||||
요약의 대체 여부에는 이전 단계의 대안/휴식도 반영한다. 실제 게임 실행 결과 객체는 바꾸지 않는다.
|
||||
|
||||
API는 검사/결과의 허용 필드만 읽고 UI는 순서와 한국어 검사명을 표시한다. 목록에 상세를
|
||||
추가하지 않는다. 체크 배열은 시도당 최대4개(블럭은1개), 기존 대안 depth 한도5를 유지한다.
|
||||
전체 월간 부하 gate와 throw/rollback 실행의 별도 실패 원장, 내부 후보 조건은 후속이다.
|
||||
|
||||
### NPC 결정 저장·복구 기반
|
||||
|
||||
새 migration57은 `play_audit_decision` 요약과 `play_audit_decision_chunk` 상세를 분리한다.
|
||||
reservedTurnHandler가 기수 identity가 있는 새 AI 실행을 phase별로 모으고 실제 요청/선택/
|
||||
실행·성공/대체 결과를 함께 반환한다. 실행 tick은 해당 장수 기준, ID는 serverId·장수·tick·
|
||||
clock revision·phase로 결정한다. 정책 head 참조는 시작 시 확보하며 없는 값은 채우지 않는다.
|
||||
Gateway의 프로필 buildCommitSha→daemon 환경 TURN_BUILD_COMMIT_SHA→CLI→runtime→handler로
|
||||
실행 코드 버전을 전달한다. 전체40/64자리 SHA만 인정하며 누락/잘못된 값은 null이다.
|
||||
handler 생성 시 한 번 정규화하므로 턴마다 Git/DB를 읽지 않는다. 실행 직전 동적 정책 파생값과
|
||||
내부 후보 조건은 남아 있어 coverage는 `PROCEDURES`다. 수동 턴 중 AI를 사용하지 않은 경우 결정 행을 만들지 않는다.
|
||||
|
||||
GeneralTurnResult→world pending→capture/restore/peek/ack→기존 fenced DB flush를 연결했다.
|
||||
요약 hash와 실행/phase unique로 같은 재시도는 중복 없이 통과하고 다른 payload는 실패한다.
|
||||
상세는128개 event씩 나누며 최대200행씩 batch insert한다. 목록을 위해 전체 trace를 하나의
|
||||
header JSON으로 저장하지 않고 후보별 SQL도 추가하지 않는다. 실패하면 게임 상태와 모두
|
||||
rollback되고 commit 뒤에만 pending prefix를 비운다. 실패한 실행의 별도 진단 원장은 남는다.
|
||||
|
||||
정리는 이전 기수 header를 잠그고200개 chunk씩 삭제한 후 header를 제거한다. FK RESTRICT로
|
||||
무제한 cascade를 막으며 현재 기수는 기존 schema lock/identity 재검사로 보호한다.
|
||||
빈57·기존56→57·재실행 no-op,302 event/3chunk 복원, 삽입 실패/재시도/충돌 및 실제
|
||||
DB hooks와 정리 회귀를 검증했다. 전체 비용 gate는 후속이며 전용 API/GUI는 위 조회 절에 연결했다.
|
||||
|
||||
### NPC 판단 관측 기반
|
||||
|
||||
GeneralAI와 예약 실행 handler에 선택적 `onDecisionTrace` 관측 경계를 추가했다.
|
||||
공유 AI의 수뇌→개인 결정 순서에 동일 sequence를 유지하며 시작/최종 선택/오류,
|
||||
우선순위 절차 진입·결과, 정책/자동화/handler 부재 skip, 명령 후보 validation 결과와
|
||||
실제로 호출한 RandUtil 결과를 관측한다. 예약 우선 반환 뒤의 절차는 만들어내지 않는다.
|
||||
메서드를 재호출하지 않으며 RandUtil 내부 helper는 중복 사건으로 기록하지 않는다.
|
||||
원문 seed/meta/debug 객체를 복제하지 않고 난수 결과의 객체는 ID만 투영한다. 투영할 수
|
||||
없는 값은 `unprojected`로 명시하며 완전한 후보 상세라고 주장하지 않는다.
|
||||
|
||||
고정 seed3종에서16개 RNG utility 호출의 반환값·객체 identity·다음 RNG 결과가 같고,
|
||||
실제 NPC 선전포고→개전→점령 fixture에서도 수집 on/off 회귀가 통과했다.
|
||||
초기 관측 단계에서는 default daemon을 켜지 않았다. 이후 아래 저장 경계에서 현재 기수의 새 실행을 수집하도록 연결했다.
|
||||
이는 R5의 관측 기반일 뿐 완료가 아니다. 불변 결정 ID·기존 정책 참조·실행 결과·
|
||||
pending/rollback·migration·정리·목록/상세 API와 GUI는 위 절에서 연결했다.
|
||||
후보/조건별 실제 관측값과 실행 직전 동적 파생값은 남는다. 코드 버전 전달은 위 저장 절에 연결했다.
|
||||
|
||||
### 전달 전 DB tick 정밀도 보완
|
||||
|
||||
월 표본과 정책의 기존 INTEGER tick은 1개월36,000,000 기준 약60개월에 넘친다.
|
||||
`20260916060000_widen_play_audit_ticks`로 두 열을 BIGINT로 확장하고 writer의 안전한
|
||||
정수 검증/변환, 월 표본 상세의 문자열 응답을 연결했다. pending payload와 hash 생성은
|
||||
기존 number 의미를 유지해 이미 저장한 hash를 바꾸지 않는다. 릴리스 manifest도 이
|
||||
migration head를 가리킨다. 실제 PG의55→56/빈56/no-op·기존 값/null/hash 보존과 큰 tick
|
||||
저장 및 실제 HTTP의 월 표본/정책 상세 tick 문자열을 검증했다.
|
||||
|
||||
### 기본 조회 화면
|
||||
|
||||
장수 이름 부분 검색과 장수 번호 양방향 정렬을 현재/월말 모두 지원한다. 최대64자,
|
||||
대소문자 구분, LIKE wildcard 문자 escape를 동일하게 적용한다. 입력 중에는 요청하지
|
||||
않고 조회 버튼으로 URL에 적용하며, 더 보기와 새로고침도 같은 필터·정렬을 유지한다.
|
||||
도시→모든 주둔 장수 연결에서는 이름 조건도 해제한다. 과거 검색은 sampleId로 먼저
|
||||
좁힌 뒤 당시 JSON 이름을 검사하며 현재 이름을 참조하지 않는다. 역순은 ID `< cursor`로
|
||||
페이지를 잇는다. 자원·능력별 정렬은 아래 2026-09-26 보완에서 복합 cursor로 추가했다.
|
||||
실제 PG120개월×1,000명 fixture에서 선택 월 PK1000행, 국가 추가 시50행으로 후보를
|
||||
좁혔다. 이 한 fixture의 실행계획은 전체 COST gate나 운영 p95 증거를 대신하지 않는다.
|
||||
|
||||
프로필 game frontend의 `/play-audit`는 장수가 없는 감사 계정도 직접 접근한다.
|
||||
`capabilities`가 허용된 뒤 coverage와 국가 목록을 읽고 선택한 조회만 요청한다.
|
||||
권한 거부 시 다른 감사 자료를 미리 가져오지 않는다. URL에 탭·국가·도시·표본 월·기간을
|
||||
보존하며 도시의 주둔 장수 연결은 당시 월을 유지하고 국가 필터를 해제한다.
|
||||
장수·도시 목록은 50개씩 명시적으로 더 읽는다. 느린 이전 응답은 후속 조회를 덮지 않는다.
|
||||
|
||||
국가 목록은 현재 또는 한 월의 이름/ID/color만 반환한다. 현재 목록은 해당 세 필드만
|
||||
SELECT하며 과거 목록은 한 표본의 국가 JSON을 51행까지 읽고 allowlist projection한다.
|
||||
기본 50·최대 200과 ID cursor를 사용하고 기수 전체의 국가를 DISTINCT 스캔하지 않는다.
|
||||
멸망국은 해당 월 기준 목록으로 선택한다. 국가 시계열 API의 기본 범위는 최근 6개월, 화면의 기본 범위는 최근 12개월·월별이며
|
||||
지표·집단 전환은 이미 받은 집계에서 계산해 추가 요청을 하지 않는다.
|
||||
|
||||
PanelCard, legacy-button, legacy-sort-select를 재사용한다. Chart.js로 국고 금·쌀,
|
||||
총 병사수, 실제 수입, 집단별 5병종 평균 숙련도의 큰 그래프를 표시하고 수치 표를 함께 제공한다.
|
||||
결측값은 선을 끊고 표시하며 최근 6/12/24개월 바로가기와 월/반기 조회를 제공한다. stock 마지막 표본 월, 월별 수집 여부·국가 존재·정산
|
||||
완전성을 펼쳐볼 수 있고 null은 `자료 없음`이다. 국가 보유 금쌀/기술/세율,
|
||||
수입·지급, 집단 인원·보유 총량/평균·5병종 평균 숙련 지표를 제공한다.
|
||||
|
||||
이 화면은 Core 신규 UX다. 최대 폭 1920px, 1200px 이상에서 탐색/분석/대상 상세의
|
||||
3열 구조다. 701~1199px에서는 상세가 분석 아래에, 700px 이하는 한 열에 표시된다.
|
||||
국가는 검색 가능한 목록에서 즉시 선택하며 장수·도시 클릭은 상세 영역에 focus를 옮긴다.
|
||||
390px 모바일에서 문서 가로 넘침 없음,
|
||||
넓은 표만 내부 수평 스크롤, 공통 14px 기본 typography와 명시적 focus/disabled가 계약이다.
|
||||
월말/FINAL 장수·도시 projection과 도시 지도, 자원·능력별 정렬을 제공한다.
|
||||
전투 통계는 후속 구현으로 남는다. 로그와 현재 예약 조회는 아래 구현을 따른다.
|
||||
따라서 기본 화면 추가만으로 R1~R3/P2를 완료 처리하지 않는다.
|
||||
|
||||
Gateway 서버 관리의 프로필 카드에는 `admin.playAudit.read` capability의 해당 전체
|
||||
profile scope가 있을 때만 진입 버튼을 표시한다. 기존 `auth.issueGameSession` 발급과
|
||||
game session transfer를 사용하고 Gateway가 감사 데이터를 대신 읽지 않는다.
|
||||
기존 로비의 URL 구성/세션 전달을 `utils/gameEntry.ts`로 추출해 공유한다.
|
||||
새 감사 진입은 동일 origin의 sessionStorage 전달만 허용하며, 실패하면 현재 화면에
|
||||
재시도 가능한 오류를 표시한다. 기존 로비의 query fallback은 동작 변경 없이 유지하되
|
||||
새 감사 경로에는 적용하지 않는다. 서로 다른 origin의 관리자 진입은 지원하지 않는다.
|
||||
|
||||
`nationSnapshot`은 같은 권한/기수 범위에서 한 월말 또는 FINAL header와 그 국가의
|
||||
복합 PK 행 하나만 읽는다. 최종 국가 화면은 이 API만 사용하며 월말 시계열을 동시에
|
||||
요청하지 않는다. 국가 보유량·집단 통계와 해당 월 수집 시점까지 관측한 정산을 표시한다.
|
||||
FINAL의 관측값을 월말/반기 합계에 추가하지 않는다. 표본 없음과 해당 국가 없음도 구분한다.
|
||||
최종 수집 자체의 모든 게임 종료 경로 연결은 P1의 남은 lifecycle 검증을 따른다.
|
||||
|
||||
`generalDetail/cityDetail`은 선택 엔티티와 관련 국가·도시의 PK만 조회한다.
|
||||
현재/과거 DTO를 분리하고 과거 이름·위치 이름도 같은 표본에서 읽는다. 현재 행으로
|
||||
과거의 누락을 메우지 않는다. 목록에서 이름을 누르면 URL의 `general/cityRecord`로
|
||||
상세를 연다. 상세 열기/닫기는 목록 조회 조건에서 제외하여 목록을 다시 읽지 않는다.
|
||||
도시 상세의 주둔 연결도 적용된 월을 유지한다.
|
||||
|
||||
`generalTurns`는 현재 예약 전용 별도 조회이며 과거 시점을 입력받지 않는다.
|
||||
상세의 버튼을 눌렀을 때만 기본50/최대200, `turnIdx` cursor로 읽는다. 정상30 slot을
|
||||
넘은 잘못된 값도 숨기지 않는다. 인자는 읽기 전용 `argumentJson` 텍스트로 반환한다.
|
||||
범용 JSON의 재귀 타입을 UI에 그대로 전달하지 않으면서 값은 생략하지 않는다.
|
||||
예약 조회 실패는 장수 상세를 지우지 않는다. 과거 예약 변경은 이후 사건 원장이 담당하며
|
||||
현재 큐에서 복원한 것처럼 표시하지 않는다. 전투 통계는 남는다. 지도·검색/정렬은 후속 보완으로 구현했다.
|
||||
|
||||
`app/game-engine/src/playAudit/snapshot.ts`는 기존 메모리 엔티티에서 명시적으로
|
||||
허용한 장수·도시 필드와 국가별 자원·숙련 집계를 만든다. 입력 iterable을 각각
|
||||
한 번 순회하며 국가마다 장수 목록을 다시 검색하지 않는다. 장수의 stats/role/items도
|
||||
복사하여 이후 개명·이동·장비 변경으로 과거 표본이 변하지 않게 한다. 임의 meta,
|
||||
triggerState, credential과 전체 world는 복사하지 않는다.
|
||||
|
||||
장수 분류는 human(`npcState < 2`), npc(`>= 2`, 5 제외), troopNpc(5)이다.
|
||||
빈 집단은 합계 0, 평균 null이다. nation 0도 입력에 있으면 일반 국가와 별도로
|
||||
집계한다. 장수의 국가와 도시 소유국을 일치시키지 않으므로 외국 주둔을 보존한다.
|
||||
|
||||
정산은 당월에 실제 관측한 `income/paid`만 별도 입력으로 받는다. 수집 완료 월에
|
||||
정산이 없으면 0, 도입 월처럼 완전 수집을 증명하지 못한 기간은 null이다.
|
||||
`prev_income_gold/rice`는 과거 정산 metadata이므로 집계하지 않는다. 정산 원장의
|
||||
국가 전후값·적용 세율·보정액은 이후 원장 구현에서 보존해야 하며 이 projection만으로
|
||||
R1을 완료했다고 판단하지 않는다.
|
||||
|
||||
### 실제 초기 달력의 조회 범위
|
||||
|
||||
감사 응답의 `startYear/startMonth`는 유효한 `world.meta.initYear/initMonth`를 우선한다.
|
||||
동기화 개방은 `scenarioMeta.startYear`의 전년도에 시작할 수 있으므로 시나리오 규칙 연도를
|
||||
조회 하한으로 고정하지 않는다. 두 metadata가 없거나 유효하지 않으면 시나리오 시작 연도와
|
||||
현재 연도 중 이른 연도의 1월을 호환 fallback으로 사용한다. 이것은 최초 수집 증거가 아니며
|
||||
자료 존재는 월 header로 별도 확인한다. 불변 기수 식별자 필터도 계속 적용한다.
|
||||
|
||||
월말/최종 상세, 장수 로그, 국가 시계열의 범위 검증과 기본 최근 6개월 기간은 같은 연월
|
||||
하한을 사용한다. UI도 시작 연도의 최소 월과 현재 연도의 최대 월을 제한한다. 이미 읽던
|
||||
world metadata로 계산하며 추가 DB 조회·쓰기나 시나리오/AI 규칙 변경은 없다.
|
||||
PREOPEN은 wall-clock 대기 상태이며, 검증하는 것은 공식 개방 때의 논리 게임 달력이다.
|
||||
|
||||
## 외교 문서 상태 이벤트 저장 기반
|
||||
|
||||
`diplomacy.sendLetter/respondLetter/rollbackLetter/destroyLetter`의 기존 입력 원장
|
||||
transaction에 제안·교체·승인·거절·회수·파기 요청·파기를 연결했다. 기존 SELECT와
|
||||
UPDATE 반환값에서 변경 전후 allowlist를 만들고, 원장 잠금 SELECT의 sequence를
|
||||
재사용한다. 감사 실패는 문서/알림과 함께 rollback하며 실패한 입력 원장은 남는다.
|
||||
성공 재요청은 기존 결과를 반환해 이벤트를 중복 저장하지 않는다.
|
||||
|
||||
- migration55의 `play_audit_diplomacy_event`는 기수, 방향 있는 국가쌍, 실행/로컬 순번,
|
||||
DB sequence, 처리 당시 달력/tick/revision, actor와 작은 상태 전후 값을 저장한다.
|
||||
DB sequence와 입력 접수 sequence는 다른 개념이며 숫자 간격은 허용한다.
|
||||
- 본문은 기존 `diplomacy_letter`를 ID/hash로 참조한다. 작성 본문·작성자·작성 시각·
|
||||
국가쌍·prevId의 UPDATE를 DB trigger로 거부하고, 기존 새 문서 작성 경로를 유지한다.
|
||||
상태·서명·aux 갱신은 허용한다. 기존 문서의 과거 상태를 소급 생성하지 않는다.
|
||||
- 문서 쓰기 요청당 작은 world/clock SELECT 1회, RUNNING realtime이면 readiness
|
||||
SELECT 1회, 이벤트 200개당 bulk INSERT와 ID/hash 확인 SELECT 각 1회가 추가된다.
|
||||
원문 SELECT와 본문 복사는 추가하지 않는다. 기존 알림 wall time 조회와 결합해
|
||||
줄일 여지는 남으며, SQL/WAL 비용 gate의 실측 완료를 뜻하지 않는다.
|
||||
- reset 뒤 이전 이벤트는 기존 retention 경로에서 ID 최대200개씩 정리한다.
|
||||
과거 tick/revision은 시계 이동 대상이 아닌 KEEP 이력으로 등록한다.
|
||||
- 기존 `rollbackLetter`는 회수다. 현재 복구 mutation은 없어 복구 이력을 꾸며내지 않는다.
|
||||
|
||||
실제 PostgreSQL에서 문서 8개 시나리오, unit 7건, retention 4건, 신규55개/
|
||||
증분54→55/재실행 migration을 검증했다. 외교 상태의 API 즉시 응답·엔진 월간/턴
|
||||
변경, 도입 당시 기준, 감사 조회 API/UI는 후속 구현이며 **R4 전체 완료가 아니다**.
|
||||
일반 외교 알림은 WALL_TIME이고 제의 처리와 tombstone은 구분한다. 오래된 알림
|
||||
테스트의 게임 tick 가정을 현행 envelope 계약에 맞췄다.
|
||||
|
||||
### 즉시 외교 응답의 관계 전이
|
||||
|
||||
`messages.respond`의 별도 `executeInputEvent` 경로에도 인증된 입력 context를 전달한다.
|
||||
불가침 체결·불가침 파기·종전 수락에서 이미 잠근 양방향 관계 행을 before로 사용하고,
|
||||
각 diplomacy UPDATE의 반환값을 after로 기록한다. state/term/dead/isDead/isShowing만
|
||||
투영하며 임의 meta를 복사하지 않는다. 같은 값의 재적용은 상태 전이에 포함하지 않는다.
|
||||
|
||||
추가 상태/clock/입력 SELECT는 없고, 두 방향의 전이를 한 bulk INSERT와 ID/hash
|
||||
확인 SELECT로 저장한다. 원장·관계·로그·알림과 같은 transaction이다. API commit 뒤
|
||||
엔진 메모리 동기화가 실패해도 재요청은 원장 결과를 재사용해 감사 이력을 중복 쓰지
|
||||
않는다. 엔진 동기화에서 같은 사건을 다시 수집하지 않는다.
|
||||
|
||||
실제 PG에서 세 응답의 양방향 before/after, 처리 순서, RESOLVED 제의 상태와 동기화
|
||||
실패 후 재요청을 검증했다. 엔진 transport만 fixture 응답이므로 엔진 runtime 동기화
|
||||
완료의 증거는 아니다. 거절/실패/무변경은 관계 전이와 구분할 시도 원장 구현에 남겼다.
|
||||
엔진 턴 변화와 기준 수집, 외교 조회 화면은 아직 남았다.
|
||||
|
||||
### 엔진 월간 외교 전이
|
||||
|
||||
`createMonthlyDiplomacyHandler`가 이미 가진 before/after에서 state/term/dead의 실제
|
||||
변화만 directed event로 모은다. 사건 순서는 국가 ID 쌍으로 고정하고 실행 identity는
|
||||
기수/달력/clock revision을 사용한다. 자연 월간 실행에 actor나 입력 원장 ID를 만들지
|
||||
않는다. 기본 TRADE matrix 보충 자체와 무변경 행은 기록하지 않는다.
|
||||
|
||||
pending queue는 world capture/restore/peek/ack에 포함하고, 기존 fenced DB transaction
|
||||
안에서 bulk200 INSERT와 hash 확인을 수행한다. 실패 시 상태와 이력은 함께 rollback,
|
||||
queue는 재시도까지 유지하며 commit 후에만 제거한다. 기존 계산/RNG/로그 순서는
|
||||
그대로고 추가 상태 SELECT는 없다. 기존 before 목록을 정렬한 메모리 사본만 추가한다.
|
||||
|
||||
실제 PG에서 개전·기간 감소·사상자 처리·불가침 만료·종전의 기존 결과/로그를 유지하며,
|
||||
메모리 checkpoint 복구, 감사 INSERT 실패 rollback, 재시도/중복 방지를 검증했다.
|
||||
엔진 개별 명령 전이와 초기 외교 기준, 조회 API/UI 및 전체 비용 실측은 남았다.
|
||||
|
||||
### 예약 턴 명령의 외교 전이
|
||||
|
||||
`createReservedTurnHandler`는 각 실제 action의 실행 순번과 실행 전 장수 identity,
|
||||
국가/개인 명령 구분, actionKey를 diplomacy patch에 운반한다. world가 patch를 실제
|
||||
적용하는 순서대로 직전/직후 state/term/dead를 queue하므로 같은 턴의 중간 전이를
|
||||
최종값으로 덮어쓰지 않는다. API 응답 동기화의 직접 world patch는 다시 기록하지 않는다.
|
||||
|
||||
실행 ID는 장수/실행 전 scheduled tick/clock revision이며 기수 ID와 함께 unique하다.
|
||||
ordinal은 해당 턴의 patch 순서이고 무변경을 생략하면 간격이 생길 수 있다. 입력 접수
|
||||
sequence를 예약 턴의 실행 ID로 오인하지 않는다. 메모리 Map을 직접 읽어 before
|
||||
관측 때문에 기본 관계 생성 순서가 달라지지 않도록 했다. 상태 SELECT/RNG 호출은 없다.
|
||||
|
||||
연속 두 전이와 checkpoint 복구 fixture, 실제 NPC 선전포고의 양방향 사건,
|
||||
감사 on/off에서 기존 개전·점령 진행을 검증했다. 완전한 RNG trace 동일성, 모든 명령의
|
||||
실제 DB 재로드와 당시 NPC 정책/결정 trace 연결은 후속 gate다. 즉시 명령 executor와
|
||||
특수 상태 변경을 포함한 최종 mutation inventory 및 초기 기준/조회 화면도 남았다.
|
||||
|
||||
### 외교 이력 조회 API
|
||||
|
||||
프로필 game-api의 `diplomacyHistory/diplomacyEvent`는 공통 감사 권한과 read-only
|
||||
transaction을 재사용한다. 목록은 서로 다른 국가 두 개와 현재 기수 안의 기간을
|
||||
필수로 받아 sequence 역순 기본50/최대200개만 반환한다. 국가쌍의 입력 순서는 무관하지만
|
||||
각 사건의 방향은 유지한다. sequence/cursor/tick은 정밀도를 잃지 않는 문자열이다.
|
||||
|
||||
목록에는 본문·전후 값·요청 원장 정보를 싣지 않는다. 상세는 해당 기수 event 1개와
|
||||
필요한 문서 1개만 읽고 원문 hash를 검증한다. AVAILABLE/NOT_APPLICABLE/
|
||||
MISSING_REFERENCE/HASH_MISMATCH를 구분하고 원문 부재·불일치에서는 본문을 반환하지
|
||||
않는다. 기록의 before/after를 보여 주며 현재 문서 상태로 덮어쓰지 않는다. actor와
|
||||
상태는 allowlist로 투영하여 계정 ID·debug 등 내부 값을 제외한다.
|
||||
|
||||
coverage는 RECORDED_EVENTS_ONLY이며 최초 수집 전의 상태 완전성을 주장하지 않는다.
|
||||
국가쌍/sequence index를 사용 가능한 형태지만 실제 큰 기수의 기간별 scan/EXPLAIN 비용
|
||||
검증은 후속 gate다. 실제 HTTP+PG/Redis에서 권한·sanction·잘못된 범위/cursor,
|
||||
본문 지연 조회/hash 상태·기수 전환의 접근 차단과 input_event 무증가를 검증했다.
|
||||
외교 화면과 초기 기준 수집은 아직 남아 있다.
|
||||
|
||||
### 외교 감사 화면
|
||||
|
||||
CHE/HWE의 기존 `PlayAuditView` 필터·PanelCard에 외교 이력을 추가했다. 국가/상대 국가,
|
||||
기간과 선택 event를 URL에 보존하고 기존 정책 이력과 같은 표·상세·pagination 흐름을
|
||||
사용한다. 상세 선택·닫기·오류 재시도는 국가/이력 목록을 다시 읽지 않는다. 필터 편집은
|
||||
조회 적용 전 요청하지 않는다. 본문은 선택한 상세 응답에서만 읽는다.
|
||||
|
||||
원문 hash를 검증한 뒤 기존 `purifyDiplomacyHtml`로 만든 briefHtml/detailHtml만 렌더링한다.
|
||||
원문 HTML은 별도 details 안에서 escaped text로 보여준다. 이 때문에 선택 문서의
|
||||
응답에는 원문/정제본이 함께 오지만 추가 DB 조회나 중복 저장은 없다. 해시 불일치와
|
||||
참조 부재에는 본문을 표시하지 않는다. 이전 문서 번호와 당시 actor/상태/실행 연결을
|
||||
보이고, 무소속은 외교 국가 선택에서 제외한다.
|
||||
|
||||
실제 HTTP에서 script 포함 원문의 보존/표시용 정제를 확인하고, production bundle
|
||||
Chromium의 CHE/HWE에서 desktop1280×720/mobile390×844, DPR1로 검증했다.
|
||||
선택적 상세/재시도·deep link/reload·pagination·draft 적용과 표 내부 가로 스크롤,
|
||||
원문 script 비실행을 검사한다. 외교 최초 기준과 최종 mutation inventory/전체 비용
|
||||
검증은 남아 있으며 화면 추가만으로 R4 전체 완료를 판단하지 않는다.
|
||||
|
||||
### 외교 관계 최초 관측
|
||||
|
||||
`initializeAuditDiplomacy`는 복구된 clock과 이미 로드한 관계에서 실제 국가쌍의
|
||||
방향별 state/term/dead를 한 번만 기록한다. 재야(0)는 제외하며 당시의 관측 값만
|
||||
보존한다. 이전 상태·원인·actor는 null이고 과거 체결 시점을 추정하지 않는다.
|
||||
`playAuditDiplomacy` 기수 표식과 사건을 readiness 전 기존 fenced flush에서 함께
|
||||
저장한다. 관계가 없는 경우에도 표식은 flush하고, 정상 재시작은 다시 쓰지 않는다.
|
||||
checkpoint rollback은 표식과 pending 사건을 함께 복원한다.
|
||||
|
||||
추가 DB 전체 SELECT는 없다. 초기 관계 목록의 짧은 필드만 기존 200행 batch writer로
|
||||
저장하며 최초 저장량은 방향별 관계 수에 비례한다. 정상 재시작은 메모리 표식 검사만
|
||||
수행한다. 기본 교역 관계도 최초에는 보존해 국가쌍 조회의 시작 값을 제공하지만,
|
||||
월간 기본 matrix 생성은 계속 전이로 기록하지 않는다. 대규모 국가 수의 payload와
|
||||
WAL 실측, 이후 신생국·소멸국 및 기존 문서의 도입 기준은 별도 검증이 남아 있다.
|
||||
|
||||
단위 검증은 재야 제외·빈 관계·잘못된 관측 시각·재호출·checkpoint 복원을 다룬다.
|
||||
실제 격리 PostgreSQL startup 검증은 실패 rollback, PREOPEN 저장, 재시작의 동일 행과
|
||||
표식 및 readiness 경계를 확인한다. UI는 이를 '관계 최초 관측'으로 구분한다.
|
||||
|
||||
### 기존 외교 문서 도입 기준
|
||||
|
||||
`databaseHooks.flushInitialAudit`는 문서 도입 표식이 없는 기수에만 seed와 같은 schema lock → lease → CLOCK →
|
||||
GENERAL_ACCESS 순서의 transaction을 열고 clock authority와 기수 identity를 확인한다.
|
||||
`documentBaseline.ts`가 문서를 id cursor 200건씩 읽어 기존 불변 원문 참조/hash와
|
||||
관측 당시 상태·서명·이전 문서 번호를 저장한다. 원문 자체를 사건에 복제하지 않는다.
|
||||
현재 유효 문서뿐 아니라 보유 중인 교체·종료 문서도 보존하며 과거 승낙 시점이나
|
||||
actor는 만들지 않는다. 기존 API가 쓰던 상태 projection을 infra로 옮겨 함께 사용한다.
|
||||
|
||||
문서 기준 사건·world 표식과 대기 중인 초기 상태/정책/관계가 같은 fenced transaction에서
|
||||
확정되고 commit 뒤에만 ack한다. 실패는 memory checkpoint를 복원한다. 문서가 0건이어도
|
||||
표식을 저장하고 일반 재시작에서는 문서 SELECT를 수행하지 않는다. 본문 mutation API와
|
||||
같은 CLOCK lock 아래서 읽으며, 조회 준비가 되기 전에 종료한다.
|
||||
|
||||
비용 재검토: 최초에 작은 identity SELECT 1회, 문서 SELECT `floor(L/200)+1`회와 기존
|
||||
writer의 batch INSERT/hash 확인 SELECT를 사용한다. 원문은 해시 계산에 필요한 한 batch만
|
||||
유지하며 매 턴 다시 읽지 않는다. 초기 실패 복원을 위한 world memory checkpoint 1개의
|
||||
비용과 대규모 도입 transaction 시간·WAL은 전체 COST gate에서 측정해야 한다.
|
||||
실제 PG fixture는 201건/2 batch와 INITIAL 저장 실패의 전체 rollback, 원문 hash와 상태,
|
||||
재시작의 동일 event/표식을 확인한다. 화면에서는 '문서 최초 관측'으로 구분한다.
|
||||
|
||||
### 국가 생성·멸망 관계
|
||||
|
||||
국가 생성은 `addNation`(월간 NPC/이민족/즉시 명령)과 예약 턴의 직접 created.nations
|
||||
반영 경로를 모두 수집한다. 기본 matrix를 만든 뒤 신생국과 연결된 방향별 관계만 가져오고,
|
||||
한 번에 여러 국가를 생성해도 같은 관계를 중복 기록하지 않는다. 후자의 누락된 정책
|
||||
기준 4영역 초기화도 같은 지점에 연결했다. 생성 시 조회 범위는 대상 국가 수×기존 국가 수며
|
||||
전체 관계 matrix를 감사용으로 복제하지 않는다.
|
||||
|
||||
멸망은 후계자 없음/전투/월간 방랑 처리의 공통 removeNation에서 기존 삭제 순회가
|
||||
제거하는 관계를 재사용한다. 생성은 before null, 제거는 after null이며 default 교역
|
||||
생성을 월간 외교 상태 전이로 바꾸지 않는다. 기존 world 감사 순번은 `nation-relations:N`
|
||||
실행 identity에만 쓰고 사건 안의 ordinal은 방향별 정렬 순서다. checkpoint 복원과 기존
|
||||
fenced transaction/pending acknowledgement를 그대로 따른다. 원인 actor/request 연결은
|
||||
관측되지 않으면 null이며 향후 조사 C/E context 연결 대상으로 남긴다. 예약 턴에서 생성·멸망한 관계는 전역 clock 대신 해당 장수의 실행 tick을 전달한다.
|
||||
|
||||
단위 검증은 같은 ID 생성·삭제·재생성, 중복 add/remove, rollback, 예약 턴 복수 신생국의
|
||||
정책 기준/관계 중복 방지를 확인한다. 실제 PG는 제거 저장 실패의 nation/관계 rollback,
|
||||
재시도, 삭제 뒤 재생성, 정책 4개 및 이력 중복 방지를 검증한다. CHE/HWE에서 생성·종료의
|
||||
한쪽 상태 부재를 표시한다. 상대국이 없는 단독 신생국은 관계 사건이 없으며 독립 국가
|
||||
생멸 원장은 향후 조사 C에 속한다.
|
||||
|
||||
## NPC·국방 정책 버전 저장 기반
|
||||
|
||||
`PlayAuditPolicy`는 현재 기수/국가/영역별 불변 revision과 이전 버전 ID를 보존한다.
|
||||
영역은 국가 NPC 값, 국가 NPC 우선순위, 장수 NPC 우선순위, 국방(`war/scout/secretlimit`)이다.
|
||||
공지·권유문·세율·지급률 등 나머지 국가 설정의 사건 기록은 자원/행위 원장 연결에서 남아 있다.
|
||||
NPC 설정은 기존 allowlist의 저장 값만 복사하며 setter/time이나 임의 nation metadata를
|
||||
복사하지 않는다. 누락(null)은 설정 상속이다. 개인별·server별 보정이 적용된 최종 AI 값인
|
||||
것처럼 표시하지 않으며, 해당 관측값과 코드 버전은 이후 NPC trace가 담당한다.
|
||||
|
||||
daemon world 구성과 신규 `addNation`에서 네 기준 버전을 pending에 담는다. 기존 정상
|
||||
포인터가 있으면 재시작 때 재생성하지 않는다. 포인터 ID는 기수·국가·영역·revision으로
|
||||
검증하므로 다른 국가 metadata를 복사해도 기존 국가 이력을 이어받지 않는다.
|
||||
DB runtime의 기준 수집은 clock recovery/synchronization 뒤 수행하고 readiness 전에
|
||||
startup flush로 내구화한다. PREOPEN에서 명령이 없어도 정책 기준을 저장한다.
|
||||
|
||||
기존 NPC mutation의 검증·CAS와 국가 설정의 권한·횟수 제한을 통과한 뒤 변경 전후를 비교한다.
|
||||
setter/time 변경과 동일 설정 저장에는 새 적용 버전을 만들지 않는다. 기존 CAS token 갱신,
|
||||
write와 성공 응답 계약은 유지한다. 거부된 입력은 정책 적용 이력에 넣지 않으며 기존
|
||||
input_event의 ok:false와 구분한다. OBSERVED_GAP은 저장된 포인터와 현재 설정 불일치를
|
||||
발견했을 때의 기준 재관측이다. 실제 변경 전 값/actor/시각을 추정하지 않는다.
|
||||
|
||||
pending 정책·최신 포인터·전역 감사 ordinal은 world capture/restore와 acknowledgement에
|
||||
포함한다. 정책 row, nation meta, world meta와 input_event 완료는 같은 transaction이다.
|
||||
기존 input_event fence SELECT에 sequence/actor_user_id만 추가하여 추가 요청 조회를 피한다.
|
||||
수행자 ID가 명령과 일치하지 않거나 요청이 있는데 input context가 없으면 저장을 거부한다.
|
||||
actor는 당시 user/general/name/nation/officer/npc/permission만 보존하며 createdAt은 DB wall time이다.
|
||||
향후 수뇌 DTO는 nation 소유권과 기존 resolver를 적용하고 userId/inputSequence/requestId 및
|
||||
관리자 진단을 제외해야 한다. 수뇌 직책/가입 전 열람 정책·화면은 확정 설계대로 후속 범위다.
|
||||
|
||||
ID와 payload hash로 재시도를 검증한다.200행마다 createMany와 ID/hash 확인 SELECT를 사용하며
|
||||
본문 전체를 재조회하지 않는다. 최초 기준은 국가당4행, 적용 변경은 해당 영역1행이다.
|
||||
세계 전체 정책이나 장수별 정책을 매 턴 복사하지 않는다. 기존 NPC 기본값은 별도 pure module로
|
||||
옮겨 mutation과 audit allowlist가 공유하고 기존 export 경로는 유지한다.
|
||||
정책 schema version은1이며 migration은 기존 이력을 backfill하지 않는다. 이미 적용한 migration
|
||||
checksum은 수정하지 않고 schema_version 필드는 별도 증분 migration으로 추가했다.
|
||||
|
||||
정리 worker는 이전 기수 정책 ID도 최대200개씩 삭제한다. policy version의 self-reference는
|
||||
삭제 FK로 강제하지 않아 과거 비공개 기수를 key batch로 정리할 수 있다. 현재 기수 포인터는
|
||||
같은 transaction으로 저장하고 조회 시 항상 현재 기수 범위를 검사해야 한다.
|
||||
NPC 결정의 정책 참조와 거부/무변경 시도의 사건 연결은 아직 남았다.
|
||||
|
||||
`policyHistory`는 국가/영역/기간을 필수로 받고 기본50·최대200개의 버전 요약을
|
||||
revision 내림차순 cursor로 반환한다. 목록에서는 전후 정책 본문과 요청 자료를 읽지 않는다.
|
||||
`policyVersion`은 선택 ID 한 건의 현재 기수 범위를 검사하고 전후 설정·당시 직책·입력
|
||||
sequence를 반환한다. BigInt는 문자열로 보존하며 목록/상세 모두 계정 ID를 노출하지 않는다.
|
||||
world identity와 자료는 기존 RepeatableRead/timeout 계약을 공유한다. 별도 count/쓰기나
|
||||
현재 정책을 읽어 과거를 채우는 조회는 없다. 기존 국가/영역/revision 및 기간 index를
|
||||
재사용한다. 반환 상한은 DB scan 상한의 증명이 아니며 실행계획/부하 측정은 P6에 남는다.
|
||||
|
||||
`projectPolicyConfiguration`은 수뇌 공개에 재사용할 전후 값·당시 소속/직책만 남기고
|
||||
계정·요청·tick/ordinal·관리자 진단을 제외한다. 현행 endpoint는 관리자 전용이다.
|
||||
향후 자국 API에서 기존 국가 resolver의 인가를 별도 적용해야 하며 이 projection 자체가
|
||||
인가를 대신하지 않는다. 수뇌용 route나 직책 권한을 이번 변경에서 새로 열지 않았다.
|
||||
|
||||
프로필 `/play-audit`의 정책 화면은 적용한 국가/영역/기간만 읽고, 상세 열기/이전 버전
|
||||
이동/실패 재시도가 목록과 국가 목록을 다시 읽지 않도록 한다. URL로 선택 버전을 보존한다.
|
||||
입력 중인 필터는 조회 버튼을 누르기 전 SQL 요청을 발생시키지 않는다. 최초 관측,
|
||||
실제 변경, 관측 누락 이후 기준과 자료 없음의 의미를 구분한다. 기존 PanelCard와 제어
|
||||
스타일을 사용하고 NPC 설정 화면의 한국어 필드 이름을 따른다. 값은 텍스트로 출력한다.
|
||||
|
||||
## 초기 도입 기준과 즉시 내구성
|
||||
|
||||
`initializeAuditCollection`은 기수별 첫 관측에서만 INITIAL 표본과 world의
|
||||
`playAuditCollection` 시작 좌표/실제 관측 시각을 pending에 담는다. INITIAL은 기존
|
||||
국가·도시·장수 projection과 batch 저장을 재사용하며 월말과 FINAL을 대체하지 않는다.
|
||||
새 migration은 INITIAL kind를 허용하고 부분 unique index로 기수당 한 행만 허용한다.
|
||||
재시작은 저장된 marker를 사용하며 초기 상태를 현재 값으로 갱신하지 않는다.
|
||||
|
||||
runtime은 기존 lease 획득·world load·clock 복구/동기화 후 정책/상태 기준을 수집한다.
|
||||
기존 fenced persistence로 기준과 국가 포인터/world marker를 함께 commit한 뒤에만
|
||||
clockReady를 공개한다. 별도 input_event를 만들지 않는다. 초기 저장 실패는 기존 startup
|
||||
오류 경로로 lease/연결을 정리하고 준비 완료를 알리지 않는다. 정상 재시작은 pending이
|
||||
없으면 추가 flush가 없다. pending 확인은 배열 길이만 검사하여 큰 snapshot을 복사하지 않는다.
|
||||
|
||||
초기 수집은 이미 로드된 world를 각 한 번 순회한다. 추가 전체 엔티티 SELECT 없이 기존
|
||||
header/child batch transaction을 한 번 수행한다. readiness 전에 저장하므로 큰 기수의
|
||||
startup latency/WAL/heap 비용은 P6에서 함께 측정해야 한다. 값이나 표본을 생략하는
|
||||
방식으로 비용을 줄이지 않는다. 초기 read-model receipt는 첫 실제 명령에 섞지 않는다.
|
||||
|
||||
조회 API의 표본 kind와 coverage cursor에 INITIAL을 포함한다. 현재 world meta에서
|
||||
`collectionStart`만 allowlist projection하여 추가 DB 조회 없이 시작 시점을 표시한다.
|
||||
국가·장수·도시의 수집 시작 기준 조회는 같은 snapshot identity를 유지하며 국가 시계열에는
|
||||
MONTH_END만 포함한다. INITIAL의 흐름을 정규 월/반기 합계에 더하지 않는다.
|
||||
시나리오 개방 달력과 실제 상세 수집 시작은 서로 다른 값이다.
|
||||
|
||||
## 이전 기수 월별 표본 정리
|
||||
|
||||
새 daemon runtime은 실제 `serverId`를 고정해 이전 월별 감사 표본 정리를 시작한다.
|
||||
기수 변경 직후 조회 차단은 기존 API identity 필터가 담당한다. 정리는 gameplay flush와
|
||||
별도 transaction이며, seeder와 같은 schema advisory lock을 try-lock한 뒤 DB의 현재
|
||||
identity를 재확인한다. 이전 runtime/누락 identity는 정리하지 않는다. 부모 표본의 row lock으로
|
||||
child 추가와 빈 부모 삭제의 경쟁을 막고, 장수→도시→국가 child의 PK만 최대200개 읽어
|
||||
그 행을 삭제한다. 모두 빈 뒤 header1개를 삭제하여 대량 cascade를 피한다.
|
||||
|
||||
batch는 SQL statement2초/transaction5초 상한이고 성공 후1초, lock경합/오류 후30초에
|
||||
재시도한다. 동시에 두 batch를 수행하지 않는다. 기수가 바뀌거나 이전 자료가 없으면
|
||||
worker가 끝나므로 평상시 idle polling은 없다. 프로세스 재기동은 DB의 남은 key부터 다시
|
||||
시작한다. 종료는 진행 중 transaction을 기다린 뒤 connector를 닫는다. 원문 DB 오류 대신
|
||||
고정 경고를 운영 로그에 남기며 정리 실패로 gameplay 결과를 실패 처리하지 않는다.
|
||||
|
||||
현재 정리 대상은 구현된 PlayAuditMonth/General/City/Nation과 PlayAuditPolicy다. 기존 연감/계정 원장과
|
||||
LogEntry 보존 정책은 바꾸지 않는다. 외교·정책·trace 테이블을 추가할 때 같은 수명주기와
|
||||
key 단위 삭제를 연결해야 한다. Gateway RESET의 기존 process중지→seed commit→재기동
|
||||
경로에서 시작한다. 예약 상태에서는 runtime이 없을 수 있어 seed commit 직후에도 batch
|
||||
하나를 시도한다. 실패한 seed에는 정리가 실행되지 않고, 정리 실패는 seed 결과의 warning으로
|
||||
남긴 뒤 이후 runtime이 재시도한다. RESERVED에서 남은 물리 정리는 PREOPEN 기동까지
|
||||
연기되지만 새 identity로의 조회 차단은 즉시 적용된다. 취소 CANCELLED는 기존 정책상 API도
|
||||
중지되므로 관리자 감사 접근과 최종 표본 수집의 별도 lifecycle 보완은 아직 남는다.
|
||||
|
||||
검증 fixture는 `PLAY_AUDIT_RETENTION_DATABASE_URL`의 `_retention_fixture` 전용 schema를
|
||||
요구한다. schema에 정식 migration을 적용한 뒤 `playAuditRetention.integration.test.ts`를
|
||||
실행한다. 삭제·trigger rollback fixture이므로 다른 통합 suite의 DB를 공유하지 않는다.
|
||||
conditional registry는 external_fixture로 분류하며 일반 core DB URL에 자동 연결하지 않는다.
|
||||
실제 DB에서401장수/201도시/1국가를 여러 batch로 정리하고 현재 기수를 보존했다.
|
||||
추가 real daemon startup 검증은 기존 selectPool integration에 연결했다.
|
||||
|
||||
## 기존 장수 로그의 기수별 조회
|
||||
|
||||
`generalLogs`는 기존 `LogEntry`에서 현재 기수와 장수·기록 종류를 제한하고
|
||||
ID 역순 cursor로 기본 50/최대 200행을 조회한다. 네 종류는 열전/개인 행동/
|
||||
전투 결과/전투 상세다. 현재 장수가 사망했어도 보존된 로그를 조회할 수 있다.
|
||||
과거 표본에서 열면 해당 게임 월 전체 기록을 조회하며 표본 순간까지의 기록인 것처럼
|
||||
표시하지 않는다. `createdAt`은 게임 논리 시각일 수 있어 설치 wall time으로 필터하지 않는다.
|
||||
|
||||
별도 로그 복제 테이블 대신 nullable `LogEntry.serverId`를 추가했다. 엔진 공통 로그,
|
||||
천통 내기 결과, 장수 선택/재선택, 외교 메시지 응답의 기존 저장 transaction에서
|
||||
world meta의 실제 기수 ID를 함께 쓴다. 엔진과 장수 선택은 메모리에 있는 값을 사용한다.
|
||||
외교 응답은 기존 world SELECT에 meta 필드를 추가한다(조회 횟수는 동일하지만 읽는 bytes는 증가).
|
||||
별도 world SELECT나 로그 INSERT는 없다.
|
||||
공백/누락 identity를 profile명으로 대신하지 않는다. 기존 및 레거시 이관 로그는
|
||||
기수 귀속을 증명하지 못하므로 null 그대로 두며 API에서 제외하고 화면에 자료 범위를 표시한다.
|
||||
설치 시 전체 backfill이나 로그 재복사는 수행하지 않는다.
|
||||
|
||||
현재 `(generalId, category, id)` index를 재사용한다. serverId/월은 잔여 조건이므로
|
||||
오랜 기수의 희소한 월 조회에는 더 많은 index 행을 검사할 수 있다. 실행 5초 상한은
|
||||
응답 실패를 자료 없음으로 숨기지 않는다. 큰 fixture의 EXPLAIN/지연 측정 후 복합 index의
|
||||
추가 쓰기 비용과 비교하는 P6 gate는 남으며, 행 반환 상한을 스캔량 상한으로 보고하지 않는다.
|
||||
|
||||
`GeneralRecordPanels`에 선택 종류와 독립 오류/재시도 표시를 추가해 기존 표시를 재사용한다.
|
||||
상세에서 버튼을 눌러야 로그를 읽고 종류별로 받은 페이지를 재사용한다. 엔티티/월이
|
||||
바뀌면 캐시를 비우고 늦은 응답을 버린다. 동등한 월 객체가 재생성되어도 장수/도시 상세를
|
||||
재조회하지 않도록 감시 대상을 ID·연·월·종류의 원시값으로 제한한다. 로그 원문은
|
||||
기존 `formatLog` 허용 목록을 거쳐 렌더링한다.
|
||||
|
||||
## 월별 저장 구현
|
||||
|
||||
`playAudit/collection.ts`가 `beforeMonthChanged`에 이전 월 표본을 queue한다.
|
||||
`incomeHandler`에서 이미 계산한 수입·급여를 관측하여 작은 국가별 월합계를
|
||||
world meta에 함께 저장한다. 월중 재시작에도 집계가 유지되며, 수집 도입 월은
|
||||
불완전으로 표시하고 다음 월부터 완전 수집한다. 기수 identity가 없는 설치에서는
|
||||
profile명으로 대체하지 않는다. 해당 구간의 API coverage 안내는 아직 구현해야 한다.
|
||||
|
||||
`InMemoryTurnWorld`의 capture/restore/peek/acknowledge에 pending 표본을 포함하고
|
||||
`databaseHooks.persistChanges`에서 gameplay와 같은 transaction으로 저장한다.
|
||||
`unificationHandler`는 월말과 구분되는 FINAL 표본을 queue한다.
|
||||
`PlayAuditMonth/Nation/City/General` 네 테이블에 명시적 projection을 보존한다.
|
||||
국가·도시별 장수 검색 index를 두고 child insert를 200행씩 분할한다. 표본 ID와
|
||||
payload hash가 같은 재시도는 중복 저장하지 않고, 내용이 다르면 transaction을 실패시킨다.
|
||||
200은 초기 batch 설정이며 payload bytes/WAL/heap 실측을 통한 최종 선정은 남아 있다.
|
||||
|
||||
새 migration은 기존 행을 backfill하지 않는다. 전용 PostgreSQL에서 빈 설치 전체 적용,
|
||||
기존 49 migration 이후 새 migration 증분 적용, 두 번째 deploy no-op을 검증했다.
|
||||
업무 데이터와 감사 표본의 transaction rollback, 재시도/충돌, 월 경계의 전월 세율과
|
||||
world meta reload도 확인했다. 이전 기수 차단/정리, 최종 표본 전체 종료 경로,
|
||||
정산 전후값의 별도 사건 원장은 아직 남아 있다.
|
||||
|
||||
## 프로필 조회·권한 구현
|
||||
|
||||
`game-api/router/playAudit`에 `capabilities`, `coverage`, `generals`, `cities`를
|
||||
추가했다. Gateway는 capability catalog만 제공하며 게임 자료를 대신 조회하지 않는다.
|
||||
`admin.playAudit.read:<profileName>`는 `che:default`처럼 scenario까지 정확히 비교한다.
|
||||
기존 resolver와 같은 명시적 전체 grant와 `superuser/admin.superuser`를 허용하고
|
||||
일반 `admin`, Gateway 조치 감사 권한, 인게임 직책으로 접근을 추론하지 않는다.
|
||||
공통 계정 권한 판정은 추가 `admin.playAudit.accounts`를 요구한다. 계정 조사 자체는 아직 없다.
|
||||
|
||||
정상 bootstrap 첫 계정은 기존 발급 경로의 명시적 `superuser` role을 사용한다.
|
||||
역할 없는 레거시 첫 계정에 대한 Gateway의 DB 기반 관리자 fallback은 게임 token에
|
||||
전달되지 않는다. 감사 API는 기존 게임 token의 명시적 role만 신뢰하며 Gateway의
|
||||
첫 계정 판정을 game DB에서 재현하지 않는다. 해당 계정은 기존 Gateway 권한 관리로
|
||||
감사 role을 부여할 수 있다. 이 경계는 일반 `admin`의 권한 확대로 해결하지 않는다.
|
||||
|
||||
모든 조회는 인증·기존 제재·token profile 검사 후 실행한다. 장수 보유, 접속 가중치,
|
||||
input_event 쓰기를 요구하지 않는다. 현재 세계 identity와 자료를 같은 RepeatableRead
|
||||
transaction으로 읽으며 대기는 2초, 실행은 5초로 제한한다. 응답에 `asOf`, tick과
|
||||
현재 기수 identity를 포함하고 오래된 기수 ID를 client에게 입력받지 않는다.
|
||||
|
||||
장수/도시 목록은 기본 50·최대 200, ID cursor로 페이지를 읽는다. `at`이 있으면
|
||||
현재 기수의 해당 월말/FINAL 표본에서 조회하고 미수집이면 `collected:false`다.
|
||||
현재 일반 장수와 NPC/부대장 NPC, 국가·도시 필터를 지원한다. 저장·현재 자료 모두
|
||||
DTO allowlist를 적용해 임의 meta를 응답하지 않는다. `coverage`도 월 header만
|
||||
cursor 조회하며 원문·장수 목록을 같이 싣지 않는다. 아직 마감 월 캐시는 없다.
|
||||
|
||||
실제 Gateway HTTP에서 새 role 부여·범위 확대 거부·flush 호출·암호화 game token의
|
||||
정확한 scope 전달을 확인했다. 별도 PostgreSQL/Redis와 실제 game HTTP에서는
|
||||
no-general 허용, 무인증·일반 admin·다른 profile·제재 거부, 200 상한,
|
||||
과거 이름/미수집, 새 기수 전환 후 이전 표본 차단과 flush 후 401을 확인했다.
|
||||
|
||||
`nationSeries`는 국가 1개의 월별 집계만 조회한다. 월/반기 해상도, 기간, 페이지 크기
|
||||
50 기본/200 최대를 받고 긴 기수는 다음 기간 cursor로 이어 읽는다. 한 번에 읽는
|
||||
월 header는 최대 1200개(200반기), 국가 집계도 그 범위의 해당 국가만 읽으며
|
||||
병사수 집계가 없는 기존 표본만 해당 국가의 장수 원본을 DB에서 집계한다. 도시 원본이나
|
||||
전체 trace는 읽지 않는다. API 기본 기간은 최근 6개월이고 반기는
|
||||
1~~6월/7~~12월 경계로 묶는다. 보유·기술·집단 평균은 마지막 수집 표본과 그 시점을
|
||||
반환하고 수입/급여만 기간 합산한다. 누락·국가 없음·불완전 정산의 흐름은 null,
|
||||
관측한 정산 없음은 0이다. 기간 일부 요청은 from/to와 complete=false로 표시한다.
|
||||
|
||||
장수·도시 상세와 독립 로그, FINAL 별도 표시와 정책 조회는 기본 화면에 연결했다. 외교 이력도 연결했으며 NPC 결정과 조사 기능의 완성은 남았다.
|
||||
|
||||
## 초기 수집 지점과 쓰기 재검토
|
||||
|
||||
아래 표와 검증 진입점은 초기 구현 당시 inventory다. 이후 연결·검증 결과는 위의 현재 구현과 운영 안내를 기준으로 읽는다.
|
||||
|
||||
기준 Core commit은 `5ac961dfd17738dc4c39e6401f975296f39403a5`이다.
|
||||
SQL/bytes는 아직 실측하지 않았으며 아래는 현재 소스에서 확인한 연결 지점과 구현 경계다.
|
||||
|
||||
| 자료 | 실제 source / 관측할 값 | 구현·비용 결정 | 남은 검증 |
|
||||
| ------------------- | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------- |
|
||||
| 월별 장수·도시·국가 | `turn/yearbookHandler.ts`의 `beforeMonthChanged`; `playAudit/snapshot.ts`의 필드 allowlist | 메모리 한 순회, 추가 SELECT 없이 수집. 상세는 도시/국가/장수로 페이지 조회 가능한 행에 batch 저장하며 큰 월 JSON 전체를 목록 조회하지 않음 | 단위/DB 연결 확인; 전체 종료 경로·coverage·비용 gate는 남음 |
|
||||
| 세율 적용 수입·급여 | `turn/incomeHandler.ts`의 `applyIncome`, `incomeValue`, `current`, `next`, `ratio`, 장수별 `pay`; `turn/nationTaxRate.ts` | 이미 계산한 수치만 관측. 국가 수입과 실제 급여 합계 분리, 과거 metadata 재누적 금지. 정산 원장을 월집계 입력으로 재사용 | 정수화·최저 자원 보정, 원장/집계 원자 저장, 도입 월 coverage |
|
||||
| 월별 내구성 | `turn/inMemoryWorld.ts`의 capture/restore, peek/acknowledge와 pending yearbook; `turn/databaseHooks.ts`의 `persistChanges` | 별도 audit pending을 같은 transaction과 savepoint에 포함. 기존 연감의 장기보존 테이블에 상세 감사를 넣지 않음 | 실패·중복·재시작, bounded 삭제 |
|
||||
| 기수 identity | `scenario/scenarioSeeder.ts`의 `install.serverId`, `GameHistory` 충돌 검사 | profile명으로 대체하지 않음. 외부 install 입력을 만드는 지점과 RESET 전체 경로를 추가 추적한 뒤 수집 활성화 | 신규 identity 생성, 재시도, 기존 설치에 identity 누락 시 처리 |
|
||||
| 외교 | game-api `router/diplomacy/index.ts`, engine 월간 외교 처리 | 불변 문서는 참조, 갱신되는 내용만 당시 버전 저장. 현재 상태 월복사만으로 사건을 대신하지 않음 | 모든 API/engine mutation별 inventory |
|
||||
| NPC 정책 | `turn/worldCommandHandler.ts` → `turn/npcPolicyMutation.ts` | CAS 성공하고 실제 값이 달라진 경우에만 불변 버전. 무변경/거부는 적용 버전에서 제외 | NPC 결정의 버전 참조, 거부/무변경 시도 원장 |
|
||||
| 권한 | Gateway `adminCapabilities.ts`, `adminAuth.ts`; game-api `trpc.ts` 인증·제재 middleware | scoped 감사 권한과 공통 계정 추가 권한 분리. `getMyGeneral` 요구 없이 서버에서 검사 | catalog/token/flush/HTTP matrix 전체 연결 |
|
||||
|
||||
월간 실행은 이전 월 snapshot → 달 변경 → 새달 `onMonthChanged` 순서다.
|
||||
1월 금/7월 쌀 정산은 새로 진입한 월의 흐름으로 누적하고 그 월 마감에 집계한다.
|
||||
입력 목록은 이미 로드된 world를 사용하며 기존 연감의 로그 SELECT를 새 감사 수집의
|
||||
필수 입력으로 만들지 않는다.
|
||||
|
||||
## 검증 진입점
|
||||
|
||||
- 순수 projection: `app/game-engine/test/playAuditSnapshot.test.ts`.
|
||||
- 월말·저장 경계: `monthlyBoundaryPrePersistence.integration.test.ts`.
|
||||
- 수입: `monthlySemiAnnualPersistence.integration.test.ts`, `monthlyWarIncomePersistence.integration.test.ts`.
|
||||
- 원자성: `inputEventAtomicity.test.ts`, `readModelChangeJournalPersistence.integration.test.ts`.
|
||||
|
||||
순수 fixture는 분모, 0/null, 소수 수입, 미수집, 외국 주둔, 과거 값의 독립성,
|
||||
민감 meta 제외와 단일 순회를 검증한다. PostgreSQL SQL count/WAL/실행계획,
|
||||
권한 HTTP, CHE/HWE Chromium과 전체 source inventory는 아직 남아 있다.
|
||||
|
||||
월 저장 검증: `playAuditCollection.test.ts`, `playAuditPersistence.integration.test.ts`와
|
||||
확장한 `monthlyBoundaryPrePersistence.integration.test.ts`. 정확한 명령·결과는
|
||||
상위 보고서 `2026-09-16-플레이-감사-월별-저장.md`에 기록한다.
|
||||
|
||||
## 2026-09-26 국가 그래프와 당월 정산
|
||||
|
||||
`nationSeries.currentSettlement`는 현재 월을 포함한 페이지에서만 같은 RepeatableRead
|
||||
transaction의 `world_state.meta.playAuditFlows`를 읽는다. 국가·자원·연월과 유한 숫자를
|
||||
확인하여 commit된 수입/지급을 반환한다. 1월 금, 7월 쌀 정산이 월말 표본을 기다리지 않고
|
||||
노출되며, 당월 관측 없음·부분 관측·관측됨을 구별한다. 월말/반기 흐름 합계와 완전성은
|
||||
기존 계약을 유지한다. gameplay 정산, RNG, flush 순서는 변경하지 않는다.
|
||||
|
||||
새 국가 표본은 집단별 `crew` 합계를 저장한다. 기존 JSON의 crew 누락은 null로 읽고,
|
||||
시계열 조회 때 그 국가와 해당 표본 ID에 한정하여 PostgreSQL이 장수 표본을 한 번 집계한다.
|
||||
저장된 집단 인원수와 원본 행 수가 일치할 때만 합계를 사용한다. 원본 누락은 null,
|
||||
관측된 빈 집단은 0이며 live 장수로 과거를 채우지 않는다. 최대 1200개 표본에 한정된
|
||||
추가 GROUP BY 조회이며, 새 집계가 있는 표본은 원본 재집계에서 제외한다.
|
||||
|
||||
차트는 [Chart.js line](https://www.chartjs.org/docs/latest/charts/line.html)과
|
||||
[responsive](https://www.chartjs.org/docs/latest/configuration/responsive.html) 계약을 사용한다.
|
||||
필요한 line 구성요소만 등록하고 resize·unmount를 처리하며 null 구간을 연결하지 않는다.
|
||||
차트와 수치 표는 같은 응답을 사용하고 집단/지표 전환은 추가 API를 호출하지 않는다.
|
||||
|
||||
## 2026-09-26 원래 목표 재점검 보완
|
||||
|
||||
- `generals`는 금/쌀/병력/훈련/사기, 통솔/무력/지력, 경험/공헌, 병종 숙련도 5종의
|
||||
서버 정렬을 현재와 월말/FINAL 모두 지원한다. 기본 ID 정렬은 기존 숫자 cursor를
|
||||
유지하고 수치 정렬은 `{id, value}` cursor를 쓴다. 수치가 같으면 ID 오름차순,
|
||||
NULL은 마지막이다. 검색·국가·도시·NPC 필터를 먼저 적용한 전체 집합을 정렬한다.
|
||||
- SQL 식별자는 allowlist이고 검색값은 parameter이며 LIKE 특수문자는 literal 처리한다.
|
||||
현재 조회는 ID/수치 조회와 projection 일괄 조회 2회, 과거는 표본 조회 1회다
|
||||
(공통 world/header 조회 제외). 장수별 추가 조회나 저장 경로 변경은 없다.
|
||||
- `cityMap({at?})`은 기존 감사 권한과 repeatable-read 경계에서 도시/국가 projection을
|
||||
읽는다. 지도 화면을 선택할 때만 호출하며 최대 1024개 도시를 허용한다. 초과하면
|
||||
오류로 목록 사용을 안내하고, 미수집 월은 빈 수집 상태로 반환한다.
|
||||
- 기존 MapViewer를 사용하고 도시 클릭은 해당 시점 상세와 주둔 장수 목록으로 연결한다.
|
||||
지도 소유/이름은 해당 표본을 사용하며 없는 표본을 무주 도시로 만들지 않는다.
|
||||
좌표/배경은 현재 profile의 map layout이다. 물리적 지도 버전의 역사 복원은 아니다.
|
||||
좌표가 없는 도시는 목록에서 확인하도록 ID를 안내한다. 지도는 모든 국가를 표시한다.
|
||||
- 이 보완은 R2 수치 정렬과 R3 지도 탐색의 누락을 닫는다. 자원 이동 원장, 접속/계정 조사,
|
||||
월중 전체 방문 이력, 취소 시즌 audit-only runtime, 전체 COST gate는 아직 완료하지 않았다.
|
||||
원 설계 전체 P1–P6 완료로 해석하지 않는다.
|
||||
+95
-75
@@ -2,10 +2,10 @@
|
||||
|
||||
## 문서 상태와 사용법
|
||||
|
||||
**설계 기준: 2026-09-16. 제품 기능은 부분 구현 상태다.** 이 문서는 관리자 플레이
|
||||
감사의 구현 goal과 완료 판정 기준이다. 문서 작성 완료는 기능 구현 완료가 아니다.
|
||||
후속 작업은 아래 요구사항 ID, 단계와 증거 표를 유지하며 진행 상태를 갱신한다.
|
||||
현재 구현 진행과 수집 지점은 [구현 inventory](play-audit-implementation.md)에 기록한다.
|
||||
이 문서는 플레이 감사의 구조·현재 구현 경계·남은 요구사항을 관리하는 기준 문서다.
|
||||
사용 방법과 DB 적용은 [운영 안내](../play-audit-operations.md)를 따른다.
|
||||
날짜별 구현·측정 기록은 Git 이력과 상위 작업공간의 `report/`에서 확인한다.
|
||||
아래 요구사항은 전체 목표이며 일부 구현만으로 완료를 의미하지 않는다.
|
||||
|
||||
사용자 결정으로 고정한 범위:
|
||||
|
||||
@@ -27,28 +27,50 @@
|
||||
|
||||
## 1. 현재 기반과 추가 작업
|
||||
|
||||
다음은 Core `f4aabec1fa13a0ae136b8ba96e8aa5dfa9a8e79e`의 정적 조사 결과다.
|
||||
Ref 조사 checkout은 `ng_compare@2239ff667b3e841c9fed69b53539437a79203806`,
|
||||
제품 기준선은 `devel@6c7f774fa1d49774a5924780516c88b8888cc1d7`이다.
|
||||
후속 구현 시작 시 현재 소스와 다시 대조한다. 이 표는 live DB/runtime 검증이 아니다.
|
||||
현재 소스의 책임은 다음과 같다. 경로는 Core root 기준이며 운영 적용 여부와는 별개다.
|
||||
|
||||
| 기반 | 확인한 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` | 최신 활동·누적 통계이며 모든 가입·로그인·장수 생성 시도의 사건 원장이 아님 |
|
||||
| 책임 | 구현 위치 | 경계 |
|
||||
| ------------------- | ------------------------------------------------------------------- | ------------------------------------------------------ |
|
||||
| 월말 상태·정산·집계 | `app/game-engine/src/playAudit/snapshot.ts`, `collection.ts` | 현재 정산과 확정 월말을 구분하며 미수집은 null |
|
||||
| 외교·정책 사건 | 같은 디렉터리의 `diplomacy.ts`, `policy.ts`, `policyPersistence.ts` | 사건 순서와 당시 불변 정책 참조 보존 |
|
||||
| NPC 결정 수집·저장 | `decision.ts`, `decisionPersistence.ts` | 실제 실행 경로만 관측하며 RNG를 추가 소비하지 않음 |
|
||||
| 감사 실패 격리·보존 | `bestEffort.ts`, `retention.ts`, `retentionWorker.ts` | 6절의 savepoint·누락 표시·기수 분리 계약 |
|
||||
| 읽기 API | `app/game-api/src/router/playAudit/` | profile 권한, 현재/과거 projection, 제한된 목록과 상세 |
|
||||
| 화면 | `app/game-frontend/src/components/playAudit/` | 운영 안내의 현재 사용 범위 |
|
||||
| DB 모델 | `packages/infra/prisma/game.prisma`, `gateway.prisma` | 정식 migration으로 추가하며 기존 원장과 구분 |
|
||||
|
||||
위 engine source의 생략한 prefix는 `app/game-engine/src/turn/`, game router는
|
||||
`app/game-api/src/router/`다. Prisma 모델은 `packages/infra/prisma/game.prisma`와
|
||||
`gateway.prisma`에 있다.
|
||||
현재 장수 수치 정렬, 현재/과거 도시 지도, 요청 처리 조회까지 제공한다.
|
||||
후보 내부 조건 전체, 상세 자원 이동 원장, 계정/IP HMAC 조사, 월중 전체 행동 이력,
|
||||
취소 후 감사 전용 runtime과 전체 비용 gate는 미완성이다. R7-A~F는 이 후속 범위를
|
||||
정의하므로 완료된 과거 계획으로 취급하지 않는다.
|
||||
|
||||
### 현재 읽기·수집의 세부 경계
|
||||
|
||||
- 국가 `currentSettlement`는 같은 RepeatableRead에서 읽은 현재 월의 확정 정산이다.
|
||||
월말 snapshot과 합치지 않는다. 이전 월은 `beforeMonthChanged`에서 닫고 새해 금·7월 쌀
|
||||
지급은 진입한 월의 flow에 누적한다. 과거 crew 집계가 없으면 일치하는 snapshot 표본만
|
||||
제한적으로 집계하며 현재 장수 값으로 대체하지 않는다.
|
||||
- 장수 목록은 현재/월말/FINAL의 금·쌀·병력·훈련·사기·능력·경험·공헌·숙련 정렬을
|
||||
지원한다. SQL 정렬 필드는 허용 목록을 사용하며 동률은 ID 오름차순, null은 마지막이다.
|
||||
- 도시 지도는 당시 소유·이름을 현재 지도 좌표·배경에 표시한다. 과거 지형 복원이 아니며
|
||||
snapshot 누락을 중립 도시로 채우지 않는다. 1,024개 초과 도시는 명시적으로 거부한다.
|
||||
- 결정 목록은 월을 생략하면 가장 최근 기록 월을 선택한다. 명시한 과거 월은 비어 있어도
|
||||
유지하며 다음 페이지도 같은 월을 사용한다. 기본50건/최대200건이고 목록에서 전체 trace나
|
||||
COUNT를 읽지 않는다. 상세는128항목 chunk를 기본1개/최대4개씩 읽는다.
|
||||
- 결정 ID는 server/general/tick/revision/phase로 결정한다. header hash와 unique key로
|
||||
중복·충돌을 구분하고128항목씩 chunk로 나눠200행 단위로 저장한다. pending 기록은
|
||||
capture/restore/peek/ack 경계를 따른다. 감사 저장 실패 처리의 기준은 6.1절이다.
|
||||
- `DECISION_START`의 schema1 `effectivePolicy`는 서버→국가·자동화·NPC 규칙을 합친
|
||||
명시적 값이다. 우선순위·허용 여부·국가 정책20개 값·부대 편성을 저장하며 raw meta나
|
||||
seed를 노출하지 않는다. 동적 후보 조건·지급 상한·별도 자동화 권한 판정은 포함하지 않는다.
|
||||
- 실행 시도는 입력·조건·대기·문맥 검사와 대체 시도를 실제 순서대로 기록한다. 검사를
|
||||
재실행하지 않으며 ATTEMPTS coverage로 이전 PROCEDURES 기록과 구분한다.
|
||||
상세 중첩은 최대5단계, 검사 항목은 최대4개다. 수동 명령이 AI를 거치지 않으면 AI 결정은 없다.
|
||||
- 실행 버전은 profile의 `buildCommitSha` 또는 `TURN_BUILD_COMMIT_SHA`의 전체40/64자리
|
||||
SHA를 사용한다. 미지정·잘못된 값과 과거 기록은 null이며 현재 버전으로 채우지 않는다.
|
||||
- `requestState`는 같은 기수의 정책 버전/외교 사건에서 참조한 `InputEvent`의 requestId와
|
||||
inputSequence를 함께 확인한다. world·사건·요청 최대3회 읽기로 현재 상태·시도 횟수·처리
|
||||
좌표·결과/오류 존재 여부를 반환한다. 원문 payload/error나 시도별 전체 이력은 제공하지 않는다.
|
||||
|
||||
Ref `hwe/_admin5.php`의 국가·평금쌀·병종 숙련 통계, `_admin7.php`의 전체
|
||||
장수 로그, `_admin8.php`의 외교정보는 **계승할 정보·조사 목적**의 근거다.
|
||||
@@ -56,6 +78,26 @@ Ref `hwe/_admin5.php`의 국가·평금쌀·병종 숙련 통계, `_admin7.php`
|
||||
공통 계정 상관 조사와 알려진 버그 검색 프리셋은 **비교 불가인 Core 신규 기능**이다.
|
||||
감사 기능을 추가하면서 Ref 계산이나 기존 일반 유저 권한을 변경하지 않는다.
|
||||
|
||||
### 최초 관측·기수·정리 경계
|
||||
|
||||
- 조회 달력의 하한은 유효한 `world.meta.initYear/initMonth`를 우선한다. 없으면 시나리오
|
||||
시작 연도와 현재 연도 중 이른 연도의1월을 사용한다. 달력 하한은 수집 시작의 증거가
|
||||
아니며 실제 자료 존재는 월 header로 확인한다. tick은 DB bigint 정밀도를 보존한다.
|
||||
- 초기 정책·관계는 복구된 clock과 이미 로드된 상태를 관측하고 readiness 전에 저장한다.
|
||||
재야 관계는 제외하며 원인·actor·과거 변경 시점을 추정하지 않는다. 정책 포인터는 기수와
|
||||
국가 identity를 확인하고 CAS·권한 검사를 통과한 실제 변경만 버전으로 남긴다.
|
||||
`OBSERVED_GAP`은 불일치의 재관측이지 과거 변경 이력의 복원이 아니다.
|
||||
- `documentBaseline.ts`는 도입 표식이 없는 기수의 보유 문서를 ID cursor200건씩 읽는다.
|
||||
원문은 불변 ID/hash로 참조하고 당시 상태·서명·이전 문서 번호만 보존한다. 빈 결과에도
|
||||
도입 표식을 저장하며 정상 재시작은 다시 전체 문서를 읽지 않는다. 문서의 상태·서명과
|
||||
달리 본문·작성자·작성 시각·국가쌍·prevId 변경은 DB trigger가 거부한다.
|
||||
- 초기 저장은 schema→lease→CLOCK→GENERAL_ACCESS 잠금 순서와 기수·clock authority를
|
||||
지킨다. commit 후 ack하며 감사 실패의 현재 처리 규칙은 6.1절을 따른다.
|
||||
- RESET 직후 조회는 identity 필터로 차단한다. retention은 별도 transaction에서 schema
|
||||
advisory lock과 현재 identity를 확인하고 부모 row를 잠근 뒤 자식 PK 최대200개씩 삭제한다.
|
||||
FK RESTRICT와 빈 header 삭제로 대량 cascade·새 기수 삭제를 막는다. worker는 남은 key부터
|
||||
재시작하며 이전 runtime·identity 누락에는 정리하지 않는다.
|
||||
|
||||
## 2. 화면·소유권·조회 계약
|
||||
|
||||
### 2.1 Profile 화면
|
||||
@@ -81,8 +123,8 @@ base prefix를 사용하며 `/image/*`나 API URL을 root 배포 기준으로
|
||||
|
||||
### 2.2 API·권한
|
||||
|
||||
game-api가 `playAudit` 읽기 영역을 소유한다. 다음은 추가할 capability/API의 설계명이며
|
||||
현재 존재하는 endpoint가 아니다. 입력은 공통으로 대상·기간·cursor를 받고 상세는 별도 조회한다.
|
||||
game-api가 `playAudit` 읽기 영역을 소유한다. 아래 표는 요구사항 영역의 설계명이다. 실제 endpoint 이름과 제공 범위는
|
||||
`app/game-api/src/router/playAudit/index.ts` 및 1절을 기준으로 한다. 입력은 공통으로 대상·기간·cursor를 받고 상세는 별도 조회한다.
|
||||
|
||||
| 조회 영역 | 최소 계약 |
|
||||
| ----------------------------------- | ----------------------------------------------------------------------------------- |
|
||||
@@ -424,38 +466,40 @@ NPC trace의 정책 참조와 사건 연결 외에 매 턴 전체 world를 seria
|
||||
비용 표에 남긴다. 비용이 크면 중복·반복 query·직렬화·batch/index를 먼저 개선한다.
|
||||
자료 샘플링·생략·보존기간 축소는 성능 최적화로 몰래 처리하지 않고 명시적 설계 변경으로 다룬다.
|
||||
|
||||
## 8. 단계·검증·goal 인계
|
||||
### 7.4 NPC 결정 비용 probe
|
||||
|
||||
### 8.1 순차 구현 체크리스트
|
||||
`app/game-engine/test/playAuditDecisionCost.integration.test.ts`는
|
||||
`PLAY_AUDIT_COST_DATABASE_URL`이 가리키는 격리 PostgreSQL을 사용한다.
|
||||
정식 migration을 적용한 `play_audit_cost_decision_fixture` schema가 필요하며
|
||||
fixture 테이블을 비우므로 공유 DB에는 실행하지 않는다.
|
||||
|
||||
각 단계는 코드·mapping·fixture·보고서·관련 commit을 포함한다. 병렬 branch나
|
||||
별도 goal을 자동 생성하지 않는다. 완료한 단계가 전체 구현 완료를 대신하지 않는다.
|
||||
```bash
|
||||
pnpm --filter @sammo-ts/game-engine test playAuditDecisionCost.integration.test.ts --no-file-parallelism
|
||||
```
|
||||
|
||||
- [ ] 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·미검증 범위를 보고해 전체 완료를 판정한다.
|
||||
201개 결정×302항목으로200행 batch 경계를 넘겨201개 header·603개 chunk·60,702항목을
|
||||
검사한다. `/tmp/play-audit-decision-cost.json`에 JSON 크기, `pg_column_size`,
|
||||
table/TOAST/index 크기, transaction 시간, warm30회 목록 p50/p95와 EXPLAIN을 남긴다.
|
||||
URL·secret·원문 payload는 출력하지 않는다. 반복 RNG를 포함한 합성 자료라 압축률을
|
||||
운영 평균으로 사용할 수 없고, WAL·heap 및 전체 COST gate를 대체하지 않는다.
|
||||
|
||||
### 8.2 요구사항별 증거
|
||||
## 8. 요구사항별 검증
|
||||
|
||||
각 요구사항의 일부 구현과 검증은 구현 inventory/report에 기록한다. 아래 전체 합격 기준을
|
||||
각 요구사항의 일부 구현과 검증은 이 문서의 1절과 날짜별 report에 기록한다. 아래 전체 합격 기준을
|
||||
충족하기 전에는 요구사항 전체를 완료로 체크하지 않는다.
|
||||
|
||||
| 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 측정 보고 |
|
||||
| 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 실패 보존·중복 방지·기수 분리 | 실제 PostgreSQL migration 전체/증분/no-op, 실패·복구·정리 fixture |
|
||||
| COST | 7절 필수 gate와 계측 전후 비용 검토 완료 | SQL count·실행계획·WAL/bytes·p95·heap 측정 보고 |
|
||||
|
||||
계측 전후 fixed seed 비교는 명령뿐 아니라 RNG 소비 순서, state, 기존 로그와
|
||||
side effect까지 포함한다. 계승 계약에 영향을 준 경로는 Ref 차등을 추가하고 감사 UI 자체는
|
||||
@@ -465,27 +509,3 @@ GUI는 같은 Chromium·viewport·DPR·zoom·font·fixture 조건에서 실제 `
|
||||
`/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/`에 기록한다. 이 문서의 체크박스는
|
||||
단순 계획·시도·의도로 완료 처리하지 않는다.
|
||||
|
||||
### 8.4 사용량을 고려한 중간 전달
|
||||
|
||||
2026-09-16 사용자는 주간 잔여 약42%에서 약20%를 남기고 현재 작업물을 마무리하여
|
||||
사용 가능한 기능부터 이용할 수 있게 하며 DB 스키마도 미리 준비하도록 지시했다.
|
||||
이에 따라 큰 미완성 기능을 추가로 벌이기보다 현재 수집·조회·권한의 회귀 검증,
|
||||
정식 migration·운영 안내, 현재 main 통합과 push를 우선한다. 운영 적용은 대상 프로필을
|
||||
확인한 범위에서 수행한다. 구현되지 않은 NPC trace/조사 도구나 미검증 COST gate를
|
||||
완료로 표시하지 않는다. 후속 설계 범위는 삭제하지 않고 [운영 안내](../play-audit-operations.md)의
|
||||
사용 가능/미완성 경계와 구현 inventory에 남긴다.
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
# Game clock reconciliation implementation plan
|
||||
|
||||
Baseline: `main@b91dcbcaaac5acd4c7349cd3ed0996c547f58756`
|
||||
|
||||
Branch: `test/game-clock-reconciliation-20260903`
|
||||
|
||||
이 문서는 위 기준선·branch에서 진행한 구현 계획의 기록입니다. 체크 표시는 당시
|
||||
코드와 집중 검증이 있었다는 뜻이며, 현재 main·배포·운영 검증 상태를 나타내지 않습니다.
|
||||
현재 동작은 [게임 시계](../architecture/game-clock.md)와
|
||||
[재정렬 계약](../architecture/game-clock-reconciliation.md), 장애 처리는
|
||||
[복구 안내](./game-clock-recovery.md)를 읽으세요.
|
||||
|
||||
## Milestone 1 - authority and inventory
|
||||
|
||||
- [x] Branded `GameTick`, `ObservedGameInstant`, `ScheduleInstant`,
|
||||
`WallInstant`, and `ClockRevision` boundaries.
|
||||
- [x] Explicit clock phase and monotonic RUNNING projection.
|
||||
- [x] Exact alignment arithmetic preserving millisecond/sub-turn remainder.
|
||||
- [x] Opening tick zero and PREOPEN executable floor in the shared seeder.
|
||||
- [x] Schema columns for phase, revision, and deadline generation.
|
||||
- [x] Suspension, participant-checksum, and Redis projection outbox tables.
|
||||
- [x] Machine-readable DB/Redis/JSON participant inventory and architecture gate.
|
||||
- [x] Turn flush lock prefix and phase/revision/generation fence.
|
||||
- [x] Empty and upgraded database migration execution evidence.
|
||||
|
||||
## Milestone 2 - exact DB reconciliation
|
||||
|
||||
- [x] Suspension start command with DB wall time and idempotent source revision.
|
||||
- [x] Exact resume plan transaction with deterministic participant lock order.
|
||||
- [x] SHIFT adapters for cursor, generals, active auctions, message expiry, vote
|
||||
end, select pool, and NPC selection windows.
|
||||
- [x] KEEP checksum adapters for occurrences and accepted command coordinates.
|
||||
- [x] Explicit `LEGACY_COMPLETE_TURNS` and bounded `CATCH_UP` policies.
|
||||
- [x] Property tests for remaining distance, ordering, and history invariants.
|
||||
- [x] 24-hour and 65m17.250s PostgreSQL integration evidence.
|
||||
|
||||
## Milestone 3 - revisioned Redis and workers
|
||||
|
||||
- [x] Projection outbox claimer/retry/recovery state machine.
|
||||
- [x] Redis active revision and atomic due-pop script.
|
||||
- [x] Auction OPEN/FINALIZING revision and generation fence.
|
||||
- [x] Tournament durable tick dual-write and projection rebuild.
|
||||
- [x] DB-commit/Redis-failure crash-restart test.
|
||||
- [x] Readiness integration in Gateway/API process health.
|
||||
|
||||
## Milestone 4 - command and lifecycle workflows
|
||||
|
||||
- [x] All durable input events record accepted tick and accepted revision.
|
||||
- [x] Processing converts accepted coordinates across revisions or fails closed.
|
||||
- [x] Gateway pause/resume/open orchestration writes the DB clock phase.
|
||||
- [x] Unification wait becomes a durable `UNIFICATION_WAIT` suspension.
|
||||
- [x] Alignment, optional rate change, invader IDs/RNG, creation, first schedule,
|
||||
outbox, verification, and RUNNING transition form one retry-safe workflow.
|
||||
- [x] Multi-host drift and general-access/clock-operation deadlock tests.
|
||||
|
||||
## Milestone 5 - test-branch release gate
|
||||
|
||||
- [x] Full typecheck, architecture, lint, unit, build, and non-conditional
|
||||
integration suites.
|
||||
- [x] Dedicated PostgreSQL/Redis conditional integration suite with skip count
|
||||
recorded.
|
||||
- [x] Recovery runbook exercised from each incomplete status.
|
||||
- [x] Admin status/readiness exposes revision, phase, participant checksums, and
|
||||
incomplete outbox state.
|
||||
- [ ] User-test deployment evidence is recorded separately from Git push.
|
||||
- [x] All `FORBID` inventory entries are removed by typed migrations or proven
|
||||
inactive preconditions.
|
||||
|
||||
## Evidence log
|
||||
|
||||
### 2026-09-03 - authority foundation
|
||||
|
||||
- `pnpm test:bootstrap`: dependency installation, Prisma generation, and package
|
||||
preparation passed in the dedicated worktree.
|
||||
- `CI=1 TURBO_CONCURRENCY=1 pnpm typecheck`: 21/21 tasks passed.
|
||||
- `CI=1 TURBO_CONCURRENCY=1 pnpm test`: 12/12 package tasks passed. Conditional
|
||||
suites remain classified separately and are not integration evidence.
|
||||
- `CI=1 TURBO_CONCURRENCY=1 pnpm build`: 26/26 tasks passed.
|
||||
- `TURBO_CONCURRENCY=1 pnpm lint`: passed with 36 pre-existing frontend
|
||||
warnings and no errors.
|
||||
- `pnpm check:architecture`: package boundaries passed; 21 authoritative clock
|
||||
fields and 18 participants were registered.
|
||||
- Migration SQL was generated, formatted, validated, and registered as the
|
||||
release manifest head. All 43 migrations applied to the empty dedicated
|
||||
PostgreSQL instance. A second schema upgraded from the previous head with an
|
||||
existing manual `world_state` row and verified `MANUAL:1:1` plus all three
|
||||
reconciliation tables.
|
||||
- Dedicated PostgreSQL/Redis fixtures proved exact 24-hour and 65m17.250s gaps,
|
||||
schedule distance/order preservation, occurrence checksums, live-lease
|
||||
fencing, a single durable outbox, Redis target revision, and recovery after a
|
||||
crash between Redis commit and DB finalization.
|
||||
|
||||
### 2026-09-03 - revisioned workers and input coordinates
|
||||
|
||||
- `CI=1 TURBO_CONCURRENCY=1 pnpm typecheck`: 21/21 tasks passed after the
|
||||
worker/input contract changes.
|
||||
- `CI=1 TURBO_CONCURRENCY=1 pnpm test`: 12/12 package tasks passed.
|
||||
- Dedicated PostgreSQL runs passed `databaseCommandQueue.integration.test.ts`
|
||||
6/6 and `runtimeClockShiftPersistence.integration.test.ts` 3/3 when executed
|
||||
sequentially; the latter now clears each single-world fixture before the next
|
||||
case. Dedicated Redis passed tournament source revision 4/4, including an
|
||||
atomic stale-clock rejection. API input-event integration passed 13/13.
|
||||
- The 24-hour/65m17.250s reconciliation suite passed 2/2 after the other DB
|
||||
suites. Conditional files share a deliberately dedicated schema and are run
|
||||
sequentially to prevent their fixture cleanup from racing another file.
|
||||
|
||||
### 2026-09-03 - Gateway lifecycle authority
|
||||
|
||||
- Runtime `PAUSE`/`STOP` starts a durable maintenance suspension before the
|
||||
Gateway status and process reconciliation change. `RESUME` completes the DB
|
||||
reconciliation and Redis outbox before the profile becomes `RUNNING`.
|
||||
- Both an already-built overdue `RESERVED` profile and an overdue `PREOPEN`
|
||||
profile promote the game DB from `PREOPEN@0` before the Gateway status changes
|
||||
to `RUNNING`; the Redis clock phase is revision/generation fenced.
|
||||
- `pnpm --filter @sammo-ts/gateway-api test` passed 313 tests with 35
|
||||
environment-conditional skips. Gateway typecheck and target lint passed.
|
||||
|
||||
### 2026-09-03 - atomic unification wait
|
||||
|
||||
- A unification flush now commits the finalization, actionable prompts, and one
|
||||
deterministic `UNIFICATION_WAIT` suspension together. A late archive failure
|
||||
rolls the whole boundary back; retry creates one ledger.
|
||||
- The suspended command queue admits only an invader decision tied to the
|
||||
active ledger. The daemon-authorized command transaction applies a 36-hour
|
||||
exact gap, preserves participant positions, changes the fixture rate from 10
|
||||
to 20 minutes, creates one invader nation and ten deterministic generals with
|
||||
future first turns, and writes one final-rate projection outbox.
|
||||
- The dedicated PostgreSQL/Redis fixture reached `RUNNING@2/2` only after the
|
||||
Redis projection. DB-wall versus a mocked 12-hour host drift and concurrent
|
||||
general-access lock acquisition completed without drift or deadlock.
|
||||
- `CI=1 TURBO_CONCURRENCY=1 pnpm typecheck` passed 21/21 tasks,
|
||||
`CI=1 TURBO_CONCURRENCY=1 pnpm test` passed 12/12 package tasks,
|
||||
`CI=1 TURBO_CONCURRENCY=1 pnpm build` passed 26/26 tasks, and workspace lint
|
||||
passed. `pnpm check:architecture` registered 22 authoritative clock fields
|
||||
and 18 participants.
|
||||
- Dedicated sequential PostgreSQL/Redis runs passed clock reconciliation 3/3,
|
||||
atomic unification 1/1, command queue 7/7, runtime clock persistence 3/3,
|
||||
API input-event boundary 13/13, and tournament revision 4/4: 31/31 enabled
|
||||
tests with zero skips. The queue and API suites now create and remove their
|
||||
own clock fixtures so a completed file cannot leak phase or initialization
|
||||
state into the next file.
|
||||
- User-test deployment and public runtime evidence remain deliberately open:
|
||||
Git push is not deployment, and no deployment was authorized in this work.
|
||||
|
||||
### 2026-09-03 - completion audit
|
||||
|
||||
- The invader response now reads the exact post-alignment world snapshot even
|
||||
when the turn rate is unchanged. The HWE-shaped regression asserts all ten
|
||||
first turns are in the aligned next-month window rather than already due.
|
||||
- The ordinary flush fence now uses the same incomplete-row dual-read rule as
|
||||
`worldLoader`: a legacy row is `MANUAL` until all four clock snapshot fields
|
||||
exist. Input-event acceptance remains fail-closed until initialization.
|
||||
- Auction OPEN/FINALIZING recovery fixtures and direct PROCESSING input-event
|
||||
fixtures now carry explicit phase/revision/generation coordinates. The
|
||||
optional Ref-only troop parity suite is registered only in Ref mode, so the
|
||||
Core conditional gate reports only tests it actually ran.
|
||||
- `pnpm check:architecture` passed with 22 authoritative fields and all 18
|
||||
required DB/JSON participants. The gate now rejects duplicate or `FORBID`
|
||||
participants, missing required participants, and unimplemented/duplicate
|
||||
Redis participants.
|
||||
- `CI=1 TURBO_CONCURRENCY=1 pnpm test:integration:conditional` passed 79 files
|
||||
and 234 tests against isolated PostgreSQL/Redis schemas with zero skipped and
|
||||
zero failed files.
|
||||
- On the audited diff, `pnpm check:architecture` passed, full typecheck passed
|
||||
21/21 tasks, workspace test passed 12/12 tasks, build passed 26/26 tasks, and
|
||||
workspace lint completed without errors.
|
||||
|
||||
The required acceptance cases map to automated evidence as follows:
|
||||
|
||||
| Contract | Automated evidence |
|
||||
| ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| PREOPEN signed tick, tick-zero opening, executable floor; RUNNING rewind monotonicity | `packages/common/test/gameClock.test.ts`, `app/game-engine/test/scenarioSeeder.test.ts`, `app/gateway-api/test/orchestratorOperations.test.ts` |
|
||||
| Exact 24-hour and 65m17.250s gaps; ordering, remaining distance, occurrence history | `app/game-engine/test/clockReconciliation.integration.test.ts`, `packages/common/test/gameClock.test.ts` |
|
||||
| DB commit/Redis failure and incomplete-status recovery | `app/game-engine/test/clockReconciliation.integration.test.ts`, `app/game-engine/test/unificationFinalization.integration.test.ts` |
|
||||
| Auction OPEN/FINALIZING and tournament revision races | `app/game-api/test/auctionWorker.integration.test.ts`, `app/game-api/test/tournamentStoreRevision.integration.test.ts` |
|
||||
| Pending commands crossing a clock revision | `app/game-engine/test/databaseCommandQueue.integration.test.ts`, `app/game-api/test/inputEventBoundary.integration.test.ts` |
|
||||
| Delayed opening | `app/gateway-api/test/orchestratorOperations.test.ts`, `app/game-engine/test/scenarioSeeder.test.ts` |
|
||||
| 36-hour unification wait, rate change, deterministic retry, future invader turns | `app/game-engine/test/unificationFinalization.integration.test.ts`, `app/game-engine/test/unificationInvaderResume.test.ts` |
|
||||
| DB wall despite host drift; general-access lock ordering | `app/game-engine/test/clockReconciliation.integration.test.ts`, `app/game-engine/test/profileSchemaAdvisoryLock.integration.test.ts` |
|
||||
| Generated exact-gap ordering/remaining/history invariants | `packages/common/test/gameClock.test.ts` |
|
||||
|
||||
Deployment and public-runtime evidence remain outside this audit and are still
|
||||
open pending explicit user-test deployment authorization.
|
||||
+36
-32
@@ -13,39 +13,43 @@
|
||||
관리자 감사는 [운영 안내](../play-audit-operations.md)에서 현재 기능을,
|
||||
[설계](../design/play-audit.md)에서 요구사항과 배경을 확인합니다.
|
||||
|
||||
| 작업 | 문서 | 코드 시작점 |
|
||||
| --------------------- | --------------------------------------------------------------- | ------------------------------ |
|
||||
| 전체 구조 | [아키텍처 개요](../architecture/overview.md) | `app/`, `packages/`, `tools/` |
|
||||
| process·worker·daemon | [런타임 아키텍처](../architecture/runtime.md) | app server와 CLI |
|
||||
| profile·Gateway 배포 | [릴리스 운영 매뉴얼](../release-operations.md) | Admin GUI와 release-controller |
|
||||
| 파일 위치 | [파일 지도](./code-map.md) | router, handler, schema |
|
||||
| package 의존 방향 | [패키지와 파일 경계](../architecture/package-boundaries.md) | `packages/*`, `app/*` |
|
||||
| 명령·전투·효과 | [도메인과 조립](./domain-and-classes.md) | `packages/logic` |
|
||||
| mutation·flush | [요청·턴·저장](./request-turn-persistence.md) | game API, game engine |
|
||||
| action module | [행동 모듈 프로토콜](../architecture/action-module-protocol.md) | `actionModules/` |
|
||||
| ref 비교 | [차등 검증](../architecture/turn-state-differential-testing.md) | `tools/integration-tests` |
|
||||
## 현재 구조와 구현 계약
|
||||
|
||||
[구조 문서 찾아보기](./reference-map.md)에는 시계·재시도·실시간 갱신·측정·운영 자료를
|
||||
질문별로 모았습니다. 과거 계획과 현재 계약의 구분도 여기서 확인할 수 있습니다.
|
||||
| 질문 | 문서 |
|
||||
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| 무엇이 어디서 실행되나요? | [개요](../architecture/overview.md), [런타임](../architecture/runtime.md) |
|
||||
| 어느 파일부터 읽나요? | [파일 지도](./code-map.md), [패키지 경계](../architecture/package-boundaries.md) |
|
||||
| 한 행동이 어떻게 계산되나요? | [도메인과 조립](./domain-and-classes.md), [행동 모듈](../architecture/action-module-protocol.md) |
|
||||
| 언제 저장되고 실패는 어떻게 처리하나요? | [요청·턴·저장](./request-turn-persistence.md), [API 재시도](../architecture/api-input-event-replay.md) |
|
||||
| 시간 정지·재개는 어떻게 되나요? | [게임 시계](../architecture/game-clock.md), [시간 도메인 목록](../architecture/time-domains.md), [재정렬 계약](../architecture/game-clock-reconciliation.md) |
|
||||
| 설정을 여러 파일에서 조합하나요? | [시나리오 합성](../architecture/scenario-composition.md) |
|
||||
| 화면에 변경이 어떻게 전달되나요? | [실시간 변경 원장](../architecture/realtime-change-journal.md) |
|
||||
| 전투 시뮬레이터가 어디서 계산하나요? | [브라우저 Worker](../architecture/battle-simulator-browser-worker.md) |
|
||||
| Ref와 무엇을 비교하나요? | [차등 검증](../architecture/turn-state-differential-testing.md) |
|
||||
| TypeScript 버전이 왜 둘인가요? | [도구 체인 정책](../architecture/typescript-version.md) |
|
||||
|
||||
## 경계
|
||||
## 변경 범위를 빠짐없이 확인하는 목록
|
||||
|
||||
- `packages/logic`은 계산과 규칙을 소유합니다.
|
||||
- runtime I/O는 logic의 port를 app/infra adapter가 구현해 주입합니다.
|
||||
- `app/game-engine`은 clock, queue, AI, 월간 순서, transaction과 flush를
|
||||
소유합니다.
|
||||
- `app/game-api`는 transport, 인증, input validation과 request acceptance를
|
||||
소유합니다.
|
||||
- `packages/infra`의 Prisma schema와 migration은 영속 구조의 기준입니다.
|
||||
- `resources`는 scenario, map, unit set과 command profile을 구성합니다.
|
||||
- actor와 resource owner는 session과 DB에서 서버가 결정합니다.
|
||||
- `InputEvent`와 PostgreSQL transaction이 gameplay mutation의 내구성
|
||||
경계입니다.
|
||||
- Redis와 SSE는 session·worker·fan-out 경로이며 world commit의 대체 저장소가
|
||||
아닙니다.
|
||||
[엔진 호출 procedure 목록](../architecture/game-api-daemon-procedure-inventory.md)과
|
||||
[직접 변경·journal 목록](../architecture/game-api-direct-mutation-journal-inventory.md)은
|
||||
읽기 교재보다 변경 누락을 찾는 검토 자료입니다. 표의 조사 날짜·기준선과 현재
|
||||
router·검사 코드를 함께 확인하세요. 새 API가 늘어도 과거 조사 숫자가 자동으로
|
||||
현재 개수가 되지는 않습니다.
|
||||
|
||||
기능의 대응이 바뀌면 작업공간 루트 기준
|
||||
`docs/ref-core2026-mapping.md`에 entry point,
|
||||
호출 순서, 인증, DB mutation, RNG와 오류 경로를 갱신해 주세요.
|
||||
공통 작업 규칙과 계약 분류는 제품 저장소 루트의 `AGENTS.md`를 따릅니다.
|
||||
이 파일들은 핸드북 사이트 밖의 저장소 문서입니다.
|
||||
시계 참여자 JSON·mutation evidence TSV는 기계가 사용하는 자료입니다. 형식과
|
||||
검사 계약을 유지하며 해당 기능을 바꿀 때 갱신합니다.
|
||||
|
||||
## 측정·계획·운영 자료
|
||||
|
||||
| 자료 | 해석 범위 |
|
||||
| ----------------------------------------------------------------------------------- | ----------------------------------------------------------- |
|
||||
| [NPC 메모리 측정](../architecture/npc-lifecycle-memory-profile.md) | 지정 fixture의 메모리·생성/사망 부하. 운영 DB 처리량과 다름 |
|
||||
| [NPC 천통 시간 측정](../architecture/npc-unification-timing-benchmark.md) | 인메모리 계산 시간. 실제 배포 성능 보장이 아님 |
|
||||
| [시계 복구](./game-clock-recovery.md) | 상태·원장 확인과 같은 작업 재시도 |
|
||||
| [릴리스 운영](../release-operations.md) | 실제 배포 작업과 준비 확인 |
|
||||
| [관리자 콘솔](../admin-console.md), [플레이 감사 운영](../play-audit-operations.md) | 관리자 권한과 현재 조사 기능 |
|
||||
| [테스트 정책](../testing-policy.md) | 실행 준비·검증 종류·skip의 해석 |
|
||||
| [프론트엔드 CSS 구조](../frontend-css-architecture.md) | 화면 스타일의 소유권과 배치 계약 |
|
||||
|
||||
기능 설계의 목표, 코드에 있는 구현, 테스트로 확인한 범위, 실제 배포 상태는 서로
|
||||
다릅니다. 문서의 “완료”는 함께 적힌 날짜와 검증 범위로 해석하세요.
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
# 구조 문서 찾아보기
|
||||
|
||||
[기초 안내](./first-steps.md)와 [경험자 안내](./system-walkthrough.md)가 학습 경로를,
|
||||
아래 문서들은 세부 계약을 제공합니다. 목록·벤치마크·과거 계획을 처음부터 모두
|
||||
읽기보다 현재 질문에 해당하는 문서를 고르세요.
|
||||
|
||||
## 현재 구조와 구현 계약
|
||||
|
||||
| 질문 | 문서 |
|
||||
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| 무엇이 어디서 실행되나요? | [개요](../architecture/overview.md), [런타임](../architecture/runtime.md) |
|
||||
| 어느 파일부터 읽나요? | [파일 지도](./code-map.md), [패키지 경계](../architecture/package-boundaries.md) |
|
||||
| 한 행동이 어떻게 계산되나요? | [도메인과 조립](./domain-and-classes.md), [행동 모듈](../architecture/action-module-protocol.md) |
|
||||
| 언제 저장되고 실패는 어떻게 처리하나요? | [요청·턴·저장](./request-turn-persistence.md), [API 재시도](../architecture/api-input-event-replay.md) |
|
||||
| 시간 정지·재개는 어떻게 되나요? | [게임 시계](../architecture/game-clock.md), [시간 도메인 목록](../architecture/time-domains.md), [재정렬 계약](../architecture/game-clock-reconciliation.md) |
|
||||
| 설정을 여러 파일에서 조합하나요? | [시나리오 합성](../architecture/scenario-composition.md) |
|
||||
| 화면에 변경이 어떻게 전달되나요? | [실시간 변경 원장](../architecture/realtime-change-journal.md) |
|
||||
| 전투 시뮬레이터가 어디서 계산하나요? | [브라우저 Worker](../architecture/battle-simulator-browser-worker.md) |
|
||||
| Ref와 무엇을 비교하나요? | [차등 검증](../architecture/turn-state-differential-testing.md) |
|
||||
| TypeScript 버전이 왜 둘인가요? | [도구 체인 정책](../architecture/typescript-version.md) |
|
||||
|
||||
## 변경 범위를 빠짐없이 확인하는 목록
|
||||
|
||||
[엔진 호출 procedure 목록](../architecture/game-api-daemon-procedure-inventory.md)과
|
||||
[직접 변경·journal 목록](../architecture/game-api-direct-mutation-journal-inventory.md)은
|
||||
읽기 교재보다 변경 누락을 찾는 검토 자료입니다. 표의 조사 날짜·기준선과 현재
|
||||
router·검사 코드를 함께 확인하세요. 새 API가 늘어도 과거 조사 숫자가 자동으로
|
||||
현재 개수가 되지는 않습니다.
|
||||
|
||||
시계 참여자 JSON·mutation evidence TSV는 기계가 사용하는 자료입니다. 형식과
|
||||
검사 계약을 유지하며 해당 기능을 바꿀 때 갱신합니다.
|
||||
|
||||
## 측정·계획·운영 자료
|
||||
|
||||
| 자료 | 해석 범위 |
|
||||
| ----------------------------------------------------------------------------------- | ----------------------------------------------------------- |
|
||||
| [NPC 메모리 측정](../architecture/npc-lifecycle-memory-profile.md) | 지정 fixture의 메모리·생성/사망 부하. 운영 DB 처리량과 다름 |
|
||||
| [NPC 천통 시간 측정](../architecture/npc-unification-timing-benchmark.md) | 인메모리 계산 시간. 실제 배포 성능 보장이 아님 |
|
||||
| [시계 구현 계획 기록](./game-clock-reconciliation-plan.md) | 당시 branch의 milestone. 현재 운영 상태의 기준이 아님 |
|
||||
| [시계 복구](./game-clock-recovery.md) | 상태·원장 확인과 같은 작업 재시도 |
|
||||
| [릴리스 운영](../release-operations.md) | 실제 배포 작업과 준비 확인 |
|
||||
| [관리자 콘솔](../admin-console.md), [플레이 감사 운영](../play-audit-operations.md) | 관리자 권한과 현재 조사 기능 |
|
||||
| [테스트 정책](../testing-policy.md) | 실행 준비·검증 종류·skip의 해석 |
|
||||
| [프론트엔드 CSS 구조](../frontend-css-architecture.md) | 화면 스타일의 소유권과 배치 계약 |
|
||||
|
||||
기능 설계의 목표, 코드에 있는 구현, 테스트로 확인한 범위, 실제 배포 상태는 서로
|
||||
다릅니다. 문서의 “완료”는 함께 적힌 날짜와 검증 범위로 해석하세요.
|
||||
+137
-1
@@ -1,5 +1,8 @@
|
||||
# Legacy MariaDB long-lived data migration
|
||||
|
||||
이 문서는 이관 범위·설정·CLI 실행·데이터 대응·전환 검증의 단일 기준입니다.
|
||||
도구 위치는 `tools/legacy-db-migration`이며 명령은 Core 저장소 root에서 실행합니다.
|
||||
|
||||
## Scope and safety boundary
|
||||
|
||||
`tools/legacy-db-migration` is the only supported importer. It has no HTTP
|
||||
@@ -20,7 +23,96 @@ the imported rows. A repeat import updates archive-owned rows but does not
|
||||
replace a live Gateway account's password, reset status, login/display identity,
|
||||
OAuth connection, roles, sanctions, consent, icon or login timestamps.
|
||||
|
||||
### Full and incremental execution
|
||||
## Source restore
|
||||
|
||||
Restore each compressed table dump into a private MariaDB database before
|
||||
running this tool. Do not expose that database on a public interface. The dump
|
||||
directory is intentionally Git-ignored.
|
||||
|
||||
```sh
|
||||
gzip -cd /path/to/db_dumps/root/member.sql.gz | mariadb root_dump
|
||||
gzip -cd /path/to/db_dumps/che/ng_games.sql.gz | mariadb che_dump
|
||||
```
|
||||
|
||||
Restore all tables defined by ref even though the CLI intentionally projects
|
||||
only long-lived tables. This lets the dry-run verify the source inventory and
|
||||
keeps the original dump as the recovery source.
|
||||
|
||||
Database URLs belong in a Git-ignored environment file or injected process
|
||||
environment. They are deliberately not accepted as command-line flags.
|
||||
|
||||
## Ordered migration plan
|
||||
|
||||
Copy `tools/legacy-db-migration/migration-plan.example.json` to the Git-ignored
|
||||
`tools/legacy-db-migration/migration-plan.json`.
|
||||
Set its mode to 0600, then enter the MariaDB host, port, database and user for
|
||||
Gateway and each game profile. A password can come from a separate mode-0600
|
||||
file (recommended), an environment variable, or directly from the mode-0600
|
||||
plan. Target PostgreSQL URLs remain in the named environment variables.
|
||||
|
||||
When the Gateway source contains a non-default member icon, `gateway.userIcons`
|
||||
is mandatory. Mount Ref's `d_pic` directory read-only as `sourceDirectory`, and
|
||||
mount the Core2026 sam-image upload secret as a mode-0600 `uploadSecretFile`.
|
||||
The two URL fields normally point to `https://sam-image.hided.net` and its
|
||||
`/icons` path. The importer never adds account images to the image Git tree.
|
||||
An invalid historical file blocks the plan by default. After byte-level review,
|
||||
its member number may be listed in `excludedMemberNumbers`; the exclusion is
|
||||
accepted only while that exact member still has invalid image geometry/format.
|
||||
A valid file or stale/missing member exclusion fails closed. An unchanged
|
||||
invalid Ref selection is reset to the default icon instead of publishing bad
|
||||
bytes; a newer Core selection is preserved.
|
||||
|
||||
```sh
|
||||
mkdir -p tools/legacy-db-migration/secrets
|
||||
chmod 700 tools/legacy-db-migration/secrets
|
||||
cp tools/legacy-db-migration/migration-plan.example.json \
|
||||
tools/legacy-db-migration/migration-plan.json
|
||||
chmod 600 tools/legacy-db-migration/migration-plan.json
|
||||
chmod 600 tools/legacy-db-migration/secrets/*
|
||||
|
||||
pnpm migrate:legacy -- check-plan \
|
||||
--config tools/legacy-db-migration/migration-plan.json
|
||||
pnpm migrate:legacy -- run-plan \
|
||||
--config tools/legacy-db-migration/migration-plan.json --mode full
|
||||
pnpm migrate:legacy -- run-plan \
|
||||
--config tools/legacy-db-migration/migration-plan.json --mode full --apply
|
||||
```
|
||||
|
||||
For profiles whose old file archive is available, add a `battleResults` block.
|
||||
`directory` may be a local absolute/plan-relative path, or `sshHost` may select
|
||||
the host from which the directory is read. The SSH target must be a configured
|
||||
host alias; do not put credentials in the plan.
|
||||
|
||||
```json
|
||||
"battleResults": {
|
||||
"sshHost": "serv",
|
||||
"directory": "/home/letrhee/web_symlinks/sam_hided_net/sam/che/logs/preserved"
|
||||
}
|
||||
```
|
||||
|
||||
`check-plan` opens every source and target without writing. Its stage JSON lists
|
||||
every included item as `inventory`, including source, target, strategy and the
|
||||
information transferred. Gateway preflight validates every local or already
|
||||
uploaded custom icon and reports the source split without issuing a PUT. A
|
||||
configured battle-result source also reports its
|
||||
season/file/byte counts. `run-plan` also
|
||||
preflights every stage before the first import, is a dry-run without `--apply`,
|
||||
and stops at the first failed stage. Completed earlier stages remain committed;
|
||||
rerunning is safe because the Gateway and each profile have independent locks,
|
||||
transactions and run records. The JSON output never includes a connection URL
|
||||
or password.
|
||||
|
||||
For a later delta, keep the same `sourceSet`, connection identity and source
|
||||
databases, restore or expose the newer snapshot, then run:
|
||||
|
||||
```sh
|
||||
pnpm migrate:legacy -- run-plan \
|
||||
--config tools/legacy-db-migration/migration-plan.json --mode incremental
|
||||
pnpm migrate:legacy -- run-plan \
|
||||
--config tools/legacy-db-migration/migration-plan.json --mode incremental --apply
|
||||
```
|
||||
|
||||
## Full and incremental execution
|
||||
|
||||
`run-plan` first connects to every configured MariaDB and PostgreSQL target and
|
||||
checks that the checkpoint migrations exist. Only after all stages pass does it
|
||||
@@ -51,6 +143,8 @@ The source of truth for eligibility is the checked ref schema, not every table
|
||||
that happens to exist in a dump. Tables outside that schema remain only in the
|
||||
recovery dump.
|
||||
|
||||
## Data mapping
|
||||
|
||||
### Gateway
|
||||
|
||||
| Legacy table | Target | Policy |
|
||||
@@ -198,6 +292,29 @@ excluded. In particular, `general`, `city`, `nation`, their turn queues,
|
||||
`ng_betting`, `reserved_open`, `select_pool`, `select_npc_token` and `plock`
|
||||
must not be used to reconstruct a running season.
|
||||
|
||||
## Commands
|
||||
|
||||
```sh
|
||||
LEGACY_ROOT_DATABASE_URL=... pnpm --filter @sammo-ts/legacy-db-migration migrate gateway
|
||||
LEGACY_GAME_DATABASE_URL=... pnpm --filter @sammo-ts/legacy-db-migration migrate game --profile che
|
||||
```
|
||||
|
||||
After reviewing the JSON counts and excluded-table reasons, add
|
||||
`GATEWAY_DATABASE_URL` or `GAME_DATABASE_URL` and repeat with `--apply`.
|
||||
|
||||
For game archives, `GAME_DATABASE_URL` points at that profile's Core schema.
|
||||
The importer writes completed-history data to the shared
|
||||
`legacy_archive` PostgreSQL schema and writes only inheritance projections to
|
||||
the selected current profile schema. Accepted profiles are
|
||||
`che,kwe,pwe,twe,nya,pya,hwe`; run them separately against the same PostgreSQL
|
||||
database.
|
||||
|
||||
The individual commands also accept `--mode incremental` and `--source-key`.
|
||||
Use the ordered plan for production so every configured connection is checked
|
||||
before the Gateway stage starts. The direct `gateway` command intentionally
|
||||
fails closed when its source contains custom icons because it has no secure
|
||||
structured icon-upload configuration; use `run-plan` for that source.
|
||||
|
||||
### Current-season comparison fixture
|
||||
|
||||
The archive exclusion above remains the production migration contract. The
|
||||
@@ -222,6 +339,25 @@ through `CURRENT_SEASON_CAPTURE_USER_ID` and
|
||||
`CURRENT_SEASON_CAPTURE_SOURCE_OWNER`. This mode must not be used for a live
|
||||
season or as a substitute for the long-lived archive cutover procedure.
|
||||
|
||||
Start from a cloned Core database whose scenario, year and month already match
|
||||
the Ref source. Dry-run verifies that contract and reports the planned counts:
|
||||
|
||||
```sh
|
||||
LEGACY_GAME_DATABASE_URL=... GAME_DATABASE_URL=... \
|
||||
pnpm --filter @sammo-ts/legacy-db-migration migrate current-season-fixture \
|
||||
--profile hwe --expected-scenario 2601 --expected-year 186 --expected-month 1
|
||||
```
|
||||
|
||||
Applying requires both destructive flags so an ordinary archive command cannot
|
||||
replace a running season accidentally:
|
||||
|
||||
```sh
|
||||
LEGACY_GAME_DATABASE_URL=... GAME_DATABASE_URL=... \
|
||||
pnpm --filter @sammo-ts/legacy-db-migration migrate current-season-fixture \
|
||||
--profile hwe --expected-scenario 2601 --expected-year 186 --expected-month 1 \
|
||||
--replace-current-season --apply
|
||||
```
|
||||
|
||||
## Archived play read model
|
||||
|
||||
`/past-plays` is an authenticated, read-only projection. The server derives the
|
||||
|
||||
@@ -1,28 +1,26 @@
|
||||
# 플레이 감사 사용과 DB 적용
|
||||
|
||||
플레이 감사는 각 프로필의 `/play-audit`에서 읽는다. Gateway 관리자 조치 원장과
|
||||
다른 기능이다. 현재 전달은 [전체 설계](design/play-audit.md)의 부분 구현이며
|
||||
구현·검증 source는 [inventory](design/play-audit-implementation.md)에 기록한다.
|
||||
이번 전달 대상은 sam.hided.net의 전체 프로필이다. 코드와 migration을 main에 push하고,
|
||||
운영자는 각 프로필에 직접 DB 보존 업데이트를 적용한다. 이 문서의 검증 결과는 실제
|
||||
운영 DB에 이미 적용되었다는 뜻이 아니다.
|
||||
다른 기능이다. 구조·수집 경계·남은 요구사항은 [설계와 구현 기준](design/play-audit.md)에서
|
||||
관리한다. 아래 적용 절차는 각 프로필의 DB 보존 업데이트를 위한 것이며,
|
||||
문서에 기능이 기재되어 있다고 운영 DB에 적용된 것은 아니다.
|
||||
|
||||
## 지금 사용할 수 있는 기능
|
||||
|
||||
| 화면 | 사용할 수 있는 정보 | 읽을 때 주의할 점 |
|
||||
| -------- | ---------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
||||
| 국가 | 월말 금·쌀·기술력, 세율·실제 정산, 유저/NPC/부대장 NPC별 자원·숙련 집계, 월/6개월 그래프 | 보유량은 마지막 월말, 수입/지급액은 기간 합. 부분 기간/미수집은 0과 다름 |
|
||||
| 장수 | 이름 부분 검색·장수 번호 정렬, 모든 국가·재야의 현재/월말 장수, 자원·능력·숙련·병력·훈련·사기·장비·특기·위치, 독립 로그 상세 | 현재 예약은 현재 조회에서만 제공. 과거 월말은 그달 모든 명령의 이력이 아님 |
|
||||
| 도시 | 현재/월말 소유·내정 상태와 국가별 주둔 장수 상세 연결 | 월말 주둔은 그달 모든 방문자가 아님. 지도 기반 탐색은 미완성 |
|
||||
| 외교 | 국가쌍·기간별 문서 제안/승인/철회/파기, 즉시 합의와 월간·명령 관계 전이, 생성/소멸 관계 | 문서 내용과 실제 관계는 별개. 최초 관측 이전 사건은 복원하지 않음 |
|
||||
| NPC 결정 | 장수별 개인·수뇌 판단, 절차 시도/차단, 관측한 RNG 결과와 선택·실제 실행·대체 시도 | 새 실행부터 수집하며 후보 내부 조건 전체는 미완성 |
|
||||
| 정책 | NPC 국가 값·국가/장수 우선순위·국방 설정의 기준 버전과 변경 전후, 당시 주체 | 수뇌용 공개 화면은 아직 없음. NPC 결정에서 저장된 당시 버전을 직접 조회 가능 |
|
||||
| 화면 | 사용할 수 있는 정보 | 읽을 때 주의할 점 |
|
||||
| -------- | --------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
||||
| 국가 | 월말 금·쌀·기술력, 세율·실제 정산, 유저/NPC/부대장 NPC별 자원·숙련 집계, 월/6개월 그래프 | 보유량은 마지막 월말, 수입/지급액은 기간 합. 부분 기간/미수집은 0과 다름 |
|
||||
| 장수 | 이름 부분 검색·장수 번호/수치 정렬, 모든 국가·재야의 현재/월말 장수, 자원·능력·숙련·병력·훈련·사기·장비·특기·위치, 독립 로그 상세 | 현재 예약은 현재 조회에서만 제공. 과거 월말은 그달 모든 명령의 이력이 아님 |
|
||||
| 도시 | 현재/월말 소유·내정 상태와 국가별 주둔 장수 상세 연결 | 월말 주둔은 그달 모든 방문자가 아님. 지도는 현재 좌표에 당시 소유를 표시 |
|
||||
| 외교 | 국가쌍·기간별 문서 제안/승인/철회/파기, 즉시 합의와 월간·명령 관계 전이, 생성/소멸 관계 | 문서 내용과 실제 관계는 별개. 최초 관측 이전 사건은 복원하지 않음 |
|
||||
| NPC 결정 | 장수별 개인·수뇌 판단, 절차 시도/차단, 관측한 RNG 결과와 선택·실제 실행·대체 시도 | 새 실행부터 수집하며 후보 내부 조건 전체는 미완성 |
|
||||
| 정책 | NPC 국가 값·국가/장수 우선순위·국방 설정의 기준 버전과 변경 전후, 당시 주체 | 수뇌용 공개 화면은 아직 없음. NPC 결정에서 저장된 당시 버전을 직접 조회 가능 |
|
||||
|
||||
목록/그래프와 선택 상세를 분리해 읽는다. 표는 기본50건이며 더 보기를 명시적으로
|
||||
누른다. 현재 상태는 수동으로 조회하며 백그라운드 polling은 하지 않는다. 필터·월·선택
|
||||
대상은 URL에 남으므로 같은 권한으로 직접 열기/새로고침할 수 있다.
|
||||
장수 이름은 선택 시점의 이름으로 부분 검색하며 영문 대소문자를 구분한다. 검색어는
|
||||
64자까지이고 `%`·`_`도 문자 그대로 찾는다. 장수 번호 정렬은 오름차순/내림차순을 제공한다.
|
||||
64자까지이고 `%`·`_`도 문자 그대로 찾는다. 장수 번호와 자원·능력·병력·숙련 등 수치 정렬은 오름차순/내림차순을 제공한다.
|
||||
|
||||
정책 버전과 외교 사건 상세의 **요청 처리 조회**는 연결된 요청의 현재 상태와 처리 시도
|
||||
횟수, 접수·처리 시각/tick, 결과·오류 기록 존재 여부를 보여준다. 조회 시각 기준 정보이며
|
||||
@@ -143,7 +141,7 @@ flush batch로 감사의 시간 제한을 보존한다. 브라우저는 같은 D
|
||||
- 계정/IP HMAC 조사, 상세 자원 이동, 예약 변경/실행 연결, 실패·rollback 조사와 알려진
|
||||
버그 사례 조회는 미완성이다. 자동 탐지·자동 제재 기능도 제공하지 않는다.
|
||||
- 일부 국가 생성/소멸 사건은 actor/request가 null이다. 원인을 현재 주체로 추정하지 않는다.
|
||||
- 자원·능력별 정렬·지도 탐색·모든 전투 지표/연결과 전체 COST gate는 남아 있다.
|
||||
- 모든 전투 지표/연결과 전체 COST gate는 남아 있다.
|
||||
- 격리 PostgreSQL/Redis 및 mock API를 쓰는 실제 Chromium 검증은 운영 HTTPS 검증과 다르다.
|
||||
|
||||
후속 NPC/행위/계정 저장소의 필드·순서·보존·인덱스 요구는 설계의 R5/조사 A~F와 비용
|
||||
|
||||
Reference in New Issue
Block a user