---
template: flow-doc@1
doc_id: 05-treat-monitoring
title: 진료·분석 실패 감시와 재처리
config: ../_meta/template-config.yaml
last_verified: fb001e1d
flows:
  - id: treat-monitor-failure-list
    actor: 관리자
    status: conditional-unknown
    status_source: ../05-schedules/03-monitoring.md#monitoring:analysis-status
    unknown_reason: 실패 목록을 채우는 상태 전이가 담당 배치에 걸려 있고 그 배치의 활성이 판정되지 않았다. 목록 화면과 조회 API 는 확정이며 미상인 것은 그 목록이 최신 상태로 유지되는가다
    evidence: [S-route, S-roles, A-scheduler]
    limitations:
      - 목록에 실패가 어떤 시점에 나타나는지는 배치 주기에 달려 있고 그 주기의 활성은 05-schedules/04-runtime-topology.md 가 미판정으로 둔다
      - 벤더별 실패 분해는 조회 엔드포인트가 낸다. 그 분류의 정확성은 이 문서가 판정하지 않았다
    refs:
      - ../05-schedules/03-monitoring.md#monitoring:analysis-status
      - ./00-ia.md#admin-subscription-treat-monitor
      - ./00-ia.md#admin-subscription-analysis-log
    anchors:
      - insuniverse-server/apps/api/src/modules/treat/monitor/treat-monitor.controller.ts:81
      - insuniverse-server/apps/api/src/schedules/analysis-status.schedule.ts:25
      - insuniverse-server/apps/api/src/modules/menu/menu.repository.ts:32
      - insuniverse-server/apps/api/src/modules/auth/admin/roles.guard.ts:22
      - insuniverse-server/apps/api/src/modules/auth/admin/jwt.guard.ts:42
      - insuniverse-server/apps/api/src/modules/auth/admin/jwt.guard.ts:33
      - insuniverse-server/apps/api/src/modules/auth/admin/jwt.guard.ts:26
      - insuniverse-server/apps/api/src/modules/auth/admin/corp-readonly.guard.ts:24
      - insuniverse-server/apps/api/src/modules/auth/admin/corp-analysis-view.guard.ts:34
  - id: treat-monitor-reprocess
    actor: 관리자
    status: conditional-unknown
    status_source: ../05-schedules/03-monitoring.md#monitoring:analysis-status
    unknown_reason: 실패 건의 회복 경로가 관리자 조작이 아니라 배치이고 그 배치의 활성이 판정되지 않았다. 관리자 화면에서 재처리를 시작하는 엔드포인트를 이 창에서 찾지 못했다
    evidence: [S-route, S-roles, A-scheduler]
    limitations:
      - 재처리 엔드포인트 부재는 관리자 컨트롤러 전수 정독이 아니라 treat·analyze 두 모듈의 데코레이터 조회 결과다. 그 밖의 모듈에 있는 경로는 이 검색이 보지 못한다
      - 재시도 상한·벤더 재조회 비용·사용자 재통지 셋은 이 창에서 코드로 확인하지 못했다
    refs:
      - ../05-schedules/03-monitoring.md#monitoring:analysis-status
      - ./00-ia.md#admin-subscription-analysis-log
      - ./00-ia.md#admin-subscription-loss-analysis-log
    anchors:
      - insuniverse-server/apps/api/src/schedules/analysis-status.schedule.ts:25
      - insuniverse-server/apps/api/src/modules/analyze/analyze-admin.controller.ts:72
      - insuniverse-server/apps/api/src/modules/auth/admin/roles.guard.ts:27
      - insuniverse-server/apps/api/src/modules/auth/admin/corp-analysis-view.guard.ts:47
  - id: treat-monitor-analysis-stall
    actor: 관리자
    status: conditional-unknown
    status_source: ../05-schedules/03-monitoring.md#monitoring:analysis-stall
    unknown_reason: 정체 감시와 팩스 상태 감시가 둘 다 배치에 걸려 있고 두 배치의 활성이 판정되지 않았다. 화면과 조회 경로는 확정이다
    evidence: [A-scheduler, A-comment, S-route]
    limitations:
      - 두 감시의 임계값에서 스케줄러 주석과 서비스 코드가 갈린다는 관측이 정책 문서에 있다. 이 문서는 어느 쪽이 옳은지 판정하지 않는다
      - 경고 발송은 관리자가 켠 경고 종류에만 반응하며 그 설정은 운영 DB 에 있다
    refs:
      - ../05-schedules/03-monitoring.md#monitoring:analysis-stall
      - ../05-schedules/03-monitoring.md#monitoring:fax-status
      - ./00-ia.md#admin-subscription-treat-monitor
      - ./00-ia.md#admin-invoice-fax-usage
    anchors:
      - insuniverse-server/apps/api/src/schedules/analysis-stall.schedule.ts:24
      - insuniverse-server/apps/api/src/schedules/fax-status.schedule.ts:28
      - insuniverse-server/apps/api/src/schedules/analysis-status.schedule.ts:33
      - insuniverse-server/apps/api/src/modules/invoice/invoice-admin.controller.ts:40
  - id: treat-monitor-view-token
    actor: 관리자
    status: active-prod
    status_source: ./00-ia.md#admin-subscription-analysis
    evidence: [S-guard, S-test, S-route]
    limitations:
      - 운영 환경의 ADMIN_VIEW_TOKEN_ENFORCE 실제 값은 저장소로 판정할 수 없다. 두 분기의 동작은 확정이고 어느 분기가 도는지가 미상이다
    refs:
      - ./00-ia.md#admin-subscription-analysis
      - ./00-ia.md#admin-analysis-view-param-param
    anchors:
      - insuniverse-server/apps/api/src/modules/admin-view-token/admin-view-token.guard.ts:49
      - insuniverse-server/apps/api/src/modules/admin-view-token/admin-view-token.guard.spec.ts
  - id: treat-monitor-error-tester
    actor: 관리자
    status: staged-only
    status_source: ./00-ia.md#admin-test-treat-error-tester
    evidence: [S-route, S-roles, S-component]
    limitations:
      - 이 화면의 등록이 환경 판정에 걸린다는 것까지가 이 문서의 관측이다. 어느 환경에서 실제로 켜져 있는지는 저장소로 판정할 수 없다
    refs:
      - ./00-ia.md#admin-test-treat-error-tester
    anchors:
      - insuniverse-server/apps/api/src/modules/treat-test/treat-test.controller.ts:16
      - insuniverse-admin/src/app/router/routes/page.routes.tsx:46
      - insuniverse-server/apps/api/src/modules/auth/admin/corp-readonly.guard.ts:21
