5.9 KiB
Named manual restore read-only audit
감사일: 2026-07-12 (KST)
범위는 운영 Oracle/MariaDB의 SELECT와
%LOCALAPPDATA%\MBN_STOCK_WEBVIEW\OperatorData의 production read path뿐이다.
원본 저장소, 운영 DB, 운영 파일, import marker/intent, Tornado2에는 쓰기나 명령을
보내지 않았다. 감사 출력에는 program code, title, stock name/code, VI 이름 목록,
파일 경로, 해시 또는 인증 정보를 포함하지 않는다.
현재 PList 문법 분포
실제 DC_LIST/PLAY_LIST 2,213행을 lossless parser로 읽고
LegacyPListAuditRoute.ManualFreshMaterialization 36행을 독립 closed grammar로 다시
분류했다.
| 종류 | 행 | 세부 분포 |
|---|---|---|
| GraphE | 36 | 주요매출 구성 12, 매출액 12, 영업이익 12 |
| GraphE 성장성 지표 | 0 | 현재 저장 행 없음 |
| FSell 순매도 | 0 | 현재 저장 행 없음 |
| VI 수동 목록 | 0 | 현재 저장 행 없음 |
| 합계 | 36 | 역사형 36, canonical형 0 |
따라서 이전 감사 문구의 GraphE/FSell/VI 36행은 “세 manual 복원 경로가 차단
대상”이라는 범주명이었고, 실제 36행의 내용은 전부 GraphE였다. FSell/VI 경로는
구현돼 있으나 현재 named PList에는 해당 행이 없다.
분류기는 Web의 named-manual-restore-workflow.js와 같은 두 문법을 닫힌 값으로
허용한다.
| 종류 | 역사형 | fresh materialize 후 canonical형 |
|---|---|---|
| GraphE | 한국어 시장 + 원본 GraphE 화면명 + 종목 표시명 + 1/1 |
canonical 시장 + canonical 화면명 + 동일 표시명 + 1/1 |
| FSell | 수동 순매도 3개 audience + 지수/순매도 + 1/1 |
INDIVIDUAL/FOREIGN/INSTITUTION + MANUAL_NET_SELL |
| VI | 쉼표 구분 이름 목록 + 계산된 1/N |
PAGED_VI + reserved snapshot subject + lowercase SHA-256 version |
실제 fresh materialization 결과
GraphE는 브라우저/네이티브 복원 순서를 그대로 축약해 다음 증거를 요구했다.
LegacyManualFinancialScreenService.GetAsync첫 조회- 현재 종목 마스터 검색(브라우저와 같은 최대 200건, truncated 금지)
- 같은 GraphE identity를 두 번째로 조회
- 두 조회의 64자리 Oracle row version이 동일한지 확인
- 시장·provider·종목 code가 유일한지 확인
ManualFinancialCutContracts.CreateSelection결과가 화면별 cut 계약과 일치하는지 확인
| 화면 | 후보 | fresh materialize 가능 | 차단 |
|---|---|---|---|
| 주요매출 구성 | 12 | 5 | 7 (GRAPH_E_DATA_INVALID) |
| 매출액 | 12 | 6 | 5 (GRAPH_E_AMBIGUOUS), 1 (GRAPH_E_DATA_INVALID) |
| 영업이익 | 12 | 11 | 1 (GRAPH_E_DATA_INVALID) |
| 합계 | 36 | 22 | 14 |
GRAPH_E_DATA_INVALID 9행은 production GraphE parser가 현재 INPUT_* 저장값을
typed record로 만들 수 없는 경우다. GRAPH_E_AMBIGUOUS 5행은 같은 화면에서
하나의 저장 identity가 둘 이상 조회되어 원본 schema의 이름-only identity로는
어느 행인지 결정할 수 없는 경우다. 원시 이름과 저장값은 출력하지 않았다.
22행의 성공은 “현재 raw PList 행을 fresh GraphE selection으로 복원 가능”하다는 뜻이다. 각 scene의 시세 조회, cut asset, PREPARE 또는 Tornado/PGM 송출 성공을 의미하지 않는다.
FSell/VI production source 확인
현재 named PList 후보는 0행이지만 향후 저장/복원을 막는 source 문제인지 별도로 검사했다.
- runtime guard:
ReadyLegacyData - FSell: 세 audience 모두 production CP949 parser로 opened-handle read 성공, audience마다 정확히 5행
- VI: production opened-handle read 성공, 9개 항목, 2 page, versioned 두 번째 read 일치
- 운영 디렉터리: 4개 data entry가 감사 전후 이름·크기·생성/수정 시각·SHA-256 모두 동일
- marker/intent: 감사 전후 생성 없음
- writes attempted:
false
ReadyLegacyData인 현재 profile에 byte-identical 4파일이 이미 존재하므로 importer를
실행하거나 완료 marker를 임의로 생성하지 않는다. 이번 결과는 기존 trusted store의
read 가능성만 증명한다.
Native request 대응
| 복원 종류 | fresh proof에 사용되는 native request |
|---|---|
| GraphE | request-manual-financial-load → search-stocks → request-manual-financial-selection |
| FSell | request-manual-net-sell-data |
| VI | request-vi-manual-list 후 반환 version을 reserved snapshot reference로 고정 |
구현 API는
src/MBN_STOCK_WEBVIEW.Core/Data/LegacyNamedManualRestoreAudit.cs의 closed classifier와
pure fresh-proof evaluator, 실행 경계는
tools/MBN_STOCK_WEBVIEW.DbSmoke/NamedManualRestoreReadOnlyAudit.cs의
NamedManualRestoreReadOnlyAudit.RunAsync다. 실행 경계는 root smoke에서 다음처럼
호출할 수 있다.
await NamedManualRestoreReadOnlyAudit.RunAsync(
runtime.Executor,
NamedManualRestoreReadOnlyAudit.DefaultTrustedDirectory(),
cancellationToken);
집중 Core 테스트 21건은 역사형/canonical GraphE·FSell·VI grammar, NXT 표시명 검색, GraphE double-read version, master identity 중복, FSell 5행, VI version/name/page drift를 검증한다.
남은 조치
GRAPH_E_DATA_INVALID9행은 운영자가 승인한 snapshot을 먼저 만들고 각 INPUT_* 원본 필드 형식을 확인한다.GRAPH_E_AMBIGUOUS5행은 이름-only identity 중 어느 행을 유지할지 운영자가 결정해야 한다. 감사 코드가 임의 삭제하거나 첫 행을 선택하지 않는다.- 수정은 별도의 운영 DB write 승인, 대상 identity, rollback 계획과 OutcomeUnknown 규칙을 갖춘 회차에서만 수행한다.
- 수정 후 같은 read-only 감사를 다시 실행해 36/36 fresh materialization을 확인한다.
- 그 다음 scene data/asset DryRun을 확인하고, 실제 PGM TAKE IN은 별도 Gate A/B 승인 회차에서만 수행한다.