fix(collector): MERGE + auto_resolved/expired 상태 변경 감지

- pending 파일 콘텐츠 해시 추적 (_pending_hashes)
- 내용 변경 시 Gateway에 재전달 (MERGE: command 업데이트, status 변경)
- _startup_pending으로 시작 시 기존 파일과 신규 파일 분리
This commit is contained in:
Variet Worker
2026-03-11 22:48:47 +09:00
parent 58a421f5a6
commit d2a477e12e

View File

@@ -11,6 +11,7 @@ Flow:
"""
import asyncio
import hashlib
import json
import time
import logging
@@ -42,12 +43,21 @@ class CollectorBridge:
self._running = False
# Pre-populate with existing pending files → skip on startup (prevents 만료됨 spam)
self._startup_pending: set[str] = set()
self._forwarded_pending: set[str] = set()
self._pending_hashes: dict[str, str] = {} # rid → content hash (for MERGE/status detection)
for fname in self.local.list_json_files("pending"):
rid = fname.replace(".json", "")
self._startup_pending.add(rid)
self._forwarded_pending.add(rid)
if self._forwarded_pending:
logger.info(f"[COLLECTOR] skipping {len(self._forwarded_pending)} existing pending files")
# Pre-hash existing files
data = self.local.read_json("pending", fname)
if data:
self._pending_hashes[rid] = hashlib.md5(
json.dumps(data, sort_keys=True).encode()
).hexdigest()
if self._startup_pending:
logger.info(f"[COLLECTOR] skipping {len(self._startup_pending)} existing pending files")
async def start(self):
"""Start the Collector polling loops."""
@@ -72,26 +82,53 @@ class CollectorBridge:
# ─── Forward local pending → Gateway ───
async def _forward_pending_loop(self):
"""Scan local pending/ and forward new requests to Gateway."""
"""Scan local pending/ and forward new + updated requests to Gateway.
Tracks content hashes to detect:
- New pending files → forward immediately
- MERGE updates (step_probe updates command text) → re-forward
- Status changes (auto_resolved, expired) → re-forward
"""
while self._running:
try:
current_files = set()
for fname in self.local.list_json_files("pending"):
rid = fname.replace(".json", "")
if rid in self._forwarded_pending:
continue
current_files.add(rid)
data = self.local.read_json("pending", fname)
if data is None or data.get("status") != "pending":
if data is None:
continue
# Forward to Gateway
# Compute content hash to detect changes
content_hash = hashlib.md5(
json.dumps(data, sort_keys=True).encode()
).hexdigest()
prev_hash = self._pending_hashes.get(rid)
if prev_hash == content_hash:
continue # No change
is_new = rid not in self._forwarded_pending
if is_new and rid in self._startup_pending:
continue # Skip pre-existing files from before startup
# Forward to Gateway (new or updated)
self.remote.write_json("pending", fname, data)
self._forwarded_pending.add(rid)
logger.info(f"[COLLECTOR] → Gateway: pending {rid[:12]}")
self._pending_hashes[rid] = content_hash
# Clean up stale forwarded tracking (keep last 200)
if len(self._forwarded_pending) > 200:
self._forwarded_pending = set(list(self._forwarded_pending)[-100:])
if is_new:
logger.info(f"[COLLECTOR] → Gateway: pending {rid[:12]}")
else:
status = data.get("status", "?")
logger.info(f"[COLLECTOR] → Gateway: pending UPDATE {rid[:12]} status={status}")
# Clean up tracking for deleted files
for rid in list(self._forwarded_pending):
if rid not in current_files and rid not in self._startup_pending:
self._forwarded_pending.discard(rid)
self._pending_hashes.pop(rid, None)
except Exception as e:
logger.error(f"[COLLECTOR] forward_pending error: {e}")