---

# 진료·분석 실패 감시와 재처리

> **admin 은 자동화된 테스트 근거가 없다.** `insuniverse-admin` 동결 HEAD `fb001e1d` 에 테스트 파일이 한 건도 없다. 이 디렉터리의 모든 문서는 라우트 등록과 컴포넌트 실존과 소스 읽기만을 근거로 하며 화면 축에 `S-test` 등급을 단 항목이 하나도 없다. 절차의 단계 순서가 런타임에서 실제로 그 순서로 도는지는 이 문서가 판정하지 않았다.

이 문서는 진료 조회와 분석이 실패했을 때 관리자가 무엇을 보고 무엇을 할 수 있는지를 담는다. 화면 구조의 정본은 [화면 인벤토리](./00-ia.md)이고, 감시 정책의 정본은 [감시 주기](../05-schedules/03-monitoring.md)다.

**이 문서의 흐름 셋은 `conditional-unknown` 이다.** 고른 값이 아니라 유도된 값이다 — 그 셋이 승계하는 감시 정책이 전부 `conditional-unknown` 이고, 그 미상은 담당 배치의 활성이 판정되지 않았다는 데서 온다. 화면과 조회 경로는 확정이며 미상인 것은 **그 목록이 최신으로 유지되는가** 하나다.

**나머지 둘은 다른 이유로 갈린다.** 열람 토큰 흐름은 `active-prod` 이고, 오류 테스터 흐름은 이 창의 유일한 `staged-only` 화면을 지나므로 그 값을 승계한다.

