subscription-svc의 reportDeviceEvents다(§2).
1. 이벤트 분류 2종
| 분류 | category | 설명 | 예시 |
|---|---|---|---|
| 사용자 행위 이벤트 | BEHAVIOR | 게스트가 장치를 조작한 행위. 고볼륨 | kiosk.screen.entered, kiosk.checkin.started, kiosk.checkin.completed, kiosk.payment.attempted, kiosk.payment.failed |
| 장치 상태/헬스 이벤트 | DEVICE_HEALTH | 장치 자체의 상태 변화, 장애 | device.card_reader.fault, device.printer.paper_out, ccu.connection.lost, ccu.door.opened |
category와 severity(INFO/WARN/ERROR/CRITICAL)는 별도 분류 필드로 둔다 — actionType=EVENT 하나로 뭉뚱그리지 않는다. 화면 진입처럼 고볼륨인 BEHAVIOR 이벤트는 보존기간과 기본 조회 노출 정책(예: 기본 조회에서 제외, 별도 조회에서만 노출)을 DEVICE_HEALTH와 분리할 수 있어야 하기 때문이다.
2. 수집 경로 & 신뢰 경계 원칙
장치는 audit-svc에 직접 쓰지 않는다. 수신 서비스는apps/subscription-svc에 신설하는 GraphQL 배치 mutation reportDeviceEvents 다 — 같은 서비스가 이미 publishAccommodationNotification으로 업장 알림을 처리하고 있고, 장치 인증 접근 제어(isKioskSelf/isCcuSelf, apps/subscription-svc/src/graphql/helpers/accessControl.ts)도 거기 갖춰져 있으므로 새 서비스를 만들지 않고 그 위에 얹는다.
용도 구분: publishAccommodationNotification은 프런트/키오스크 화면에 실시간으로 보여줄 알림(pubsub 구독 대상)이고, reportDeviceEvents는 화면에 보여주지 않는 이벤트 로그(audit 파이프라인 적재 전용)다. 알림이 필요 없는 화면 진입·장치 헬스 이벤트를 알림 mutation에 얹지 않는다.
호출 흐름:
- 키오스크/CCU가 게이트웨이를 경유해 장치 인증 컨텍스트(
context.extra.user.kioskId또는.ccuId)를 갖고reportDeviceEvents를 호출한다. - resolver가
isKioskSelf/isCcuSelf로 요청의deviceId가 인증된 장치 본인인지 검증한다 —requestAdultAuthentication(apps/subscription-svc/src/graphql/resolver/notification.ts:223)이 쓰는 것과 동일한 패턴:isKioskSelf(context.extra, { kioskId })가 거짓이면 즉시 거부. accommodationId는 요청 바디 값을 그대로 믿지 않는다.requestAdultAuthentication이kioskAccommodationId !== accommodationId일 때KIOSK_ACCOMMODATION_MISMATCH로 거부하듯, subscription-svc가 인증된 장치의 등록 정보(context.extra.user.accommodationId)에서 조회한 값과 요청값을 대조해 불일치 시 거부한다.- subscription-svc가
actorType/contextId등을 enrich한 뒤 기존 audit 파이프라인(publishAuditLogBatch)으로 발행한다.
accommodationId,kioskId등 장치 신원에 관한 값은 장치가 주장하는 값을 신뢰하지 않는다. resolver가 인증 컨텍스트에서 서버 측이 직접 조회해 채운다.- 장치가 보낸 페이로드의
accommodationId/kioskId필드는 참고용으로만 취급하고, 서버가 조회한 값과 다르면 요청을 거부한다.
3. 클라이언트 전송 계약
장치가 이벤트를 보낼 때 요청에 실어야 하는 필드:| 필드 | 필수 | 설명 |
|---|---|---|
eventId | ✅ | 장치가 생성하는 ULID/UUID. 오프라인 재전송 시 서버 측 멱등 키로 쓰인다 |
occurredAt | ✅ | 장치 로컬 시각(ISO 8601). 서버는 수신 시각을 receivedAt으로 별도 기록하고 둘의 차이(클럭 스큐)를 모니터링한다 |
eventType | ✅ | 이벤트 카탈로그에 등록된 안정적인 점(dot) 표기 이름 (예: kiosk.screen.entered) |
category | ✅ | BEHAVIOR | DEVICE_HEALTH |
severity | ✅ (기본값 INFO) | INFO | WARN | ERROR | CRITICAL |
deviceId | ✅ | 장치 고유 식별자 (예: kioskId) |
sessionId | 선택 | 게스트 세션/체크인 플로우 식별자 |
resourceRefs | 선택 | 알고 있는 대상 리소스 참조 (reservationId, roomId 등) |
payload | 선택 | 이벤트별 최소 필드만. 10KB 이내 권장, 초과 시 rejected(PAYLOAD_TOO_LARGE)로 명시 거부(절단 아님 — ADR-0007 §2.4 참고) |
금지 필드 (payload에 절대 포함 금지)
카드번호/트랙데이터, 비밀번호, 인증 토큰 원문, 주민등록번호 등. 서버 측 캡처 계층도 재귀 마스킹(libs/audit/src/extension/mask.ts)을 거치지만, 클라이언트가 애초에 보내지 않는 것이 원칙이다 — 마스킹은 마지막 방어선이지 전송 허용 기준이 아니다.
배치 전송 API — reportDeviceEvents
오프라인 버퍼 플러시를 위해 단건이 아닌 배열 + 부분 성공 응답 형태를 기본으로 제공한다. subscription-svc의 GraphQL mutation이다.
4. 오프라인/재시도 시맨틱
- 로컬 버퍼: 장치는 네트워크 단절 시 이벤트를 로컬에 버퍼링한다. 버퍼는 상한(건수)과 보존 시간을 둔다.
- 재시도: 지수 백오프 + jitter로 재전송한다.
- 전달 보장: at-least-once를 전제한다 — 서버가
eventId기준 dedup을 책임지므로 클라이언트는 중복 전송을 두려워하지 않아도 된다. - 전송 우선순위: 버퍼가 가득 찼거나 네트워크가 간헐적일 때 우선순위 순서로 플러시한다.
- 결제/보안 관련 이벤트 (
kiosk.payment.*,device.card_reader.*) - 장치 헬스 이벤트
- 화면 이동 등 저우선순위
BEHAVIOR이벤트
- 결제/보안 관련 이벤트 (
- 버퍼 초과 시 드롭 정책: 오래된
BEHAVIOR이벤트부터 드롭한다.DEVICE_HEALTH와 결제 관련 이벤트는 최대한 보존한다.
5. actorType 매핑 (확정)
| 이벤트 주체 | actorType |
|---|---|
| 키오스크 상 게스트 행위 (체크인, 결제 등) | KIOSK |
| 장치 자체 상태 변화 (카드단말 불량, 용지 부족 등) | DEVICE |
AuditActorType(libs/audit/src/context/audit-context.ts)에 DEVICE를 추가한다. reportDeviceEvents는 이벤트별 category를 보고 enrich 단계에서 BEHAVIOR → KIOSK, DEVICE_HEALTH → DEVICE로 매핑해 채운다.
6. 서버측 수신 서비스 체크리스트 (subscription-svc · reportDeviceEvents)
- 장치 신원 검증 — resolver가 인증 이전에
kioskId/ccuId가 정확히 1개만 제공됐는지 확인한다. 둘 다 없거나 둘 다 있으면INVALID_DEVICE_IDENTITY로 즉시 거부. - 장치 인증 —
isKioskSelf/isCcuSelf(accessControl.ts)로 요청의kioskId/ccuId가 인증 컨텍스트(context.extra.user)의 장치 본인인지 검증. 불일치 시KIOSK_AUTH_REQUIRED등으로 즉시 거부. - accommodationId 대조 — 인증된 장치의 등록
accommodationId와 요청의accommodationId를 대조, 불일치 시KIOSK_ACCOMMODATION_MISMATCH로 거부(requestAdultAuthentication과 동일 패턴). - 스키마 검증 —
eventId/occurredAt/eventType/category필수 필드와eventType카탈로그 존재 여부를 확인. - eventId 멱등 — 이미 처리된
eventId는accepted가 아니라duplicated로 응답하고 재처리하지 않는다. - Enrich —
accommodationId(§2)/actorType(§5)/contextId를 서버가 직접 채운다. 장치가 보낸 값은 무시하거나 대조용으로만 쓴다. - Audit 파이프라인 발행 —
publishAuditLogBatch(@vpms-cluster/audit)를 호출한다. ADR-0007의 outbox 전환은 이 호출의 내부 구현만 바뀌므로reportDeviceEvents쪽 코드 변경은 없다.
7. 이벤트 예시
예시 1 — 화면 진입 배치 (BEHAVIOR)
reportDeviceEvents mutation 호출 변수: