feat(pipeline): R3 sentinel sanitize (string+number) + R4.0 inbound ports

R3 — PLC sentinel(-480910 / -481000) drop policy (root cause fix)
  - new: src/domain/policies/tag-value-sanitizer.policy.ts
  - wired in deviceCollectorService.publishData() 진입부 — 모든 sink
    (로컬 MQTT, IDC central MQTT, equipmentState, target DB) 직전 1회 호출
  - typeof bug 수정: PLC/Edge 가 '-480910.000000' string 으로 전송하는 케이스
    포함. coerceNumeric() 으로 number/string 양쪽 안전 변환 후 Set 매칭.
  - T6 (Claude tracer agent) 진단 결과 — Edge 컨테이너에서 hash field 가
    '-480910.000000' string 으로 적재되어 typeof === 'number' 만 검사하던
    이전 로직 통과. dt-web 응답까지 sentinel 도달 확인됨.

R4.0 — Inbound Port 인터페이스만 정의 (런타임 영향 0)
  - new: src/ports/inbound/plc-source.port.ts (PlcSourcePort)
  - new: src/ports/inbound/rest-request.port.ts (RestRequestPort, RestResponse)
  - new: src/ports/inbound/scheduled-trigger.port.ts (ScheduledTriggerPort)
  - 어댑터 구현은 R4.1+ 단계에서 진행 (deviceCollectorService 의 thin wrapper).

영향:
  - Edge pipeline-backend 빌드 시 sanitize 호출 활성화 → IDC Redis 까지
    sentinel 도달 차단. dt-web 의 운영 워크어라운드 제거 가능.
  - 4개 신규 파일 + 1개 기존 파일 +5 lines 수정.

Constraint: chpark 의 로컬 hex 작업과 동기화 필요 — git pull main 후 머지/리베이스 권장
Confidence: high (T6 tracer 가 진단 + Edge build 산물 코드 위치 일치)
Scope-risk: narrow (publishData 진입부 1줄 + 4 신규 파일)
Directive: SENTINEL_VALUES set 변경 시 coerceNumeric 의 string 처리도 함께 갱신
Not-tested: chpark 의 로컬 R1~R5 작업과의 충돌 (사용자가 안내 예정)
This commit is contained in:
h.offthatmuz
2026-05-15 10:30:30 +09:00
parent 974938d8aa
commit 931127505a
5 changed files with 149 additions and 0 deletions
@@ -28,6 +28,11 @@ import { upsertEquipmentState } from "./equipmentStateService";
import { ingest as forwardToCentralMqtt } from "./centralMqttForwarder";
import { getHooksForConnection } from "./scriptCache";
import { executeHook } from "./pythonHookRunner";
import { TagValueSanitizer } from "../../domain/policies/tag-value-sanitizer.policy";
// R3 — PLC sentinel 값(-480910 등) 차단. 모든 sink 직전에 호출.
// 2026-05-15: typeof bug fix — string sentinel 도 처리 (T6 진단).
const tagSanitizer = new TagValueSanitizer({ onSentinel: "drop" });
// ─── 타입 ──────────────────────────────────────────
@@ -502,6 +507,10 @@ async function applyHooks(data: CollectedData): Promise<void> {
// ─── 수집 결과 발행 ───────────────────────────────
async function publishData(data: CollectedData): Promise<void> {
// R3 — sentinel sanitize (PLC 미연결/리셋 시 -480910 등 transient garbage 차단)
// 모든 sink (로컬 MQTT, IDC central MQTT, equipmentState, target DB) 직전.
data = { ...data, tags: tagSanitizer.sanitize(data.tags) };
// 1. 로컬 MQTT 발행 (UI 실시간 스트리밍용)
if (mqttClient && mqttConfig) {
try {