**총론은 이 문서가 재기술하지 않는다.** 열람 토큰의 정책 정본은 `docs/02-access/` 소유이며 이 창에 그 디렉터리가 없다. 부재 문서로 링크를 걸지 않고 경로만 산문으로 적는다.

**인증 층에는 이 문서가 검증하지 않은 우회 분기가 둘 있다.** `admin/jwt.guard.ts:33` 은 핸들러 메타데이터 `isPublic` 이 참이면 토큰을 읽기 전에 통과시키고, `admin/jwt.guard.ts:42` 는 토큰 검증이 실패해도 `isDevSwaggerRequest` 가 참이면 `DEV_ADMIN`(`admId 19`)을 주입하고 통과시킨다. **아래 모든 권한 표의 인증 층 `차단` 판정과 모든 이탈 표의 `HTTP 401` 은 그 두 분기가 거짓일 때의 값이다.** 이 창의 관측으로는 `isPublic` 을 세팅하는 데코레이터가 저장소 전체에 0건이고 `isDevSwaggerRequest` 는 `NODE_ENV === 'dev'` 와 `referer` 와 `x-forwarded-for` 셋을 함께 요구한다 — 그래서 지금 도달 가능한 분기는 없다고 본다. **부재 증명이 아니다** — 검색어는 `isPublic` 하나이고 배포 환경변수는 대조하지 않았다. 코드 수정은 이 창 범위 밖이며 backlog `50m3-admin-jwt-guard-bypass-branches` 로 이월했다.

## 흐름 목록

| flow id | 목적 | actor | status |
|---|---|---|---|
| treat-monitor-failure-list | 진료 조회·분석 실패 목록을 확인하고 사유별로 분해한다 | 관리자 | conditional-unknown |
| treat-monitor-reprocess | 실패한 건을 다시 돌린다 | 관리자 | conditional-unknown |
| treat-monitor-analysis-stall | 분석 정체와 팩스 상태를 감시한다 | 관리자 | conditional-unknown |
| treat-monitor-view-token | 관리자가 회원 분석 결과를 연다 | 관리자 | active-prod |
| treat-monitor-error-tester | 오류 시나리오 테스터로 실패 분류를 재현한다 | 관리자 | staged-only |

## 흐름

### treat-monitor-failure-list

**전제**

- 통합 콘솔에 로그인되어 있다
- 감시 화면은 `admin-subscription-treat-monitor` 이고 개별 이력은 `admin-subscription-analysis-log` 다
- 실패 상태의 전이는 배치가 만든다. 그 배치의 활성은 판정되지 않았다

**절차**

1. `admin-subscription-treat-monitor` 에서 진료집계를 연다.
2. 개요·월별·기업별·일별 조회로 범위를 좁힌다 (`treat-monitor.controller.ts:14,22,30,36`).
3. 벤더별 실패 분해는 `GET admin/treat-monitor/vendor-errors` 가 낸다 (`treat-monitor.controller.ts:81`). 벤더 축 분해는 `5e18eb66` 이 넣었다.
4. 개별 건의 이력은 `admin-subscription-analysis-log` 에서 본다.

**대량 실패에 대한 일괄 조치는 이 화면에 없다.** 이 컨트롤러의 핸들러 여섯은 전부 `@Get` 이다 — 조회와 엑셀 내보내기뿐이고 상태를 바꾸는 경로가 없다. 그 귀결은 `treat-monitor-reprocess` 가 다룬다.

**이 컨트롤러의 여섯 핸들러에는 `@Roles` 선언이 없다.** `RolesGuard` 는 클래스에 걸려 있으나(`treat-monitor.controller.ts:9`) 메타데이터가 없으면 `roles.guard.ts:22` 가 **통과시킨다.** 즉 인증된 관리자면 계정 종류와 무관하게 진료집계를 조회할 수 있다. 이 문서의 관측이며 코드를 고치지 않는다.

**권한**

