309 lines
28 KiB
Markdown
309 lines
28 KiB
Markdown
# MBN_STOCK_WEBVIEW 송출 운영·검증 절차
|
|
|
|
이 문서는 마이그레이션된 WebView → `IPlayoutEngine` → K3D/Tornado2 경로를 검증할 때의 승인, 실행, 감시, 장애 복구와 rollback 기준이다. 기존 [`PLAYOUT.md`](PLAYOUT.md)의 K3D 설치·해시 핀·격리 진단 규칙을 대체하지 않고, 35개 scene과 PageN 동등성 검증에 필요한 운영 절차를 추가한다.
|
|
|
|
## 현재 상태
|
|
|
|
| 항목 | 상태 |
|
|
|---|---|
|
|
| 기본 모드 | `DryRun`; COM 객체와 `KTAPConnect`를 만들지 않음 |
|
|
| 자동 테스트 | 최종 전체 재검증에서 Core Debug/Release x64 각각 790/790, Playout 각각 374/374, Infrastructure 각각 65/65(합계 각 1,229/1,229), Web safety 12/12 통과; 이후 테스트 추가 시 개수보다 실패 0건을 기준으로 재확인 |
|
|
| Visual Studio 2026 | Debug/Release x64 빌드 성공 |
|
|
| trusted Release x64 MSIX | 생성·서명 검증·x64 설치·package context 실행 성공. 실제 DB DryRun에서 5001 timer refresh, 5074 playlist NEXT와 page 1→2 Page NEXT, TAKE OUT 정리를 확인; 나머지 PageN/마지막 경계는 자동 테스트로 검증 |
|
|
| 실제 데이터 전체 scene smoke | 34개 도달 가능 builder 통과: 33개 DB + `s5025` trusted 외부 CP949 파일. `s8086` diagnostic 포함 Oracle/MariaDB query 55건 통과 |
|
|
| `s5025` trusted 수동 파일 | 외부 CP949 source 통합 검증 통과; 실제 파일/환경은 계속 Git 밖에서 회차별 preflight |
|
|
| `s5006` `Video\큐브배경.vrv` | 현재 승인 Cuts root에서 누락 |
|
|
| 이번 마이그레이션 WebView workflow의 실제 Tornado2 검증 | **승인된 5001/5074 범위에서 Round H 실제 PGM 완료; 35개 전체의 실제 PGM 검증으로 확대 해석하지 않음** |
|
|
|
|
과거 고정 `5001 → 5006` runner의 실제 PGM 기록은 연결과 기본 K3D 명령 표면의 증거다. 현재 WebView playlist, fresh TAKE IN, Page NEXT, timer refresh, callback/unload의 동등성 완료 증거는 아니다.
|
|
|
|
2026-07-11의 승인 회차 `MBNWEB-20260711-A`에서는 현재 MSIX WebView runtime으로 `CONNECT → PREPARE 5001/page 1`까지 성공했다. Network Monitoring에 `HELLO`, `LOAD_SCENE`, `BEGIN_TRANSACTION`, 데이터 mutation, `QUERY_VARIABLES`, `END_TRANSACTION`, `SCENE_PREPARE`의 request/success가 기록됐고 PREPARE 동안 PGM은 검은 화면이었다. TAKE IN은 두 번째 K3D Prepare/Play 전에 수행하는 fresh Oracle 조회에서 `DATABASE_UNAVAILABLE`로 명확히 실패했다. `PLAY`가 없고 PGM도 검은 상태임을 확인해 TAKE IN을 반복하지 않았으며, 승인 범위의 `TAKE OUT All` 한 번으로 `STOPAL → UNLOAD_SCENE 5001` 성공을 확인하고 `BYE` 뒤 앱을 정상 종료했다. 직후 probe는 Oracle unhealthy/MariaDB healthy와 정상 DNS/TCP 도달을 보였고, 회차 종료 뒤 전체 55-query 스모크는 두 DB 모두 다시 통과했다. 이 회차는 실패·복구 증거이며 동등성 완료 증거가 아니다. 새 package/계획 SHA-256과 새 Gate A/B로 처음부터 다시 시작한다.
|
|
|
|
이 회차 뒤 health query와 시장 탭 DB 조회를 송출 fresh query와 하나의 DB activity gate로 직렬화했다. 송출 명령과 자동 on-air refresh가 시작되면 진행 중인 health/market 조회를 취소하고 gate를 먼저 확보하며, 30초 periodic health는 송출 중 건너뛴다. native database status에는 단조 증가 sequence를 붙이고 Web은 오래된 응답을 폐기한다. DB health의 `IsTransient`와 숫자 provider error code만 진단 도구에 보존하며 SQL, parameter, host, 계정, 암호, connection string과 provider message는 출력하지 않는다.
|
|
|
|
### Round G 실패 경계와 Round H 최종 실제 검증
|
|
|
|
Round G `MBNWEB-20260711-G`는 5001 TAKE IN과 화면 표시까지 성공했지만, 승인된 자동 refresh 1회 예산을 넘어 refresh가 연속 실행되는 경계에서 중단됐다. Network Monitoring에는 성공한 transaction/prepare 39회와 PLAY/`OnScenePlayed` 38회가 기록됐고 FAILURE/ERROR는 각각 0이었다. `refreshLastSuccessAt`은 전진했지만 `playCompletionPending`이 사실상 계속 유지되어 관찰 대기가 timeout됐으며, 5074 playlist NEXT와 Page NEXT는 보내지 않았다. 출력이 5001로 명확히 active이고 `OutcomeUnknown=false`였으므로 승인된 비상 TAKE OUT을 정확히 한 번 보내 STOPALL과 5001 UNLOAD, PGM black, disconnect를 확인했고 재시도는 0회였다.
|
|
|
|
Round G 증거 경계는 다음 SHA-256으로 고정한다.
|
|
|
|
| 증거 | SHA-256 |
|
|
|---|---|
|
|
| 계획 | `E8A6B05E28130665A73CC882A70CD85F023EBF6C59795EC3A56671BE06B06B9A` |
|
|
| 회차 결과 | `9B573EBB0F5465164298CA96099E1853DE6E6C00B3D375FB81FC954BED3464A0` |
|
|
| manifest | `A0AA8E95CB7F0BE52E21438094D1FFCDA53245C9E33CDC5A659D513EEF19D1EC` |
|
|
|
|
Round H `MBNWEB-20260711-H`는 이 실패 경계를 수정한 source commit `fa0eae7b61f57f134dfd30ebf1e59e9cc2cccfc5`(`fa0eae7`)와 다음 불변 입력으로 실행했다.
|
|
|
|
| 항목 | 값 |
|
|
|---|---|
|
|
| 계획 SHA-256 | `55C7755C2F874B4B012239117D4AB717BBCE194E7DCC8DA9B0E40D094FF8CA19` |
|
|
| 회차 결과 SHA-256 | `AB70E61C86909F16FE407AAEEF0A5E668F8A4614375448E860C7BE47D687FA0D` |
|
|
| 증적 manifest SHA-256 | `CD4EFD0CF64D9C905D7ABF732296E5A80DD4DE362DC7C88FD0D6967846955494` |
|
|
| 서명된 Release x64 MSIX SHA-256 | `D51E8CB637860D9F0377AEE6FF691D7D954BA0FF7E30B1AB65443DA153BC5429` |
|
|
| 명령 예산 | CONNECT 1, PREPARE 1, TAKE IN 1, 5001 자동 refresh 1, 5074 playlist NEXT 1, 5074 Page NEXT 1, TAKE OUT 1, disconnect 1, retry 0 |
|
|
|
|
실제 PGM에서는 5001 삼성전자 화면, 5074 page 1과 page 2를 차례로 확인했고 TAKE OUT 뒤 black을 확인했다. Network Monitoring의 최종 증분은 다음과 일치했다.
|
|
|
|
| 명령군 | request/success 또는 callback |
|
|
|---|---|
|
|
| HELLO | 1/1 |
|
|
| LOAD 5001 / LOAD 5074 | 각각 1/1 |
|
|
| PREPARE 5001 / PREPARE 5074 | 각각 3/3, 2/2 |
|
|
| PLAY / `OnScenePlayed` | 4/4, callback 4 |
|
|
| STOPALL | 1/1 |
|
|
| UNLOAD 5001 / UNLOAD 5074 | 각각 1/1 |
|
|
| FAILURE / ERROR | 0/0 |
|
|
| BYE | request 1, 별도 success marker 0 |
|
|
|
|
BYE success marker가 0인 것은 계획의 허용 범위 0~1 안이며, 앱의 disconnected terminal state와 callback drain, 최종 PGM black을 함께 확인했으므로 정상 종료로 판정했다. 전 과정은 `OutcomeUnknown=false`, retry 0이었고 실패 뒤 추정 명령이나 반대 명령을 보내는 rollback은 필요하지 않았다. 정상 cleanup으로 승인된 TAKE OUT 1회, 두 scene unload, disconnect, 앱과 진단 listener 종료, 회차 전용 Live 설정·승인 환경 제거, PGM/Network Monitoring 창 상태 복원을 확인했다. 이후 기본 모드는 다시 `DryRun`이며 장애 rollback은 `Disabled` 전환 또는 직전 승인 패키지 복귀만 사용하고 vendor 설치·등록·라이선스는 변경하지 않는다.
|
|
|
|
Round H 전에 Core 790, Infrastructure 65, Playout 374개 테스트를 Debug/Release x64에서 각각 모두 통과했고, 실제 Oracle/MariaDB read-only query 55건, Visual Studio 2026 Debug/Release x64와 package build, 서명·설치·package context 검사를 통과했다. 같은 signed MSIX의 package-context DryRun도 `5001 PREPARE → TAKE IN → 자동 refresh 정확히 1회 → 5074 page 1 → page 2 → TAKE OUT`과 최종 IDLE, `OutcomeUnknown=false`, KTAP 미호출을 확인했다.
|
|
|
|
이 실제 Live PGM 증거는 회차에 허용된 5001과 5074에 한정된다. 35개 scene builder 전체 동등성은 자동 테스트, 55-query 실데이터 smoke와 [`SCENE_EQUIVALENCE.md`](SCENE_EQUIVALENCE.md) 매트릭스로 유지하며, 나머지 scene이 실제 PGM에서 각각 송출됐다는 의미로 기록하지 않는다.
|
|
|
|
## 절대 안전 규칙
|
|
|
|
1. 앱의 기본값은 항상 `DryRun`으로 유지한다.
|
|
2. `DryRun`, `--probe`, `--dry-run`, `--test-plan`은 `KTAPConnect`를 호출하지 않는다. 이때 Tornado2 Network Monitoring에 기록이 없는 것이 정상이다.
|
|
3. 실제 출력은 승인된 테스트 scene, 지정된 PGM, 지정된 회차에서만 허용한다.
|
|
4. Connect/PREPARE를 시작하기 전에 회차 범위 승인이 있어야 한다. **현재 PGM에 TAKE IN을 보내기 직전에는 같은 회차의 별도 명시적 승인을 다시 받아야 한다.**
|
|
5. 승인에 없는 NEXT, Page NEXT, TAKE OUT 이외 명령 또는 다른 scene으로 범위를 넓히지 않는다.
|
|
6. timeout, `OutcomeUnknown`, `WEB_TIMEOUT`, refresh fault, 응답/화면 불일치, 대상 프로세스 변경, callback 누락 상태에서는 같은 명령을 자동 또는 수동으로 반복하지 않는다.
|
|
7. 실제 검증 회차에는 `reconnectEnabled=false`, `maximumReconnectAttempts=0`을 사용한다. 재접속은 새 회차와 새 승인으로만 수행한다.
|
|
8. 안전 게이트, x64 vendor hash pin, scene allowlist, process/window/port ownership 검사를 완화하지 않는다.
|
|
9. Web 입력으로 object 이름, 파일 경로, K3D method, 임의 SQL을 받지 않는다.
|
|
10. 실제 on-air일 수 있는 scene을 수동으로 unload하거나 COM RCW를 강제 release하지 않는다.
|
|
11. pending Play callback이 있으면 TAKE OUT 이외의 PREPARE/TAKE IN/NEXT/timer refresh를 실행하지 않는다. lifecycle callback이 남아 있으면 Disconnect하지 않는다.
|
|
|
|
## Git 밖에서 준비할 항목
|
|
|
|
다음 항목은 로컬 운영 설정 또는 승인된 외부 자산이다. Git, MSIX, 로그 첨부, 테스트 fixture에 복사하지 않는다.
|
|
|
|
- 원본 test Cuts root: `C:\Users\MD\source\repos\MBN_STOCK_N\MBN_STOCK_N\bin\Debug\Cuts`
|
|
- 실제 `.t2s`, image, texture, video와 기타 scene 자산
|
|
- `s5006`의 상대 자산 `Video\큐브배경.vrv`; 현재 위 Cuts root에는 없으므로 제공 전 실제 PREPARE 금지
|
|
- `s5025` trusted CP949 수동 파일 디렉터리와 환경 변수 `MBN_STOCK_S5025_MANUAL_DATA_DIRECTORY`
|
|
- Oracle/MariaDB host, SID/service, database, user, password와 운영 query selector
|
|
- K3D/Tornado vendor DLL, Interop, license와 설치 경로
|
|
- `MBN_STOCK_K3D_NATIVE_SHA256`, `MBN_STOCK_K3D_INTEROP_SHA256` 승인 값과 승인 근거
|
|
- MSIX 서명 인증서, 개인 키, 암호와 배포용 secrets
|
|
- 실제 Tornado host/port, output channel, PGM 창 정보와 운영 설정
|
|
- Network Monitoring/PGM screenshot·영상·manifest 등 실제 방송 증거
|
|
|
|
Cuts root는 이번 작업에서 읽기 전용으로 취급한다. [`Test-LegacyCutCoverage.ps1`](../scripts/Test-LegacyCutCoverage.ps1)은 45개 active alias의 cut 존재 여부만 검사하며 자산을 복사하거나 수정하지 않는다.
|
|
|
|
## 승인 게이트
|
|
|
|
### Gate A: 회차 범위 승인
|
|
|
|
Connect 또는 실제 PREPARE 전에 다음 내용을 운영 기록에 남긴다.
|
|
|
|
```text
|
|
[MBN_STOCK_WEBVIEW Tornado 검증 회차]
|
|
회차 ID:
|
|
예정 시각/최대 종료 시각:
|
|
대상 Tornado2/PGM 식별값:
|
|
승인된 test cut alias:
|
|
승인된 selector와 시작 page:
|
|
허용 동작과 횟수: CONNECT, PREPARE, TAKE IN, NEXT/Page NEXT, timer refresh 관찰, TAKE OUT, DISCONNECT
|
|
Network Monitoring/PGM 관찰 담당자:
|
|
비상 TAKE OUT 담당자:
|
|
```
|
|
|
|
대상, cut, selector, page, 횟수 중 하나라도 바뀌면 같은 회차 승인을 재사용하지 않는다.
|
|
|
|
### Gate B: TAKE IN 직전 승인
|
|
|
|
PREPARE 결과와 Network Monitoring 상태를 운영자가 확인한 후, TAKE IN 직전에 다음과 같이 명시적인 승인을 받아야 한다.
|
|
|
|
```text
|
|
회차 <ID>의 준비된 cut <alias>, page <N>을 현재 PGM에 TAKE IN 1회 실행하는 것을 승인한다.
|
|
```
|
|
|
|
이 문구가 없거나 회차 ID/cut/page가 다르면 TAKE IN을 실행하지 않는다. 이전 대화의 일반적 동의, 과거 회차 승인, DryRun 성공은 Gate B를 충족하지 않는다.
|
|
|
|
## 실제 연결 전 offline preflight
|
|
|
|
아래 단계는 Tornado에 명령을 보내지 않는다.
|
|
|
|
1. 원본 기준선과 cut alias를 확인한다.
|
|
|
|
```powershell
|
|
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\Test-LegacySceneBaseline.ps1
|
|
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\Test-LegacyCutCoverage.ps1 `
|
|
-CutRoot "C:\Users\MD\source\repos\MBN_STOCK_N\MBN_STOCK_N\bin\Debug\Cuts"
|
|
```
|
|
|
|
2. Debug/Release x64 테스트와 Web bridge 검사를 실행한다.
|
|
|
|
```powershell
|
|
dotnet test .\tests\MBN_STOCK_WEBVIEW.Core.Tests\MBN_STOCK_WEBVIEW.Core.Tests.csproj -c Debug -p:Platform=x64
|
|
dotnet test .\tests\MBN_STOCK_WEBVIEW.Core.Tests\MBN_STOCK_WEBVIEW.Core.Tests.csproj -c Release -p:Platform=x64
|
|
dotnet test .\tests\MBN_STOCK_WEBVIEW.Playout.Tests\MBN_STOCK_WEBVIEW.Playout.Tests.csproj -c Debug -p:Platform=x64
|
|
dotnet test .\tests\MBN_STOCK_WEBVIEW.Playout.Tests\MBN_STOCK_WEBVIEW.Playout.Tests.csproj -c Release -p:Platform=x64
|
|
dotnet test .\tests\MBN_STOCK_WEBVIEW.Infrastructure.Tests\MBN_STOCK_WEBVIEW.Infrastructure.Tests.csproj -c Debug -p:Platform=x64
|
|
dotnet test .\tests\MBN_STOCK_WEBVIEW.Infrastructure.Tests\MBN_STOCK_WEBVIEW.Infrastructure.Tests.csproj -c Release -p:Platform=x64
|
|
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\Test-WebPlayout.ps1
|
|
```
|
|
|
|
3. 실제 DB smoke는 read-only query만 사용하는 별도 단계로 실행한다. DB 오류가 있으면 Tornado 단계로 넘어가지 않는다.
|
|
|
|
```powershell
|
|
dotnet run --project .\tools\MBN_STOCK_WEBVIEW.DbSmoke\MBN_STOCK_WEBVIEW.DbSmoke.csproj -c Release --no-restore
|
|
```
|
|
|
|
4. 설치된 trusted Release x64 MSIX가 package context에서 실행되는지 확인한다. `bin` 또는 loose EXE를 실제 검증에 사용하지 않는다.
|
|
5. K3D Registry64, AMD64 PE, TypeLib/Interop metadata, 두 SHA-256 pin, license를 확인한다. DLL을 repo 또는 앱 폴더로 복사해 우회하지 않는다.
|
|
6. 정확히 하나의 승인 대상 Tornado2 프로세스, PGM 창, Network Server TCP port와 LISTEN 소유권을 확인한다. 매뉴얼 예시 port를 추정해 사용하지 않는다.
|
|
7. `s5025`를 선택했다면 trusted 외부 디렉터리와 파일 preflight가 성공해야 한다. `s5006`를 선택했다면 `Video\큐브배경.vrv`가 승인 asset root 안에 있어야 한다.
|
|
8. 실제 COM을 사용하는 Test/Live playlist의 모든 cut이 로컬 폐쇄형 allowlist 안에 있고 selector가 closed enum/lookup 규칙을 통과하는지 DryRun에서 확인한다. allowlist가 비어 있으면 실제 모드 초기화 자체를 거부해야 한다.
|
|
9. 로컬 playout 설정의 `legacySceneFadeDuration` 기본값 6과 `legacySceneBackgroundKind`를 확인한다. 공통 background를 쓸 때 `legacySceneBackgroundAssetPath`는 `sceneDirectory` 아래의 승인된 상대 경로여야 한다. `PlayoutSceneCompositionFactory`의 DryRun preflight가 파일 존재, 허용 확장자, root 탈출, 절대 경로와 reparse point를 모두 거부하는지 확인한다. Web에는 이 asset 경로를 보내지 않는다.
|
|
10. Web DryRun에서 45개 active alias, row별 `enabled`, PREPARE 뒤 snapshot freeze, current entry/builder/page size/current rows/last-page/preview와 refresh 상태를 확인한다. pending command와 `OutcomeUnknown`/timeout quarantine에서도 playlist 편집이 잠겨야 한다. preview에 image/texture/video 경로가 나타나면 실제 회차를 중단한다.
|
|
|
|
하나라도 실패하면 회차를 시작하지 않는다. 설정을 수정한 뒤 처음부터 새 preflight 결과를 만든다.
|
|
|
|
## 정상 실행 순서와 관찰점
|
|
|
|
### 1. Connect
|
|
|
|
- Gate A와 모든 preflight를 다시 확인한다.
|
|
- Connect는 한 번만 보낸다.
|
|
- `OnHello`와 Network Monitoring의 request/response를 함께 확인한다.
|
|
- Connect 결과가 accepted이지만 `OnHello` 또는 monitor 왕복이 확인되지 않으면 준비 완료로 간주하지 않는다.
|
|
|
|
### 2. PREPARE
|
|
|
|
PREPARE는 다음 native 순서를 수행해야 한다.
|
|
|
|
1. 현재 활성 playlist 항목과 page 0을 native loader가 조회한다.
|
|
2. scene load, trusted common background와 기본 fade 6 또는 승인된 로컬 fade/scene effect를 적용한다.
|
|
3. `BeginTransaction` → allowlisted mutation → `QueryVariables` → `EndTransaction`을 실행한다.
|
|
4. layout 10을 `Prepare`한다.
|
|
|
|
관찰자는 Network Monitoring의 load/prepare 관련 왕복이 한 번씩인지, 앱이 `Prepared`와 정확한 cue/page를 표시하는지 확인한다. PGM에 의도하지 않은 on-air 변화가 있으면 즉시 회차를 중단하고 장애 절차를 따른다.
|
|
|
|
### 3. TAKE IN
|
|
|
|
- Gate B를 받은 뒤 한 번만 실행한다.
|
|
- PREPARE 때의 DTO를 재사용하지 않고 frozen entry/page를 실제 DB에서 새로 조회하는지 확인한다.
|
|
- fresh scene의 `LoadScene` → transaction → `Prepare(10)`이 명확히 성공한 뒤 `Play(10)`이 한 번 실행되는지와 `OnScenePlayed`를 확인한다.
|
|
- PGM에서 원본과 비교할 object 이름별 값, 표시 상태, 색, 위치/크기, crop, path, image/texture/video, fade를 기록한다.
|
|
- Network Monitoring의 두 번째 load/prepare와 PLAY request/response, PGM 화면 시각을 같은 증거 묶음에 보존한다.
|
|
|
|
### 4. NEXT
|
|
|
|
앱이 반환한 `nextKind`가 기준이다. Web에서 임의로 page/playlist 유형을 정하지 않는다.
|
|
|
|
- `PageNext`: playlist index를 유지하고 원본 `Next_Scene(0)`처럼 다음 page의 fresh 데이터를 조회한 뒤 새 scene을 `LoadScene` → transaction mutation → `QueryVariables` → `EndTransaction` → `Prepare(10)` → `Play(10)`한다. 이 경로에서 `GetPlayingScene` in-place 갱신을 기대하지 않는다.
|
|
- `PlaylistNext`: 현재 page가 마지막일 때 다음 활성 playlist 항목을 load/prepare/play한다. 비활성 항목은 앞으로 건너뛰고 끝에서 wrap하지 않는다.
|
|
- 각 NEXT는 Gate A에 승인된 유형과 횟수 안에서만 한 번씩 실행한다.
|
|
- `pageIndex`, `pageCount`, `itemCount`, 마지막 부분 page의 빈 object clear/hide와 PGM 화면을 함께 확인한다.
|
|
- 모든 operator command는 timer를 먼저 멈춘다. TAKE IN/playlist NEXT 뒤에는 원본 `m_time`으로 첫 timer가 시작되지만 Page NEXT 성공 뒤에는 timer가 정지 상태로 남아야 한다.
|
|
|
|
### 5. Timer refresh
|
|
|
|
- 승인 범위에 포함된 경우에만 자동 refresh를 관찰한다. 현재 안전 스케줄러는 직전 `OnScenePlayed` callback이 lifecycle queue에서 drain된 뒤에만 one-shot cooldown을 시작한다. 5001의 첫 cooldown은 온전한 2초이고, 성공한 refresh 뒤에는 callback drain 후 온전한 3초 recurring cooldown이다. 따라서 실제 dispatch 간격은 callback/drain 시간과 cooldown의 합이며, 원본의 절대 3초 cadence보다 의도적으로 길다.
|
|
- 이 callback-aware cooldown은 recurring refresh를 제거한 것이 아니다. missed tick을 누적하거나 따라잡지 않고 합치며, refresh는 최대 하나만 in-flight다. 각 실행은 current entry/page의 fresh DB DTO를 사용해 retained on-air scene에 `BeginTransaction` → mutation → `QueryVariables` → `EndTransaction` → `Prepare(10)` → `Play(10)`을 한 번 수행한다.
|
|
- timer refresh는 scene-level background와 fade effect를 다시 적용하지 않는다.
|
|
- 앱의 `refreshActive`, `refreshNextAt`, `refreshLastSuccessAt`, fault code/message와 PGM 데이터를 함께 기록한다.
|
|
- refresh가 실패하거나 timeout/unknown이면 fault latch가 켜지고 반복이 즉시 끝나야 한다. TAKE OUT 이외의 명령을 시도하지 않는다.
|
|
- 명확히 성공한 TAKE OUT 뒤 native refresh state가 reset되면 전용 refresh fault marker만 사라져야 한다. `OutcomeUnknown`, `WEB_TIMEOUT` 또는 native quarantine 표시는 함께 reset되면 안 된다.
|
|
- operator NEXT는 먼저 현재 refresh epoch를 원자적으로 stop한다. 그 시점에 Play callback이 pending이면 DB activity 대기나 scene query 전에 `PLAY_CALLBACK_PENDING`으로 즉시 fail-fast하고 자동 재시도하지 않는다. 명령 gate가 바로 풀리므로 TAKE OUT은 계속 사용할 수 있다. callback이 완료된 cooldown 구간에서는 NEXT를 정상 실행할 수 있다.
|
|
- refresh epoch의 cancellation과 status는 같은 lock에서 교체·중지·갱신·완료한다. Stop 뒤 늦은 `onDelay`, 이전 세대의 success/fault/finally가 현재 세대나 stopped 상태를 다시 활성화할 수 없다.
|
|
|
|
### 6. TAKE OUT과 Disconnect
|
|
|
|
- 승인된 TAKE OUT 한 번으로 `TakeOut(All)`/`StopAll`을 실행한다.
|
|
- `OnCutOut` 또는 `OnStopAll`, 앱 state clear와 PGM의 검은 화면 또는 승인된 종료 상태를 확인한다.
|
|
- pending callback과 retired scene 정리가 끝난 뒤에만 Disconnect한다.
|
|
- Network Monitoring에서 stop 계열 왕복과 BYE/disconnect를 확인한다.
|
|
|
|
## Network Monitoring과 PGM 증거 체크리스트
|
|
|
|
vendor monitor의 실제 명령 표기는 버전에 따라 다를 수 있으므로 추정 문자열로 성공을 만들지 않는다. raw 화면과 시각을 보존하고 다음 의미 단위로 대조한다.
|
|
|
|
| 단계 | Network Monitoring | PGM/앱 |
|
|
|---|---|---|
|
|
| Connect | HELLO 또는 대응 connect request/response 한 쌍 | 앱 Connected, 대상 process generation 일치 |
|
|
| PREPARE | scene load, mutation transaction, prepare의 중복 없는 왕복 | Prepared cue/page/row 수 일치, 의도하지 않은 on-air 없음 |
|
|
| TAKE IN | fresh DB 결과의 두 번째 load/prepare 뒤 PLAY request/response | `OnScenePlayed`, 실제 object 값과 시각 상태 일치 |
|
|
| Page NEXT | 같은 alias의 새 scene load/transaction/prepare/play 왕복 | playlist index 유지, page만 +1, 빈 slot clear/hide, refresh timer 정지 |
|
|
| Playlist NEXT | 다음 cut load/prepare/play 왕복 | 다음 활성 항목과 page 0 표시 |
|
|
| Timer refresh | retained on-air scene의 update transaction, prepare, PLAY 1회의 중복 없는 왕복 | callback drain 뒤 첫 full `m_time`, 이후 callback drain 뒤 full 3초 cooldown, 같은 entry/page의 fresh 값, refresh 상태 정상 |
|
|
| TAKE OUT | STOP/STOPALL 대응 왕복 | `OnCutOut`/`OnStopAll`, 승인된 종료 화면 |
|
|
| Disconnect | BYE 또는 대응 disconnect 왕복 | callback pending 0, 연결 해제 |
|
|
|
|
회차 증거에는 다음을 포함한다.
|
|
|
|
- 회차 ID, package version, Git commit, 실행 시각과 timezone
|
|
- cut alias, closed selector, page index/count, DB 조회 식별값; password와 connection string 제외
|
|
- 각 앱 명령의 결과 code와 `OutcomeUnknown` 여부
|
|
- 같은 시각의 Network Monitoring과 PGM 화면
|
|
- PREPARE/TAKE IN/NEXT/timer refresh/TAKE OUT 전후 screenshot 또는 연속 capture
|
|
- callback 순서, retired/unloaded scene 수, disconnect 결과
|
|
- Web의 frozen entry/builder/page size/current rows/last-page/preview와 refresh status; asset path는 제외
|
|
- 운영자 최종 판정과 불일치 목록
|
|
|
|
증거는 Git 제외 로컬 경로 또는 승인된 증거 저장소에 보관한다. 캡처에 host, port, 계정, license 정보가 보이면 외부 전달 전에 별도 보안 절차로 처리하며 원본 증거를 repo에 넣지 않는다.
|
|
|
|
## 장애 판정과 반복 금지
|
|
|
|
| 상황 | 즉시 조치 | 재시도 조건 |
|
|
|---|---|---|
|
|
| DB/selector/asset preflight 실패, COM 호출 전 명시적 `Rejected` | 회차 중단, DryRun으로 복귀, 원인 기록 | offline 수정과 전체 preflight 후 새 회차 승인 |
|
|
| Connect timeout 또는 `OnHello`/monitor 불일치 | 결과를 불명확으로 격리, Connect 반복 금지 | 운영자가 세션과 process/port 상태를 확인한 뒤 새 회차 승인 |
|
|
| PREPARE/TAKE IN/NEXT/refresh/TAKE OUT timeout | `OutcomeUnknown`으로 취급, 같은 명령과 반대 명령 자동 실행 금지 | PGM/monitor/콜백을 사람이 확인하고 출력 안전을 복구한 뒤 새 회차 승인 |
|
|
| `WEB_TIMEOUT` | strict timeout-quarantine message를 native에 전달. MainWindow가 process-lifetime latch를 먼저 세우고 vendor session을 quarantine; 늦은 응답, UI 재시도와 WebView reload로 해제 금지 | native status와 PGM을 사람이 대조하고 process를 새로 시작한 뒤 새 승인 |
|
|
| refresh DB/scene/COM fault | refresh loop 중단, fault latch 유지, TAKE OUT 외 명령 금지 | 정상 TAKE OUT과 실제 화면 확인 후 offline 원인 수정, 새 회차 승인 |
|
|
| 명령 성공 응답과 PGM 화면 불일치 | 앱 state를 신뢰하지 말고 회차 중단 | PGM 운영자가 실제 상태를 판정하고 새 회차 승인 |
|
|
| callback 누락/지연 | scene을 unload하거나 disconnect 강제하지 않음 | callback 도착 또는 운영자의 수동 안전 판정 후 새 회차 |
|
|
| Tornado process generation, 창, LISTEN 소유권 변경 | 모든 자동 동작 중지, 대상 격리 | 새 process를 처음부터 검증하고 새 승인 |
|
|
| 연결 장애/Disconnect timeout | 자동 reconnect 금지, 연결·출력 상태를 불명확으로 기록 | 운영자가 Tornado 세션을 확인한 뒤 새 회차 |
|
|
|
|
`OutcomeUnknown`은 단순 실패 code가 아니라 실제 출력 결과를 모른다는 latch다. Cancelled, Rejected, `WEB_TIMEOUT` 또는 UI 오류로 낮추지 않는다. timeout quarantine은 native process-lifetime latch이므로 WebView reload나 오류 창으로 해제되지 않는다. refresh fault marker는 성공한 TAKE OUT 뒤 native reset에만 맞춰 제거할 수 있지만 unknown/quarantine latch에는 영향을 주지 않는다. process를 재시작해 표시를 지우는 행위도 실제 출력 복구가 아니다.
|
|
|
|
## Rollback과 unload
|
|
|
|
### 명확히 출력 전 실패한 경우
|
|
|
|
COM 명령 전 validation에서 명시적으로 거부됐고 Network Monitoring/PGM에 변화가 없음을 확인한 경우에만 offline 수정으로 돌아간다. 수정 후에는 기존 회차를 이어가지 않고 새 회차로 시작한다.
|
|
|
|
### 출력이 명확히 active이고 엔진이 정상인 경우
|
|
|
|
Gate A에 포함된 TAKE OUT을 한 번 실행한다. 성공 callback과 PGM 종료 화면을 확인한 뒤 Disconnect한다. 같은 TAKE OUT을 확인용으로 반복하지 않는다.
|
|
|
|
### 결과가 불명확한 경우
|
|
|
|
1. 앱에서 추가 PREPARE, TAKE IN, NEXT, timer refresh, TAKE OUT, Disconnect를 자동으로 보내지 않는다.
|
|
2. 현재 앱 status, 마지막 명령, Network Monitoring, PGM을 캡처한다.
|
|
3. 방송 운영자가 Tornado 본 프로그램/PGM에서 실제 출력과 세션을 판정한다.
|
|
4. 필요한 수동 정리는 방송 운영 권한과 현장 절차로 수행한다. Codex나 앱이 임의 명령을 추정하지 않는다.
|
|
5. 안전한 black/approved fallback과 세션 종료가 사람에게 확인된 뒤 앱을 종료한다.
|
|
6. 장애 원인과 조치가 확정될 때까지 새 검증을 승인하지 않는다.
|
|
|
|
### Scene 수명 규칙
|
|
|
|
- `OnScenePlayed`가 새 scene의 재생을 확정하면 이전 scene만 retired queue로 이동한다.
|
|
- `OnCutOut`은 해당 layout, `OnStopAll`은 전체 player의 on-air 참조를 정리할 근거다.
|
|
- pending Play callback이 있으면 TAKE OUT 이외의 명령을 fail-closed 차단한다. pending `OnScenePlayed`/`OnCutOut`/`OnStopAll`이 있으면 Disconnect와 unload를 시도하지 않는다.
|
|
- `CutOut`/`StopAll` pending counter는 SDK dispatch 직전에 증가하고 동기 호출 실패 시 감소한다. 성공 callback은 대응 counter를 하나 감소시키고 중단된 Play의 pending accounting을 취소한다. dispatch 뒤 cancellation/timeout이면 counter 상태를 성공으로 추정하지 않는다.
|
|
- 이전 connection generation에서 늦게 온 callback은 현재 generation의 state나 scene을 정리하는 근거로 쓰지 않는다.
|
|
- 장기 실행 중 retired scene은 callback으로 안전성이 확인된 뒤 STA queue 안에서 `Unload`와 release를 수행한다.
|
|
- 강제 GC, 임의 RCW release, 현재 on-air scene unload를 rollback으로 사용하지 않는다.
|
|
|
|
## 완료 판정
|
|
|
|
실제 검증 회차는 다음을 모두 만족할 때만 성공이다.
|
|
|
|
1. Gate A와 Gate B가 같은 회차에 기록돼 있다.
|
|
2. 허용된 test cut과 selector만 사용했다.
|
|
3. PREPARE → fresh DB TAKE IN → 승인된 callback-aware timer refresh → Page NEXT/playlist NEXT → TAKE OUT 순서가 중복 없이 완료됐다. refresh는 callback drain 뒤 full initial/recurring cooldown과 회차 예산을 지켰다.
|
|
4. 모든 결과가 명확한 성공이며 `OutcomeUnknown=false`다.
|
|
5. Network Monitoring request/response와 PGM 화면이 같은 시각 증거로 남아 있다.
|
|
6. 원본과 object 값, 표시, 색, 위치/크기, crop, path, asset, fade, page와 refresh 상태가 일치한다. Web preview에는 실제 asset path가 없다.
|
|
7. callback과 unload 순서가 정상이고 Disconnect가 성공했다.
|
|
8. DB password, vendor DLL/license, 인증서/private key, 실제 cut/asset, 운영 설정이 Git 변경에 포함되지 않았다.
|
|
|
|
검증 후 결과를 [`SCENE_EQUIVALENCE.md`](SCENE_EQUIVALENCE.md)의 해당 행에 기록한다. 일부 scene 성공이나 과거 runner 성공으로 나머지 행을 일괄 완료 처리하지 않는다.
|