From 3584cacc6e41fa460f180d14dfcb0d3a976bb282 Mon Sep 17 00:00:00 2001 From: NohDoyeon241104 Date: Fri, 4 Sep 2026 14:19:16 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20=EB=A0=88=EC=8B=9C=ED=94=BC=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=EA=B5=90=EC=B0=A8=EC=98=A4=EC=97=BC=20=ED=8A=B8?= =?UTF-8?q?=EB=A6=AC=EA=B1=B0=20=EC=B0=A8=EB=8B=A8=20=EC=9D=B8=EC=88=98?= =?UTF-8?q?=EC=9D=B8=EA=B3=84=20=EB=AC=B8=EC=84=9C=20(ISSUE-260904-02)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../260904_레시피저장_교차오염_트리거차단.md | 245 ++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 Doc/인수인계/260904_레시피저장_교차오염_트리거차단.md diff --git a/Doc/인수인계/260904_레시피저장_교차오염_트리거차단.md b/Doc/인수인계/260904_레시피저장_교차오염_트리거차단.md new file mode 100644 index 0000000..59b1ca8 --- /dev/null +++ b/Doc/인수인계/260904_레시피저장_교차오염_트리거차단.md @@ -0,0 +1,245 @@ +# 레시피 저장 교차오염 트리거 차단 (2026-09-04) + +> **목적** : 액티브 전환 시 엉뚱한 레시피 파일에 다른 레시피 내용이 덮어써지던 건의 +> 조사 결과와 적용한 차단 조치를 남긴다. +> +> - 대상 설비 : PtoP 각형화 단동 용접기 (유니셀) +> - 이슈번호 : `ISSUE-260904-02` (원인 이슈 `ISSUE-260826-04` 의 조치) +> - 커밋 : `bd6a7cd` +> - 선행 분석 : `docs/분석/2026/08/26/레시피_Drawing_덮어쓰기_원인분석_260826.md`, +> `docs/분석/2026/08/27/레시피_파일IO_동작별_순서맵_260827.md` + +**표기 규칙** (기존 인수인계 문서와 동일) + +| 표기 | 의미 | +|---|---| +| 🟩 **소스확인** | PC 소스/설정 파일에서 직접 대조해 확인한 사실 | +| 🟦 **현장기록** | 현장 로그·시험 기록에 남아 있는 값 | +| 🟨 **미확보** | 아직 확인되지 않음. 이어받는 사람이 채워야 할 항목 | + +--- + +## 1. 한 줄 요약 + +**저장 "대상"과 저장 "내용"이 묶여 있지 않다.** LASA 는 레시피를 메모리에 1개만 유지하므로 +활성 레시피가 바뀌는 순간 메모리 내용이 통째로 바뀌는데, 저장 함수는 목적지를 **이름 문자열로만** +만들고 내용은 **그 시점 메모리**를 쓴다. 둘이 같은 레시피인지 검증하는 코드가 없다. + +이번 조치는 **트리거 차단**이다. 구조적 원인은 그대로 남아 있다. + +--- + +## 2. 현상 + +- 오창 선행라인 CCW 설비에서 발생. PTOP 에서도 동일 계열 이슈 있었음. +- 레시피 10번을 액티브로 쓰던 중 갑자기 2번으로 액티브가 바뀌고, + **원래 쓰던 10번 파일 안에 2번 내용이 복사되어 덮어써짐.** +- 간헐적. Auto↔Manual 전환 시 특히 잦음. + +### 2.1 방향이 범인을 특정한다 🟩 + +"**10번 파일에 2번 내용**" — 목적지가 10(선택), 내용이 2(활성)다. + +| 저장 기준 | 만들어지는 결과 | +|---|---| +| **활성** 기준 저장 | 2번 파일에 10번 내용 ← 보고와 **반대** | +| **선택** 기준 저장 | **10번 파일에 2번 내용** ← 보고와 일치 | + +→ 선택(`SelectedRecipe`) 기준으로 저장하는 경로가 범인이다. + +--- + +## 3. ★ 사수 지시와 PTOP 소스가 다르다 🟩 + +CCW 기준으로 받은 지시 두 가지는 **PTOP 소스에 그 형태로 존재하지 않는다.** +그대로 주석 처리하면 아무것도 바뀌지 않는다. 이어받는 사람이 반드시 알아야 할 부분이다. + +| 지시 | PTOP 실제 | +|---|---| +| ① `OnSaveRecipeForEditing()` 호출부 전부 주석 처리 | **호출부 0건.** `RecipeHelper.cs:71` 에 정의만 있는 **사문**. AZ 소스처럼 이미 제거된 상태 | +| ② AutoRun 시작 시 "선택 레시피 선저장" 로직 차단 | **선저장이 없다.** `RunSequenceAutoRun:344` → `OnUpdateCurrentActivatedRecipe` → `OnActiveReloaded()` + `UpdateAutorunRecipe()` 이며 **둘 다 읽기 전용** | + +Auto↔Manual 전환 경로에도 저장이 없다. + +| 경로 | 코드 | 동작 | +|---|---|---| +| Auto 진입 | `EquipmentSetup.cs:286` `OnActiveReloaded()` | 읽기 | +| Manual 진입 | `EquipmentSetup.cs:289` `//RollbackRecipe()` | 이미 주석 | +| AutoRun 정지 | `UCMainAutoRunViewModel.cs:462` `RollbackRecipe()` | 읽기 | + +> 사수님의 **구조 진단(선택 기준 저장이 범인)은 정확하다.** 다만 PTOP 에서 그 역할을 하는 함수 이름이 +> 다를 뿐이다. + +--- + +## 4. 저장이 실제로 일어나는 경로 5개 (전수) 🟩 + +| # | 위치 | 저장 목적지 | 트리거 | 이번 조치 | +|---|---|---|---|---| +| **S1** | `UCScannerObjectTeachingViewModel.cs` `OnActivatedRecipe` | **활성** | `SYS_RECIPE_ACTIVATED` 마다 **무조건** | ✅ 조건부화 | +| **S2** | 같은 파일 `OnSaved` | **선택** | Aurora 저장 버튼 | ✅ 가드 | +| S3 | 같은 파일 `OnRecipeEntryChanged` | 신규 레시피 | 레시피 New | ❌ 미경유 (의도적) | +| S4 | `RMSHandlerForPTOP.cs:146` | recipe.Name | RMS 레시피 체인지 | ❌ 미경유 | +| S5 | `RecipeHelper.cs:77` `OnSaveRecipeForEditing` | 선택 | **호출 없음** | 사문 | + +**S1 이 사용자 조작 없이 발화한다.** 260825 로그 실측 🟦 — 저장 조작이 0건인 16분 구간에 +`from [OnActivatedRecipe]` 로그가 8줄, 즉 **자동 저장 8회**. + +**S4(RMS 읽기)만 순서가 올바르다** — `Reload → 수정 → Save`(`RMSHandlerForPTOP.cs:132~147`). +수정 시 참조 모델로 쓸 것. + +--- + +## 5. 저장 주체는 Aurora 가 아니다 🟩 + +"SaveRecipe" 라는 이름이 3개라 헷갈린다. + +| 계층 | 위치 | 소스 | 우리가 고칠 수 있나 | +|---|---|---|---| +| ① Aurora 내부 저장 | `Aurora.exe` | ❌ 없음 | ❌ | +| ② `RecipeHelper.SaveRecipe(name, includeModel)` | `WeldingCommon/Utils/RecipeHelper.cs:38` | ✅ | ✅ | +| ③ `MainPage.SaveRecipe(targetPath, includeModel)` | `LASA/GUI/Page/MainPage.cs` | ✅ | ✅ | + +②가 ③을 호출한다(`RecipeHelper.cs:57`). **①은 우리 TeachingEditor/FieldHelper 파일 저장에 관여하지 않는다.** + +### 5.1 Aurora 는 콜백만 준다 — 그런데 정답을 이미 넘겨주고 있다 + +`IRecipeViewModel` (`Aurora.Api`, `Aurora.xml:3989~4030`) — *"커스텀 레시피 뷰어를 구현할 때 상속받는 인터페이스"*. +`UCScannerObjectTeachingViewModel : NotifyPropertyChangedBase, IRecipeViewModel`. + +| 멤버 | 시점 | +|---|---| +| `PreSaved(int id, string recipeName)` | Save 되기 **전** 호출. **`false` 반환 시 저장 중단** 🟦 | +| `OnSaved(int id, string recipeName)` | Save 된 **후** 호출 | +| `OnChanged` / `Reloaded` / `OnModelSelectChanged` | 뷰어 레시피 변경·리로드·모델 변경 | + +저장소 어디에도 `PreSaved`/`OnSaved` 호출부가 없다. Aurora 가 부른다. + +**★ `OnSaved` 가 Aurora 인자를 버린다.** + +```csharp +public void OnSaved(int id, string recipeName) // ← Aurora 가 알려준 "진짜 저장 대상" +{ + ... + RecipeHelper.SaveRecipe(RecipeHelper.SelectedRecipe.name); // ← 인자 무시, 전역값 사용 +``` + +`RecipeTrace.cs:104` 주석에도 *"OnSaved 가 인자를 버리는 경로"* 라고 이미 적혀 있었다. +**어긋남을 판정할 기준값이 이미 손에 들어와 있다.** + +--- + +## 6. 이벤트 체인 — 어긋남을 처리하는 곳은 없다 🟩 + +**`SYS_RECIPE_ACTIVATED`** (Aurora 발행) 구독 5곳 + +| 구독 위치 | 하는 일 | 저장 | +|---|---|---| +| `UCScannerObjectTeachingViewModel.cs:64` | ActivateModel + **SaveRecipe(활성)** + 이벤트 재발행 | ✅ S1 | +| `UCScannerObjectTeachingControlBoxViewModel.cs:224` | LaserInterlock 로드 (Background 지연) | ❌ | +| `CommonEquipmentSetup.cs:114` | Merge → **로그 전용** | ❌ | +| `VisionCommon/VisionEventId.cs:47,62,74,84` | 비전 Merge | ❌ | +| `AuroraUIComponent/UCRecipeSelectViewModel.cs:74` | SelectRecipe — **PTOP 생성 0건, 사문** | ❌ | + +**`EVENT_CHANGE_RECIPE_INFO`** (우리 발행 — `OnActivatedRecipe`, `OnSaved`) 구독 2곳 + +| 구독 위치 | 하는 일 | 저장 | +|---|---|---| +| `UCMainAutoRunViewModel.cs:142` | `OnActiveReloaded()` + `UpdateAutorunRecipe()` | ❌ | +| `RMSHandlerForPTOP.cs:56` | `ReloadRecipe()` + PLC 파라미터 업로드 | ❌ | + +**결론: 발행·구독 어느 지점에도 신원 검증이 없다.** 전부 넘어온 값을 그대로 따라간다. + +--- + +## 7. 적용한 조치 — 파일 1개, 블록 3개 + +`WeldingComponents/WeldingCommon/Views/Recipes/UCScannerObjectTeachingViewModel.cs` +마커 `[레시피 덮어쓰기 이슈]`. + +### ① `OnActivatedRecipe` — 조건부 저장 + 스킵 로그 + +```csharp +if (string.Equals(activeRecipe.Name, selectedNameOnActivate, StringComparison.OrdinalIgnoreCase)) { + RecipeHelper.SaveRecipe(activeRecipe.Name); +} else { + Log.W($"[레시피 덮어쓰기 차단] OnActivatedRecipe 자동저장 스킵. ..."); +} +``` + +> **왜 완전 삭제가 아닌가** — `OnRecipeEntryChanged` 의 **Copy/Rename 분기가 비어 있다**(`:118-121`). +> 이 저장이 새 레시피 폴더의 TeachingEditor 파일 생성을 겸했을 가능성이 있고, 파일이 없으면 +> `ReloadRecipe` 의 `throw;`(`RecipeHelper.cs:116`)가 Rx `Subscribe` 안에서 터져 +> **이후 `SYS_RECIPE_ACTIVATED` 를 영구히 못 받는다.** 레시피 전환이 통째로 죽는 모드다. + +### ② `PreSaved` — 활성≠선택이면 저장 차단 + 모달 + +`return false` 로 저장을 막고, 사유와 조치 방법을 담은 확인 모달을 띄운다. + +### ③ `OnSaved` — 동일 검사 2중 방어 + 모달 + +`PreSaved` 를 뚫고 온 경우를 대비. **조용히 스킵하면 사용자가 "저장됐다"고 오인**하므로 반드시 알린다. + +**`RecipeHelper`·`MessageUtil` 무수정, 신규 파일 없음, 컴파일 영향 없음.** + +--- + +## 8. 현장 검증 (2026-09-04) + +| 항목 | 결과 | +|---|---| +| 활성≠선택 상태에서 저장 → 모달 + 저장 차단 | 🟦 **정상 동작 확인** | +| 모달에 선택/활성 값 표시 | 🟦 정상 (`FindStrToken` 통과 확인) | +| **`PreSaved`=false 시 Aurora 가 실제로 저장 중단** | 🟦 **확증** — 소스 부재로 추정만 하던 사항 | +| 평소 저장(활성=선택)이 막히지 않는지 | 🟨 미확인 | +| **레시피 복사·이름변경 → 활성화** | 🟨 **미확인 (최우선)** | +| 자동 사이클 페이지 전환 | 🟨 미확인 | + +> `PreSaved` 가 `false` 를 반환하면 Aurora 가 **`Save Failed` 모달을 추가로 띄운다** +> (`AuroraUIComponent.ShowSaveFailedDialog`). 모달이 2개 뜨지만, 우리 것은 *사유와 조치*를 +> Aurora 것은 *실패 사실*을 알리므로 역할이 겹치지 않아 현행 유지하기로 했다. + +--- + +## 9. 남아 있는 위험 🟨 + +1. **오탐** — `IRecipeViewModel.OnChanged`(`:163`)는 `SelectedRecipe` 만 바꾸고 활성은 건드리지 않는다. + Aurora 가 **활성화 없이 뷰어만 전환**하는 흐름을 가졌다면, 정상 상황인데도 저장이 막힌다. + 막히면 판정 조건을 **로드 파일 신원**(`DB.Recipe.Recipe.SelectedRecipeFile`) 기준으로 바꾸면 된다. + 그 비교 코드는 `RecipeHelper.cs:48-55` 에 `[관측]` 로그로 **이미 들어가 있다**(차단만 안 할 뿐). +2. **Copy/Rename 분기 공백** — 위 §7 ① 참조. 배포 상태에서 반드시 1회 시험할 것. +3. **근본 원인 미해결** — `SaveRecipe` 가 목적지와 내용을 묶어 검증하지 않는다. + 가드를 안 거치는 S3(신규생성)·S4(RMS)에는 같은 구멍이 남는다. + 근본 대응은 `SaveRecipe` 에 불변식을 넣는 것인데, S3 가 **의도적으로** 신원을 위반하므로 + (`includeModel:false` 로 현재 메모리를 새 파일에 씀) `allowCrossIdentity` 같은 의도 인자 분리가 필요하다. +4. **진단계측이 미연결** — `WeldingCommon/Utils/RecipeTrace.cs` 의 `BeforeSave`/`AfterSave` 가 + 저장 전후 **파일 MD5** 로 교차오염을 판정하도록 완성돼 있으나 **호출이 0건**이다. 2줄이면 살아난다. + +--- + +## 10. 로그 grep 키 + +| 문자열 | 의미 | +|---|---| +| `[레시피 덮어쓰기 차단] OnActivatedRecipe 자동저장 스킵` | 정상. 활성 전환마다 나온다 | +| `[레시피 덮어쓰기 차단] PreSaved 저장 차단` | 평소 저장에서 나오면 **오탐** | +| `[레시피 덮어쓰기 차단] OnSaved 저장 스킵` | Aurora 가 PreSaved 를 무시함. 이상 상황 | +| `[관측] 메모리/대상 불일치` | 기존 계측. **가드 미경유 경로**(S3·S4)의 어긋남 | +| `*** RELOAD 실패 ***` | §9-2 위험이 실재. 이후 레시피 전환 불가 가능 | + +--- + +## 11. 운영 지침 + +- **레시피는 항상 "액티브된 레시피 = 선택된 레시피" 상태로 두고 사용한다.** (사수 지시) +- **AutoRun 중 Recipe 화면의 레시피 목록을 클릭하지 말 것.** 조회가 아니라 활성 변경이며 저장을 유발한다. +- drawing 객체는 변경 이력에 남지 않는다(`RchHandler` 는 ScannerParam 6필드만 기록). + **덮어써지면 복구 경로가 없다** — OS 이전 버전(VSS) 또는 수동 백업뿐이다. + +--- + +## 변경 이력 + +| 일자 | 내용 | 작성 | +|---|---|---| +| 2026-09-04 | 최초 작성. 조사 결과 + 3블록 차단 조치 적용(`bd6a7cd`), 현장 차단 동작 검증 | 노도연 |