| 층 | 차단성 | 이 흐름에서의 강제 지점 | 실패 시 |
|---|---|---|---|
| 인증 | 차단 | `admin/jwt.guard.ts:26` — 컨트롤러 전체에 `AdminJwtAuthGuard` (`treat-monitor.controller.ts:9`) | HTTP 401 |
| 계정 종류 | 차단 | `roles.guard.ts:22` — `RolesGuard` 는 붙었으나 `@Roles` 선언이 0건이라 통과시킨다 | 해당 없음 — 선언이 없어 이 층이 무력하다 |
| 쓰기 스코프 | 차단 | 해당 없음 — 전 구간 GET 이라 `corp-readonly.guard.ts:24` 가 항상 허용한다 | 해당 없음 |
| 기업 노출 등급 | 차단 | 해당 없음 — `CorpAnalysisViewGuard` 미적용 컨트롤러다 (`corp-analysis-view.guard.ts:34`) | 해당 없음 |
| 메뉴 노출 | 비차단 | `menu.repository.ts:32-39` — 감시 메뉴 미배분이면 사이드바에서 사라진다 | 사이드바에서 사라짐. 인증만 있으면 API 는 응답한다 |

**실패·이탈**

| 지점 | 조건 | 결과 |
|---|---|---|
| 목록 조회 | 담당 배치가 돌지 않는다 | 계속 목록은 열리나 최신 상태가 아니다. 배치 활성이 미판정이다 |
| 일별 조회 | 범위가 커 응답이 늦다 | 계속 상한을 이 문서가 판정하지 않았다 |
| 벤더 분해 | 벤더 코드가 매핑에 없다 | 계속 분류의 정확성을 이 문서가 판정하지 않았다 |
| 일괄 조치 시도 | 상태를 바꾸는 핸들러가 없다 | 막다름. 이 화면에서 더 나아가지 않는다 |

### treat-monitor-reprocess

**전제**

- `treat-monitor-failure-list` 로 실패 건을 특정했다
- 확인 화면은 `admin-subscription-analysis-log` 이고 손해분석 축은 `admin-subscription-loss-analysis-log` 다

**절차**

1. `admin-subscription-analysis-log` 에서 실패 건의 이력을 확인한다.
2. 손해분석 축이면 `admin-subscription-loss-analysis-log` 에서 같은 일을 한다.
3. **재처리를 시작하는 관리자 엔드포인트를 이 창에서 찾지 못했다.** 관리자 분석 컨트롤러의 핸들러 일곱은 전부 `@Get` 이고(`analyze-admin.controller.ts:72,90,110,128,142,162,182`) 진료집계 컨트롤러도 마찬가지다.
4. 관측된 회복 경로는 배치 하나다 — `analysis-status.schedule.ts:25` 가 인증 stale 건을 실패로 정산하고 이어서 예약수 정합을 보정한다.

**부재를 부재 증명으로 적지 않는다.** 3단계의 관측은 `treat` 와 `analyze` 두 모듈의 데코레이터 조회 결과이고 관리자 컨트롤러 전수 정독이 아니다. 다른 모듈에 재처리 경로가 있다면 이 검색은 보지 못한다.

**재시도 상한·벤더 재조회 비용·사용자 재통지 셋도 확인하지 못했다.** Q46 이 묻는 세 항목이며 그중 어느 것도 이 창에서 코드로 답하지 못했다. 추측으로 메우지 않는다.

**권한**

| 층 | 차단성 | 이 흐름에서의 강제 지점 | 실패 시 |
|---|---|---|---|
| 인증 | 차단 | `admin/jwt.guard.ts:26` — 이력 조회 컨트롤러에 `AdminJwtAuthGuard` | HTTP 401 |
| 계정 종류 | 차단 | `roles.guard.ts:27` — `@Roles('admin','sales','corp')` (`analyze-admin.controller.ts:44`) | HTTP 403 접근 권한이 없습니다 |
| 쓰기 스코프 | 차단 | `corp-readonly.guard.ts:24` — 이 흐름은 GET 뿐이라 항상 통과한다 | 해당 없음 — 이 흐름에서는 발생하지 않는다 |
| 기업 노출 등급 | 차단 | `corp-analysis-view.guard.ts:47` — `role` 이 `corp` 면 요구 등급을 본다. 운영사 관리자는 34행에서 통과한다 | HTTP 403 분석관리 화면 접근 권한이 없습니다 |
| 메뉴 노출 | 비차단 | `menu.repository.ts:32-39` — 이력 메뉴 미배분이면 사이드바에서 사라진다 | 사이드바에서 사라짐. API 는 응답한다 |

**실패·이탈**

| 지점 | 조건 | 결과 |
|---|---|---|
| 재처리 시도 | 관리자 엔드포인트가 관측되지 않았다 | 막다름. 화면에서 시작할 수 있는 경로를 이 문서가 찾지 못했다 |
| 배치 회복 | 담당 배치가 돌지 않는다 | 계속 실패 건이 그대로 남는다. 활성이 미판정이다 |
| 배치 회복 | 인증 stale 임계 | 계속 코드와 주석이 갈린다는 관측이 감시 정책에 있다 |
| 사용자 재통지 | 회복 후 통지 여부 | 계속 이 창에서 확인하지 못했다 |

### treat-monitor-analysis-stall

**전제**

- 통합 콘솔에 로그인되어 있다
- 정체 감시의 화면 자리는 `admin-subscription-treat-monitor` 이고 팩스 축은 `admin-invoice-fax-usage` 다
- 두 감시의 발효는 각각의 배치 활성에 걸려 있다

**절차**

1. `admin-subscription-treat-monitor` 에서 정체 여부를 본다.
2. 정체 감시 배치는 `analysis-stall.schedule.ts:24` 가 진다.
3. 팩스 상태는 `admin-invoice-fax-usage` 에서 보고 감시 배치는 `fax-status.schedule.ts:28` 이 진다.
4. 임계를 넘으면 경고가 발송된다. 발송은 관리자가 켠 경고 종류에만 반응하며 그 설정은 운영 DB 에 있다.

**두 감시의 임계에서 스케줄러 주석과 서비스 코드가 갈린다.** 그 관측은 감시 정책 문서가 `limitations` 로 담고 있고 이 문서는 어느 쪽이 옳은지 **판정하지 않는다** — 판정 권한이 이 PRD 에 없다.

**권한**

| 층 | 차단성 | 이 흐름에서의 강제 지점 | 실패 시 |
|---|---|---|---|
| 인증 | 차단 | `admin/jwt.guard.ts:26` — 감시·청구서 조회 양쪽에 `AdminJwtAuthGuard` | HTTP 401 |
| 계정 종류 | 차단 | `roles.guard.ts:22` — 진료집계 축은 `@Roles` 선언이 0건이라 통과한다. 청구서 축은 `invoice-admin.controller.ts:40` 의 `@Roles('admin')` 을 따른다 | 진료집계 축 해당 없음 — 이 층이 무력하다. 청구서 축 HTTP 403 접근 권한이 없습니다 |
| 쓰기 스코프 | 차단 | 해당 없음 — 전 구간 GET 이다 (`corp-readonly.guard.ts:24`) | 해당 없음 |
| 기업 노출 등급 | 차단 | 해당 없음 — `role` 이 `corp` 가 아니면 통과한다 (`corp-analysis-view.guard.ts:34`) | 해당 없음 |
| 메뉴 노출 | 비차단 | `menu.repository.ts:32-39` — 감시·청구서 메뉴 미배분이면 사이드바에서 사라진다 | 사이드바에서 사라짐. API 는 응답한다 |

**실패·이탈**

| 지점 | 조건 | 결과 |
|---|---|---|
| 정체 확인 | 담당 배치가 돌지 않는다 | 계속 화면은 열리나 감시가 서지 않는다 |
| 경고 발송 | 관리자가 그 종류를 켜지 않았다 | 계속 임계를 넘어도 발송되지 않는다 |
| 경고 발송 | 발송 자체가 예외로 실패한다 | fail-open — 배치는 계속 진행하고 로그만 남긴다 (`analysis-status.schedule.ts:33`) |
| 임계 판정 | 주석과 코드가 갈린다 | 계속 이 문서가 어느 쪽이 옳은지 판정하지 않는다 |

### treat-monitor-view-token

**전제**

- 통합 콘솔에 로그인되어 있다
- 목록은 `admin-subscription-analysis` 이고 상세 뷰어는 팝업 `admin-analysis-view-param-param` 이다
- 열람 토큰 강제 여부는 환경변수 `ADMIN_VIEW_TOKEN_ENFORCE` 가 정한다

**절차**

1. `admin-subscription-analysis` 에서 대상 건을 고른다.
2. 상세를 열면 `admin-analysis-view-param-param` 팝업이 뜨고 열람 토큰을 질의문자열 `t` 또는 헤더 `x-admin-view-token` 으로 실어 보낸다 (`admin-view-token.guard.ts:28-30`).
3. 가드가 대상을 해석해 토큰을 검증한다 (`admin-view-token.guard.ts:31-33`).
4. 완전 검증된 v2 토큰이면 관리자 identity 를 복원한다 (`admin-view-token.guard.ts:37-44`).

**강제가 꺼져 있으면 미검증도 통과한다.** `admin-view-token.guard.ts:49-55` 가 `ADMIN_VIEW_TOKEN_ENFORCE !== 'true'` 일 때 경고 로그만 남기고 `true` 를 낸다. 켜져 있으면 56-57행이 `ForbiddenException` 을 던진다. **이것이 `fail-open` 의 정확한 사례이며** 무중단 롤아웃을 위한 의도된 게이트라고 주석이 적는다.

**꺼진 상태에서 무엇이 허용되는가.** 토큰 없이도 상세가 열리고, v2 가 아닌 토큰은 identity 복원만 건너뛴다. IDOR 차단은 플래그를 켠 뒤에야 활성이다 — 그 문장이 47행 주석에 그대로 있다.

**운영에서 어느 분기가 도는지는 저장소로 판정할 수 없다.** 두 분기의 동작은 확정이고 미상인 것은 환경변수 값 하나다.

**권한**

| 층 | 차단성 | 이 흐름에서의 강제 지점 | 실패 시 |
|---|---|---|---|
| 인증 | 차단 | `admin/jwt.guard.ts:26` — 목록 조회 경로에 `AdminJwtAuthGuard` | HTTP 401 |
| 계정 종류 | 차단 | `roles.guard.ts:27` — `@Roles('admin','sales','corp')` (`analyze-admin.controller.ts:44`) | HTTP 403 접근 권한이 없습니다 |
| 쓰기 스코프 | 차단 | 해당 없음 — 열람은 GET 이다 (`corp-readonly.guard.ts:24`) | 해당 없음 |
| 기업 노출 등급 | 차단 | `corp-analysis-view.guard.ts:47` — `role` 이 `corp` 일 때만 발화한다 | HTTP 403 분석관리 화면 접근 권한이 없습니다 |
| 메뉴 노출 | 비차단 | `menu.repository.ts:32-39` — 분석 메뉴 미배분이면 사이드바에서 사라진다 | 사이드바에서 사라짐. API 는 응답한다 |

**실패·이탈**

| 지점 | 조건 | 결과 |
|---|---|---|
| 상세 열기 | 강제 꺼짐 · 토큰 미검증 | fail-open — 통과시키고 경고 로그만 남긴다 (`admin-view-token.guard.ts:49-55`) |
| 상세 열기 | 강제 켜짐 · 토큰 미검증 | HTTP 403 invalid or missing admin-view token |
| 상세 열기 | 대상 해석 실패 | 계속 대상이 없으면 검증을 건너뛴다. 강제 여부가 그다음을 정한다 |
| 팝업 진입 | 부모 창 없이 직접 열림 | 막다름. 팝업 레이아웃이 네비게이션을 렌더하지 않는다 |

### treat-monitor-error-tester

**전제**

- 통합 콘솔에 로그인되어 있다
- 테스터 화면 `admin-test-treat-error-tester` 의 라우트 등록이 환경 판정을 통과했다
- 이 화면은 이 저장소에서 유일하게 `staged-only` 인 화면이다

**절차**

1. `admin-test-treat-error-tester` 를 연다. 라우트 등록 자체가 환경 판정에 걸려 있어 그 판정이 거짓이면 화면이 없다.
2. 시나리오 목록을 받아 재현할 오류를 고른다 (`treat-test.controller.ts:27`).
3. 본인확인·진료조회 시나리오를 실행한다 (`treat-test.controller.ts:30,33`).
4. 결과와 매핑 상태를 확인한다 (`treat-test.controller.ts:39,122`).

**이 컨트롤러에는 `RolesGuard` 가 없다.** 16행이 `@UseGuards(AdminJwtAuthGuard)` 하나뿐이고 `@Roles` 도 0건이다. 즉 **인증된 관리자면 계정 종류와 무관하게 이 모듈의 `@Post` 핸들러를 부를 수 있다.** 카탈로그 편집·삭제(`treat-test.controller.ts:49,56,63,70`)도 같은 층 아래 있다. 이 문서의 관측이며 코드를 고치지 않는다.

**미배포가 미검증과 같은 뜻은 아니다.** 이 화면의 `staged-only` 는 등록이 환경 판정에 걸린다는 관측이고, 어느 환경에서 실제로 켜져 있는지는 저장소로 판정할 수 없다.

**권한**

| 층 | 차단성 | 이 흐름에서의 강제 지점 | 실패 시 |
|---|---|---|---|
| 인증 | 차단 | `admin/jwt.guard.ts:26` — `@UseGuards(AdminJwtAuthGuard)` (`treat-test.controller.ts:16`) | HTTP 401 |
| 계정 종류 | 차단 | 해당 없음 — `RolesGuard` 가 붙지 않았고 `@Roles` 도 없다. 이 층이 통째로 서지 않는다 | 해당 없음 — 층 자체가 없다 |
| 쓰기 스코프 | 차단 | 해당 없음 — `CorpReadonlyGuard` 미적용 컨트롤러다 (`corp-readonly.guard.ts:21`) | 해당 없음 |
| 기업 노출 등급 | 차단 | 해당 없음 — `CorpAnalysisViewGuard` 미적용 컨트롤러다 (`corp-analysis-view.guard.ts:34`) | 해당 없음 |
| 메뉴 노출 | 비차단 | `menu.repository.ts:32-39` — 테스터 메뉴는 배분 대상이 아니다. 라우트 등록이 노출을 정한다 | 라우트 미등록이면 화면 자체가 없다 |

**실패·이탈**

| 지점 | 조건 | 결과 |
|---|---|---|
| 화면 진입 | 라우트 등록 환경 판정이 거짓 | 막다름. 화면이 등록되지 않는다 |
| 시나리오 실행 | 인증만 있고 계정 종류가 다르다 | 계속 `RolesGuard` 가 없어 통과한다 |
| 카탈로그 편집 | 같은 조건 | 계속 같은 층 아래 있어 통과한다 |
| 결과 확인 | 매핑이 없는 벤더 코드 | 계속 매핑 상태 조회가 그 사실을 낸다 (`treat-test.controller.ts:122`) |

## 연혁

릴리스 태그가 존재하지 않는다. 릴리스 열은 `—` 로 두고 커밋 SHA 로 추적한다. 절차의 단계·권한 층·이탈 경로가 바뀐 시점만 적는다.

| 릴리스 | 날짜 | SHA | 변경 요약 |
|---|---|---|---|
| — | 2026-06-08 | `82826ac4` | prod 빌드에서 테스터 라우트 미등록 — 이 화면이 환경 판정에 걸리게 됐다 |
| — | 2026-06-08 | `fa513fe5` | 테스터 라우트 노출을 stg 전용으로 fail-closed — 같은 축의 후속 |
| — | 2026-07-21 | `5e18eb66` | 일별 집계 grain 에 벤더 추가 — 실패 분해 단계가 생겼다 |
| — | 2026-07-23 | `ef4908eb` | 진료집계 엑셀 다운로드 엔드포인트 — 조회 절차에 내보내기가 붙었다 |
| — | 2026-07-31 | `482f60df` | 관리자 열람 통과를 admin-view 단명 토큰으로도 인가 — 열람 흐름에 토큰 층이 생겼다 |
