Skip to content

feat(observability): 모니터링 API 공용 기반 + Redis 인프라 #29 - #69

Open
tlgms wants to merge 2 commits into
developfrom
feature/29-observability-01-foundation
Open

feat(observability): 모니터링 API 공용 기반 + Redis 인프라 #29#69
tlgms wants to merge 2 commits into
developfrom
feature/29-observability-01-foundation

Conversation

@tlgms

@tlgms tlgms commented Aug 11, 2026

Copy link
Copy Markdown

Summary

  • monitoring-observability.md 기반 관제 API 구현의 1/4번째 PR (스택 PR 중 첫 번째, base: develop)
  • observability 시스템의 domain 계층(enum/exception/model)과 adapter-in 공용 응답/에러 처리(ApiResponse, ErrorDetail, ErrorResponse, GlobalExceptionHandler)를 identity 컨벤션에 맞춰 구성
  • 세션·트래픽 집계를 위한 SessionStorePort/RedisSessionStoreAdapter, IP 기준 rate limit을 위한 RateLimitPort/RedisRateLimitAdapter 추가
  • Redis/JWT/xlsx(POI) 의존성을 kotlin.MODULE.bazel에 추가

Related Issue

Scope

  • In scope: domain enum/exception/model, adapter-in 공용 DTO/예외처리, Redis 세션 저장소, Bazel 의존성
  • Out of scope: 실제 API 엔드포인트(컨트롤러)는 다음 PR에서 추가

Testing

  • bazel build //systems/observability/..., bazel test //systems/observability/... 통과
  • bazel test //systems/... (전체 시스템) 통과 확인 — 다른 시스템 영향 없음

Checklist

  • API 단위로 커밋 분리
  • 코드 리뷰

tlgms and others added 2 commits August 9, 2026 21:53
monitoring-observability.md 명세에 맞춰 observability 시스템의 domain
enum/exception/model과 adapter-in 공용 응답/에러 처리(ApiResponse,
ErrorDetail, ErrorResponse, GlobalExceptionHandler)를 identity 시스템
컨벤션에 맞춰 구성. Redis/JWT/xlsx(POI) 의존성을 kotlin.MODULE.bazel에
추가.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
세션·트래픽 집계(동시접속자, 총방문자, 체류시간, 기기분포)를 위한
SessionStorePort/RedisSessionStoreAdapter와 IP 기준 rate limit을 위한
RateLimitPort/RedisRateLimitAdapter를 추가. collect/session API의 기반.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown
📝 Walkthrough

사용자 영향: 모니터링 API의 도메인·세션 집계·공통 오류 처리 기반을 추가했습니다. 실제 API 엔드포인트는 아직 제공하지 않습니다.

주요 변경 사항

  • observability-domain에 모니터링용 enum, 예외, Cursor, Session 모델을 추가했습니다.
  • DeviceTypeParserHealthStatusClassifier를 추가했습니다.
  • ApiResponse, ErrorDetail, ErrorResponse를 추가했습니다.
  • GlobalExceptionHandler로 도메인 예외, 잘못된 요청, 검증 오류, 미처리 예외를 공통 처리합니다.
  • RecordSessionEventUseCase, SessionStorePort, RateLimitPort를 추가했습니다.
  • SessionCollectionService에서 ENTER, HEARTBEAT, LEAVE 흐름과 IP별 분당 60회 제한을 처리합니다.
  • Redis 기반 세션 저장소와 고정 윈도우 rate limit 어댑터를 추가했습니다.
  • UTC Clock Spring Bean을 추가했습니다.
  • Redis, JJWT, Apache POI, Spring Boot 웹·검증 의존성을 Bazel 설정에 추가했습니다.
  • 도메인, 애플리케이션, 어댑터 테스트를 실제 단위 테스트 스위트로 변경했습니다.

위험 영역

  • Redis 키 구조, TTL, 동시성 집계 동작을 운영 환경에서 검증해야 합니다.
  • rate limit은 Redis INCREXPIRE 기반의 고정 윈도우 방식입니다.
  • GlobalExceptionHandler는 미처리 예외 발생 시 trace ID와 스택을 로그에 기록합니다.
  • 세션 집계와 heartbeat 만료 정책은 후속 API 연결 시 클라이언트 계약과 일치해야 합니다.

마이그레이션 및 호환성

  • 신규 모듈과 포트는 기존 시스템의 공개 API를 변경하지 않습니다.
  • 운영 환경에 Redis 연결 설정과 Redis 키 정책이 필요합니다.
  • 실제 모니터링 API 컨트롤러와 엔드포인트는 후속 PR에서 추가해야 합니다.
  • Bazel 타깃은 테스트 의존성에 각 모듈의 :main을 포함하도록 변경했습니다.

검증 및 롤아웃

  • Cursor 인코딩·디코딩 및 잘못된 커서 검증
  • User-Agent 기반 DeviceType 판별 검증
  • 서비스 상태 분류 검증
  • 세션 ENTER, HEARTBEAT, rate limit 검증
  • 공통 예외 응답 및 HTTP 상태 검증
  • 관련 Bazel 빌드 및 테스트 통과
  • 전체 시스템 테스트에서 기존 시스템 영향 확인
  • Redis 기반 통합 테스트 및 부하 테스트
  • 후속 API 엔드포인트 추가 후 계약 테스트

Walkthrough

Changes

Observability 핵심 기능

Layer / File(s) Summary
도메인 타입과 정책
systems/observability/observability-domain/src/main/kotlin/... , systems/observability/observability-domain/src/test/kotlin/...
오류 코드, 세션·서비스·리포트 관련 열거형, Cursor, Session, DeviceTypeParser, HealthStatusClassifier를 추가했습니다. 관련 단위 테스트와 테스트 스위트를 구성했습니다.
세션 이벤트 수집 유스케이스
systems/observability/observability-application/src/main/kotlin/..., systems/observability/observability-application/src/test/kotlin/..., systems/observability/observability-bootstrap/src/main/kotlin/...
RecordSessionEventUseCase, SessionStorePort, RateLimitPort, SessionCollectionService를 추가했습니다. IP별 분당 60회 제한과 ENTER, HEARTBEAT, LEAVE 처리를 구현했습니다. UTC Clock Bean과 서비스 테스트를 추가했습니다.
Redis 어댑터
systems/observability/observability-adapter-out/src/main/kotlin/..., systems/observability/observability-adapter-out/deps.bzl, systems/observability/observability-adapter-out/BUILD.bazel
Redis INCR·EXPIRE 기반 rate limit과 세션 저장소를 추가했습니다. 세션 TTL, 방문자·디바이스·동시성 통계를 관리합니다.
웹 오류 응답
systems/observability/observability-adapter-in/src/main/kotlin/..., systems/observability/observability-adapter-in/src/test/kotlin/..., systems/observability/observability-adapter-in/deps.bzl, systems/observability/observability-adapter-in/BUILD.bazel
ApiResponse, ErrorDetail, ErrorResponseGlobalExceptionHandler를 추가했습니다. 도메인 예외, 잘못된 요청, 검증 오류, 처리되지 않은 예외의 응답과 trace ID 로깅을 테스트합니다.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant SessionCollectionService
  participant RedisRateLimitAdapter
  participant RedisSessionStoreAdapter
  Client->>SessionCollectionService: 세션 이벤트 전송
  SessionCollectionService->>RedisRateLimitAdapter: IP rate limit 확인
  RedisRateLimitAdapter-->>SessionCollectionService: 허용 여부 반환
  SessionCollectionService->>RedisSessionStoreAdapter: 세션 enter, heartbeat, leave 처리
  RedisSessionStoreAdapter-->>SessionCollectionService: 저장 결과 반환
  SessionCollectionService-->>Client: 세션 ID와 heartbeat 간격 반환
Loading

Suggested labels: kotlin, bazel

🚥 Pre-merge checks | ✅ 8 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% which is insufficient. The required threshold is 85.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Behavior Change Needs Tests ⚠️ Warning RedisRateLimitAdapterRedisSessionStoreAdapter에 실제 동작 테스트가 없고, adapter-out 테스트는 moduleLoads()만 실행합니다. PR 설명에도 생략 사유가 없습니다. observability-adapter-out에 두 Redis 어댑터의 카운터, TTL, 세션 입장·heartbeat·퇴장·집계 동작을 검증하는 테스트를 추가하고 테스트 스위트에 등록하십시오.
✅ Passed checks (8 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 feat(observability): <subject> 형식을 따르며 변경 내용과 관련된 이슈 번호 #29를 포함합니다.
Description check ✅ Passed 설명이 도메인 기반, 공용 응답 및 예외 처리, Redis 어댑터, 의존성, 테스트 범위를 구체적으로 설명합니다.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Kotlin Layer Boundary ✅ Passed observability-domain의 모든 Kotlin 파일에 adapter/bootstrap import가 없습니다. application은 @Service만 사용하며 Spring 빈 등록 목적이고 configuration-application에도 동일한 관례가 있습니다.
Go Error Context ✅ Passed 기준 브랜치 대비 변경 파일은 Kotlin과 Bazel 파일뿐이며, 변경된 Go 코드와 외부 호출 오류 처리가 없습니다.
Bazel Formatting ✅ Passed 변경된 5개 BUILD.bazel과 3개 deps.bzl은 저장소의 buildifier 형식(4칸 들여쓰기, 명확한 블록, 후행 쉼표)을 따릅니다. 모든 모듈의 타깃 이름도 main/test로 안정적입니다.
Todo Must Reference Issue ✅ Passed develop...HEAD의 44개 변경 파일과 추가 라인을 검사했으며 TODO/FIXME 주석이 발견되지 않았습니다.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/29-observability-01-foundation
  • 🛠️ cleanup stale imports
  • 🛠️ harden error messages
  • 🛠️ test clarity pass

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@systems/observability/observability-adapter-in/src/test/kotlin/hs/kr/entrydsm/observability/adapterin/web/exception/GlobalExceptionHandlerTest.kt`:
- Around line 45-55: Update
keepsGenericResponseAndLogsCorrelationContextForUnhandledException to capture
logger.error output while invoking handleUnhandledException, then assert an
error event was emitted containing the correlation context
X-trace-Id=test-trace-id. Preserve the existing generic response assertions and
MDC cleanup.

In
`@systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisRateLimitAdapter.kt`:
- Around line 16-19: RedisRateLimitAdapter의 increment 및 expire 호출을 Lua 스크립트로 통합해
INCR 결과가 1일 때 EXPIRE가 동일한 원자적 작업으로 실행되도록 수정하세요. 기존 windowSeconds와 redisKey 값을
스크립트 인자로 전달하고, 실패 시 TTL 없는 키가 남지 않는 경로를 검증하는 테스트를 추가하세요.

In
`@systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisSessionStoreAdapter.kt`:
- Around line 41-44: RedisSessionStoreAdapter의 heartbeat()에서 저장된 FIELD_SERVICE를
먼저 조회해 요청 service와 다르면 실패시키고 기존 서비스 값이나 ZSET을 변경하지 않도록 하세요. 서비스가 일치할 때만
FIELD_LAST_HEARTBEAT_AT, TTL, touchWindow()를 갱신하고 저장된 서비스 기준으로 window를 처리하세요. 이
서비스 소속 변경 차단 동작을 검증하는 Kotlin 테스트를 추가하세요.
- Around line 83-86: Update the CONCURRENT_MAX_KEY handling in sampleConcurrency
to perform the read, comparison, and conditional write as one atomic Redis
operation, such as a Lua script, so concurrent calls cannot overwrite a larger
maximum with a smaller value. Preserve the existing behavior of storing only
when current exceeds the recorded maximum.
- Around line 37-45: Make the session lifecycle transition atomic across
RedisSessionStoreAdapter. In heartbeat(), conditionally update metadata, TTL,
and the activity window only if the session still exists; in leave(), atomically
verify and remove the session while preventing concurrent heartbeats or
duplicate leaves from recreating or double-counting it, using Lua or
WATCH/MULTI/EXEC. Add deterministic concurrency tests in the same subsystem
covering heartbeat-after-leave and two simultaneous leave requests; update the
anchor file at lines 37-45 and sibling location at lines 48-59 as required.

In `@systems/observability/observability-application/deps.bzl`:
- Around line 1-4: Remove the Spring `@Service` annotation from
SessionCollectionService and move its bean registration into the
observability-bootstrap configuration. Then remove
`@maven//`:org_springframework_boot_spring_boot_starter from the application
KOTLIN_DEPS, leaving the domain dependency intact.

In
`@systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionService.kt`:
- Around line 15-22: SessionCollectionService의 애플리케이션 계층 Spring 의존성을 제거하세요.
클래스에서 `@Service` 어노테이션과 org.springframework.stereotype.Service import를 삭제하고, 해당 빈
등록은 observability-bootstrap 설정에서 유지하세요.

In
`@systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionServiceTest.kt`:
- Around line 30-46: Add deterministic tests for the normal HEARTBEAT and LEAVE
flows in SessionCollectionServiceTest. Capture the session ID returned by ENTER,
assert HEARTBEAT succeeds for that ID, and add a state-transition test that
performs ENTER then LEAVE and verifies a subsequent HEARTBEAT fails with
SESSION_NOT_FOUND using a meaningful exception assertion.

In
`@systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/TestMain.kt`:
- Around line 3-8: TestMain.kt의 suite 선언에서 사용하는 SessionCollectionServiceTest를
hs.kr.entrydsm.observability.application 패키지에서 명시적으로 import하도록 추가하세요.

In
`@systems/observability/observability-bootstrap/src/main/kotlin/hs/kr/entrydsm/observability/config/ClockConfig.kt`:
- Around line 7-10: ClockConfig의 clock()이 UTC를 반환하는지 검증하는
observability-bootstrap 단위 테스트를 추가하세요. ClockConfig().clock().zone을 확인해 UTC와
일치하는지 단언하고, 해당 테스트가 같은 subsystem의 기존 테스트 관례를 따르도록 구성하세요.

In
`@systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/model/Session.kt`:
- Around line 12-16: Session의 새 동작을 검증하는 SessionTest를 추가하십시오. heartbeat 테스트에서는
service와 lastHeartbeatAt이 갱신되고 나머지 필드는 보존되는지 확인하십시오. isExpired 테스트에서는 만료 직전에는
false, 정확한 만료 시각에는 false, 만료 직후에는 true가 되는 경계 조건을 검증하십시오.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 5653b288-5d7f-47dd-972d-3b8d479af503

📥 Commits

Reviewing files that changed from the base of the PR and between 17bc952 and b0d675f.

⛔ Files ignored due to path filters (1)
  • kotlin.MODULE.bazel is excluded by none and included by none
📒 Files selected for processing (43)
  • systems/observability/observability-adapter-in/BUILD.bazel
  • systems/observability/observability-adapter-in/deps.bzl
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/common/ApiResponse.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/common/ErrorDetail.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/common/ErrorResponse.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/exception/GlobalExceptionHandler.kt
  • systems/observability/observability-adapter-in/src/test/kotlin/hs/kr/entrydsm/TestMain.kt
  • systems/observability/observability-adapter-in/src/test/kotlin/hs/kr/entrydsm/observability/adapterin/web/exception/GlobalExceptionHandlerTest.kt
  • systems/observability/observability-adapter-out/BUILD.bazel
  • systems/observability/observability-adapter-out/deps.bzl
  • systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisRateLimitAdapter.kt
  • systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisSessionStoreAdapter.kt
  • systems/observability/observability-application/BUILD.bazel
  • systems/observability/observability-application/deps.bzl
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionService.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/RecordSessionEventUseCase.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/SessionEventResult.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/RateLimitPort.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/SessionStorePort.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/TestMain.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionServiceTest.kt
  • systems/observability/observability-bootstrap/src/main/kotlin/hs/kr/entrydsm/observability/config/ClockConfig.kt
  • systems/observability/observability-domain/BUILD.bazel
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/DeviceType.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ErrorCode.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/LogLevel.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/LogSource.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/MetricType.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ReportFormat.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ReportStatus.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ServiceName.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ServiceStatus.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/SessionEventType.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/exception/MonitorDomainException.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/exception/MonitorException.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/model/Cursor.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/model/Session.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/DeviceTypeParser.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/HealthStatusClassifier.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/TestMain.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/CursorTest.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/DeviceTypeParserTest.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/HealthStatusClassifierTest.kt
📜 Review details
🧰 Additional context used
📓 Path-based instructions (8)
**/{BUILD.bazel,*.bzl}

📄 CodeRabbit inference engine (Custom checks)

In BUILD.bazel and .bzl files, require buildifier-compatible formatting and stable target naming

Files:

  • systems/observability/observability-application/deps.bzl
  • systems/observability/observability-adapter-in/deps.bzl
  • systems/observability/observability-adapter-out/deps.bzl
  • systems/observability/observability-adapter-in/BUILD.bazel
  • systems/observability/observability-adapter-out/BUILD.bazel
  • systems/observability/observability-domain/BUILD.bazel
  • systems/observability/observability-application/BUILD.bazel
**/*.bzl

⚙️ CodeRabbit configuration file

**/*.bzl: Apply Bazel Starlark (.bzl) style guidance.

Readability and docs:

  • Keep file/module docstrings and docstrings for public functions/macros.
  • Use descriptive parameter names and document attribute intent.

API design:

  • Macros should take a name argument and derive generated target names from it.
  • Prefer keyword arguments when calling macros for clarity and stability.
  • Keep macro side effects predictable and visible.

Encapsulation:

  • Use private visibility for helper targets created by macros unless explicitly public.
  • Avoid exposing internal implementation targets unintentionally.

Tooling:

  • Enforce buildifier formatting and lint compliance.

Files:

  • systems/observability/observability-application/deps.bzl
  • systems/observability/observability-adapter-in/deps.bzl
  • systems/observability/observability-adapter-out/deps.bzl
**/*.{kt,go}

📄 CodeRabbit inference engine (Custom checks)

If production logic is changed in Kotlin or Go files, require corresponding test updates in the same subsystem unless the PR description explicitly justifies why tests are unnecessary

Files:

  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/MetricType.kt
  • systems/observability/observability-bootstrap/src/main/kotlin/hs/kr/entrydsm/observability/config/ClockConfig.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/RateLimitPort.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/RecordSessionEventUseCase.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/TestMain.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ServiceStatus.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/common/ApiResponse.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ReportStatus.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/LogLevel.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/SessionEventResult.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ServiceName.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/CursorTest.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/model/Cursor.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/SessionEventType.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ReportFormat.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/LogSource.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/DeviceTypeParserTest.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/HealthStatusClassifier.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/exception/MonitorException.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/common/ErrorDetail.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/DeviceType.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/TestMain.kt
  • systems/observability/observability-adapter-in/src/test/kotlin/hs/kr/entrydsm/observability/adapterin/web/exception/GlobalExceptionHandlerTest.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionServiceTest.kt
  • systems/observability/observability-adapter-in/src/test/kotlin/hs/kr/entrydsm/TestMain.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ErrorCode.kt
  • systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisRateLimitAdapter.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/exception/MonitorDomainException.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/SessionStorePort.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/common/ErrorResponse.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/DeviceTypeParser.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionService.kt
  • systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisSessionStoreAdapter.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/HealthStatusClassifierTest.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/model/Session.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/exception/GlobalExceptionHandler.kt
**/*-domain/**/*.{java,kt,scala,groovy}

📄 CodeRabbit inference engine (Custom checks)

For files under *-domain modules, fail if imports reference adapter or bootstrap packages

Files:

  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/MetricType.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ServiceStatus.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ReportStatus.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/LogLevel.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ServiceName.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/CursorTest.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/model/Cursor.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/SessionEventType.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ReportFormat.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/LogSource.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/DeviceTypeParserTest.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/HealthStatusClassifier.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/exception/MonitorException.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/DeviceType.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/TestMain.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ErrorCode.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/exception/MonitorDomainException.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/DeviceTypeParser.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/HealthStatusClassifierTest.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/model/Session.kt
**/*.{java,kt,scala,groovy,go,js,ts,tsx,jsx,py,rb,rs,cpp,c,h,hpp,cs}

📄 CodeRabbit inference engine (Custom checks)

Flag TODO/FIXME comments introduced by this PR that do not include an issue reference in the form #123 or a full tracker key like PROJ-123

Files:

  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/MetricType.kt
  • systems/observability/observability-bootstrap/src/main/kotlin/hs/kr/entrydsm/observability/config/ClockConfig.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/RateLimitPort.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/RecordSessionEventUseCase.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/TestMain.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ServiceStatus.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/common/ApiResponse.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ReportStatus.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/LogLevel.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/SessionEventResult.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ServiceName.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/CursorTest.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/model/Cursor.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/SessionEventType.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ReportFormat.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/LogSource.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/DeviceTypeParserTest.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/HealthStatusClassifier.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/exception/MonitorException.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/common/ErrorDetail.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/DeviceType.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/TestMain.kt
  • systems/observability/observability-adapter-in/src/test/kotlin/hs/kr/entrydsm/observability/adapterin/web/exception/GlobalExceptionHandlerTest.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionServiceTest.kt
  • systems/observability/observability-adapter-in/src/test/kotlin/hs/kr/entrydsm/TestMain.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ErrorCode.kt
  • systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisRateLimitAdapter.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/exception/MonitorDomainException.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/SessionStorePort.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/common/ErrorResponse.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/DeviceTypeParser.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionService.kt
  • systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisSessionStoreAdapter.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/HealthStatusClassifierTest.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/model/Session.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/exception/GlobalExceptionHandler.kt
**/*.kt

⚙️ CodeRabbit configuration file

**/*.kt: Apply Kotlin Official Coding Conventions.

Formatting and structure:

  • Use 4 spaces for indentation; no tabs.
  • Keep files focused and readable; avoid horizontal alignment for spacing.
  • Place related declarations together and keep overloads adjacent.
  • Keep implementation member order stable and logical for readability.

Naming:

  • Package names are lowercase and do not use underscores.
  • Class/object names use UpperCamelCase.
  • Functions/properties/local variables use lowerCamelCase.
  • Constants use UPPER_SNAKE_CASE only for true constants.

API and null-safety:

  • Avoid platform type leakage in public APIs.
  • Use explicit types in public APIs when inference obscures meaning.
  • Prefer immutable values (val) over mutable values (var) unless mutation is required.
  • Flag nullable flows that can be replaced with safer modeling.

Imports and idioms:

  • Avoid wildcard imports unless justified by language/tooling conventions.
  • Prefer expression bodies for short, clear functions.
  • Prefer standard library idioms over custom utility wrappers when equivalent.

Architecture and tests:

  • Respect module boundaries (domain/application/adapter/bootstrap layering).
  • Highlight behavior-changing code that lacks corresponding unit/integration tests.
  • Ask for deterministic tests and meaningful assertions, not only happy-path checks.

Files:

  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/MetricType.kt
  • systems/observability/observability-bootstrap/src/main/kotlin/hs/kr/entrydsm/observability/config/ClockConfig.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/RateLimitPort.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/RecordSessionEventUseCase.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/TestMain.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ServiceStatus.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/common/ApiResponse.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ReportStatus.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/LogLevel.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/SessionEventResult.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ServiceName.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/CursorTest.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/model/Cursor.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/SessionEventType.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ReportFormat.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/LogSource.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/DeviceTypeParserTest.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/HealthStatusClassifier.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/exception/MonitorException.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/common/ErrorDetail.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/DeviceType.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/TestMain.kt
  • systems/observability/observability-adapter-in/src/test/kotlin/hs/kr/entrydsm/observability/adapterin/web/exception/GlobalExceptionHandlerTest.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionServiceTest.kt
  • systems/observability/observability-adapter-in/src/test/kotlin/hs/kr/entrydsm/TestMain.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ErrorCode.kt
  • systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisRateLimitAdapter.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/exception/MonitorDomainException.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/SessionStorePort.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/common/ErrorResponse.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/DeviceTypeParser.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionService.kt
  • systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisSessionStoreAdapter.kt
  • systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/HealthStatusClassifierTest.kt
  • systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/model/Session.kt
  • systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/exception/GlobalExceptionHandler.kt
**/*-application/**/*.{java,kt,scala,groovy}

📄 CodeRabbit inference engine (Custom checks)

For files under *-application modules, flag direct dependency on infrastructure-specific framework classes unless justified

Files:

  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/RateLimitPort.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/RecordSessionEventUseCase.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/TestMain.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/SessionEventResult.kt
  • systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionServiceTest.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/SessionStorePort.kt
  • systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionService.kt
**/BUILD.bazel

⚙️ CodeRabbit configuration file

**/BUILD.bazel: Apply Bazel BUILD style guidance.

Core rules:

  • BUILD formatting must match buildifier output.
  • Prefer DAMP BUILD files over over-abstracted DRY patterns.
  • Keep top-level layout clear: load() first, then package/default visibility, then targets.

Target definitions:

  • Keep deps explicit and close to each target's real direct dependencies.
  • Avoid recursive globs unless there is a clear, documented reason.
  • Avoid top-level list comprehensions for generating many targets.
  • Prefer literal labels and stable naming for readability and tooling compatibility.
  • Use boolean values (True/False), not numeric stand-ins.

Maintenance:

  • Flag duplicated target logic that should be moved into a macro.
  • Flag macro usage that hides important dependency or visibility decisions.

Files:

  • systems/observability/observability-adapter-in/BUILD.bazel
  • systems/observability/observability-adapter-out/BUILD.bazel
  • systems/observability/observability-domain/BUILD.bazel
  • systems/observability/observability-application/BUILD.bazel
🔇 Additional comments (35)
systems/observability/observability-adapter-in/deps.bzl (1)

1-11: LGTM!

systems/observability/observability-adapter-in/BUILD.bazel (1)

20-20: LGTM!

systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/common/ApiResponse.kt (1)

1-7: LGTM!

systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/common/ErrorDetail.kt (1)

1-17: LGTM!

systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/dto/common/ErrorResponse.kt (1)

1-9: LGTM!

systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/exception/GlobalExceptionHandler.kt (1)

22-62: LGTM!

systems/observability/observability-adapter-in/src/test/kotlin/hs/kr/entrydsm/TestMain.kt (1)

3-11: LGTM!

systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/DeviceType.kt (1)

1-8: LGTM!

systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ErrorCode.kt (1)

3-30: LGTM!

systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/LogLevel.kt (1)

1-6: LGTM!

systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/LogSource.kt (1)

1-8: LGTM!

systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/MetricType.kt (1)

1-6: LGTM!

systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/model/Cursor.kt (1)

7-31: LGTM!

systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/HealthStatusClassifier.kt (1)

5-28: LGTM!

systems/observability/observability-domain/BUILD.bazel (1)

20-20: LGTM!

systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/TestMain.kt (1)

3-12: LGTM!

systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/CursorTest.kt (1)

9-25: LGTM!

systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ReportFormat.kt (1)

1-6: LGTM!

systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ReportStatus.kt (1)

1-6: LGTM!

systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ServiceName.kt (1)

1-11: LGTM!

systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/ServiceStatus.kt (1)

1-7: LGTM!

systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/enum/SessionEventType.kt (1)

1-7: LGTM!

systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/exception/MonitorDomainException.kt (1)

1-7: LGTM!

systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/exception/MonitorException.kt (1)

1-10: LGTM!

systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/HealthStatusClassifierTest.kt (1)

8-56: LGTM!

systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/result/SessionEventResult.kt (1)

1-6: LGTM!

systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionService.kt (1)

24-65: LGTM!

systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/service/DeviceTypeParser.kt (1)

1-16: LGTM!

systems/observability/observability-domain/src/test/kotlin/hs/kr/entrydsm/observability/domain/DeviceTypeParserTest.kt (1)

8-29: LGTM!

systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/in/RecordSessionEventUseCase.kt (1)

1-15: LGTM!

systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/RateLimitPort.kt (1)

1-6: LGTM!

systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/SessionStorePort.kt (1)

1-30: LGTM!

systems/observability/observability-application/BUILD.bazel (1)

14-21: LGTM!

systems/observability/observability-adapter-out/deps.bzl (1)

1-11: LGTM!

systems/observability/observability-adapter-out/BUILD.bazel (1)

14-21: LGTM!

Comment on lines +45 to +55
fun keepsGenericResponseAndLogsCorrelationContextForUnhandledException() {
MDC.put("X-trace-Id", "test-trace-id")
try {
val response = handler.handleUnhandledException(IllegalStateException("internal detail"))

assertEquals(500, response.statusCode.value())
assertEquals("INTERNAL_SERVER_ERROR", response.body?.error?.code)
assertEquals(ErrorCode.INTERNAL_SERVER_ERROR.message, response.body?.error?.message)
} finally {
MDC.remove("X-trace-Id")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 3 --glob '*.kt' 'ListAppender|OutputCapture|MDC|LoggerFactory' systems/observability
rg -n -C 3 --glob 'BUILD.bazel' --glob '*.bzl' 'logback|slf4j|spring_boot_starter_test' systems/observability

Repository: EntryDSM/entrydsm-platform

Length of output: 8638


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- test outline ---'
ast-grep outline systems/observability/observability-adapter-in/src/test/kotlin/hs/kr/entrydsm/observability/adapterin/web/exception/GlobalExceptionHandlerTest.kt
printf '%s\n' '--- implementation ---'
cat -n systems/observability/observability-adapter-in/src/main/kotlin/hs/kr/entrydsm/observability/adapterin/web/exception/GlobalExceptionHandler.kt
printf '%s\n' '--- test ---'
cat -n systems/observability/observability-adapter-in/src/test/kotlin/hs/kr/entrydsm/observability/adapterin/web/exception/GlobalExceptionHandlerTest.kt
printf '%s\n' '--- module/build files ---'
fd -i 'BUILD|BUILD.bazel|deps.bzl|.*logback.*|.*logging.*' systems/observability
printf '%s\n' '--- relevant test patterns ---'
rg -n -C 4 --glob '*.kt' --glob '*.java' \
  'ListAppender|OutputCapture|Appender|ILoggingEvent|assert.*log|LoggingEvent|TestAppender|logCaptor|LogCaptor' .

Repository: EntryDSM/entrydsm-platform

Length of output: 6967


로그 상관관계를 단언하세요.

keepsGenericResponseAndLogsCorrelationContextForUnhandledException는 응답만 검증합니다. 로그 캡처 도구를 사용해 logger.error 이벤트와 X-trace-Id=test-trace-id를 단언하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@systems/observability/observability-adapter-in/src/test/kotlin/hs/kr/entrydsm/observability/adapterin/web/exception/GlobalExceptionHandlerTest.kt`
around lines 45 - 55, Update
keepsGenericResponseAndLogsCorrelationContextForUnhandledException to capture
logger.error output while invoking handleUnhandledException, then assert an
error event was emitted containing the correlation context
X-trace-Id=test-trace-id. Preserve the existing generic response assertions and
MDC cleanup.

Source: Path instructions

Comment on lines +16 to +19
val count = redis.opsForValue().increment(redisKey) ?: 1L
if (count == 1L) {
redis.expire(redisKey, Duration.ofSeconds(windowSeconds))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- candidate file ---'
ast-grep outline systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisRateLimitAdapter.kt
cat -n systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisRateLimitAdapter.kt

printf '%s\n' '--- related symbols and tests ---'
rg -n --glob '*.kt' 'RedisRateLimitAdapter|tryAcquire|RateLimit|expire\\(|opsForValue\\(\\)\\.increment' systems

Repository: EntryDSM/entrydsm-platform

Length of output: 1567


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- related symbols ---'
rg -n --glob '*.kt' 'RedisRateLimitAdapter|tryAcquire|RateLimit|expire\(|opsForValue\(\)\.increment' systems || true

printf '%s\n' '--- candidate-module files ---'
git ls-files 'systems/observability/observability-adapter-out' | sed -n '1,160p'

printf '%s\n' '--- build declarations mentioning Redis ---'
rg -n --glob 'build.gradle*' --glob '*.gradle.kts' --glob '*.gradle' 'redis|spring-data' . || true

printf '%s\n' '--- Redis adapter tests ---'
rg -n --glob '*Test.kt' --glob '*Tests.kt' 'RateLimit|RedisRateLimit|tryAcquire|ratelimit' . || true

Repository: EntryDSM/entrydsm-platform

Length of output: 7383


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- rate-limit callers and constants ---'
cat -n systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionService.kt
cat -n systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/RateLimitPort.kt
cat -n systems/observability/observability-adapter-out/BUILD.bazel
cat -n systems/observability/observability-adapter-out/src/test/kotlin/hs/kr/entrydsm/TestMain.kt

printf '%s\n' '--- read-only behavioral model of the two-command sequence ---'
python3 - <<'PY'
class RedisModel:
    def __init__(self):
        self.values = {}
        self.expiry = {}

    def incr(self, key):
        self.values[key] = self.values.get(key, 0) + 1
        return self.values[key]

    def expire(self, key, seconds):
        self.expiry[key] = seconds

def try_acquire_with_failure(redis, key, window_seconds, fail_before_expire=False):
    count = redis.incr(key)
    if count == 1:
        if fail_before_expire:
            raise ConnectionError("failure between INCR and EXPIRE")
        redis.expire(key, window_seconds)
    return count

redis = RedisModel()
try:
    try_acquire_with_failure(redis, "monitor:ratelimit:client:1", 60, True)
except ConnectionError:
    pass

key = "monitor:ratelimit:client:1"
assert redis.values[key] == 1
assert key not in redis.expiry
print("INCR committed while EXPIRE was skipped: key exists=%s, ttl=%s" %
      (key in redis.values, redis.expiry.get(key)))
PY

Repository: EntryDSM/entrydsm-platform

Length of output: 4943


🌐 Web query:

Redis INCR EXPIRE separate commands key without expiration Lua script atomic official documentation

💡 Result:

To atomically increment a key and set an expiration in Redis, you have two primary options: using the native INCREX command or a Lua script [1][2][3]. 1. Using INCREX (Recommended) The INCREX command is the preferred, native way to perform an atomic increment with an optional expiration [2][3]. It collapses the two-step pattern into a single O(1) command, ensuring atomicity and reducing round-trips [3]. Example usage: INCREX mykey BY 1 EX 60 2. Using a Lua Script If you are using an older version of Redis or have custom requirements, you can use a Lua script executed via the EVAL command [1][4]. Redis guarantees that scripts execute atomically, blocking all other server activities until the script completes [5][6]. The standard pattern to ensure the key is only expired on its first creation (or specific condition) is: local current = redis.call("incr", KEYS[1]) if current == 1 then redis.call("expire", KEYS[1], ARGV[1]) end return current You would call this via: EVAL "local current = redis.call('incr', KEYS[1]); if current == 1 then redis.call('expire', KEYS[1], ARGV[1]) end; return current" 1 mykey 60 Why not just use separate commands? Sending INCR and EXPIRE as separate commands is not atomic [1][4]. If a client fails between the two commands, the key will exist without an expiration, leading to potential "key leakage" where the key persists indefinitely [1][4]. While Redis Transactions (MULTI/EXEC) can group commands, they do not provide the ability to use the result of one command (e.g., the new counter value) to conditionally decide whether to execute the next (e.g., setting the expiration) within the same transaction [7][8]. Lua scripts or native commands like INCREX are necessary for such conditional logic [2][3][8].

Citations:


INCR과 TTL 설정을 원자적으로 실행하세요.

increment()expire()가 별도 명령이므로, 첫 INCR 후 실패하면 키가 TTL 없이 남습니다. Lua 스크립트에서 INCR 결과가 1일 때 EXPIRE를 함께 실행하세요. 실패 경로를 검증하는 테스트도 추가하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisRateLimitAdapter.kt`
around lines 16 - 19, RedisRateLimitAdapter의 increment 및 expire 호출을 Lua 스크립트로
통합해 INCR 결과가 1일 때 EXPIRE가 동일한 원자적 작업으로 실행되도록 수정하세요. 기존 windowSeconds와 redisKey
값을 스크립트 인자로 전달하고, 실패 시 TTL 없는 키가 남지 않는 경로를 검증하는 테스트를 추가하세요.

Comment on lines +37 to +45
override fun heartbeat(sessionId: String, service: ServiceName, now: Instant): Boolean {
if (!redis.hasKey(metaKey(sessionId))) return false
val nowMillis = now.toEpochMilli()
val hashOps = redis.opsForHash<String, String>()
hashOps.put(metaKey(sessionId), FIELD_SERVICE, service.name)
hashOps.put(metaKey(sessionId), FIELD_LAST_HEARTBEAT_AT, nowMillis.toString())
redis.expire(metaKey(sessionId), SESSION_TTL)
touchWindow(sessionId, service, nowMillis)
return true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e
file="systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisSessionStoreAdapter.kt"
printf '%s\n' '--- file outline ---'
ast-grep outline "$file" || true
printf '%s\n' '--- relevant source ---'
sed -n '1,220p' "$file"
printf '%s\n' '--- related symbols and tests ---'
rg -n --glob '*.kt' 'RedisSessionStoreAdapter|heartbeat\(|leave\(|touchWindow|SESSION_TTL|DURATION_SUM_KEY|DURATION_COUNT_KEY' systems

Repository: EntryDSM/entrydsm-platform

Length of output: 11466


🏁 Script executed:

#!/bin/bash
set -e
printf '%s\n' '--- adapter module files ---'
git ls-files 'systems/observability/observability-adapter-out'
printf '%s\n' '--- adapter tests and Redis configuration ---'
rg -n --glob '*.kt' --glob '*.gradle*' --glob '*.kts' \
  'RedisSessionStoreAdapter|StringRedisTemplate|RedisTemplate|`@DataRedisTest`|RedisConnectionFactory|observability-adapter-out' \
  systems build.gradle* settings.gradle* gradle* 2>/dev/null || true
printf '%s\n' '--- application call sites ---'
sed -n '1,130p' systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionService.kt
printf '%s\n' '--- port contract ---'
sed -n '1,100p' systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/port/out/SessionStorePort.kt

Repository: EntryDSM/entrydsm-platform

Length of output: 5557


🏁 Script executed:

#!/bin/bash
set -e
python3 - <<'PY'
from itertools import permutations

heartbeat = ["H.hasKey", "H.put(service)", "H.put(heartbeat)", "H.expire", "H.window"]
leave = ["L.entries", "L.duration", "L.delete", "L.removeWindows"]

bad = []
for order in permutations("H" * len(heartbeat) + "L" * len(leave)):
    hi = li = 0
    state = "present"
    recreated = False
    deleted_after_read = False
    for actor in order:
        if actor == "H":
            cmd = heartbeat[hi]
            hi += 1
            if cmd == "H.hasKey":
                heartbeat_seen = state == "present"
            elif cmd == "H.put(service)" and heartbeat_seen and state == "deleted":
                recreated = True
            elif cmd == "H.put(heartbeat)" and heartbeat_seen and state == "deleted":
                recreated = True
        else:
            cmd = leave[li]
            li += 1
            if cmd == "L.entries":
                leave_seen = state == "present"
            elif cmd == "L.delete" and leave_seen:
                state = "deleted"
        if recreated:
            bad.append(order)
            break

print("interleaving reproduces heartbeat-after-leave recreation:", bool(bad))
if bad:
    print("example:", " ".join(bad[0]))
PY

Repository: EntryDSM/entrydsm-platform

Length of output: 254


🏁 Script executed:

#!/bin/bash
set -e
python3 - <<'PY'
from itertools import permutations

# Model the command-level behavior in RedisSessionStoreAdapter.kt.
# A leave call reads the metadata before it increments duration and deletes the key.
commands = {
    "A": ["entries", "increment_sum", "increment_count", "delete", "remove_windows"],
    "B": ["entries", "increment_sum", "increment_count", "delete", "remove_windows"],
}
order = ["A"] * len(commands["A"]) + ["B"] * len(commands["B"])

duplicate_counted = False
example = None
for schedule in permutations(order):
    positions = {"A": 0, "B": 0}
    observed = {"A": False, "B": False}
    increments = 0

    for actor in schedule:
        command = commands[actor][positions[actor]]
        positions[actor] += 1

        if command == "entries":
            observed[actor] = True
        elif command == "increment_sum" and observed[actor]:
            increments += 1

    if increments == 2:
        duplicate_counted = True
        example = schedule
        break

print("two concurrent leave calls can both count one session:", duplicate_counted)
if example:
    print("example:", " ".join(example))
PY

Repository: EntryDSM/entrydsm-platform

Length of output: 253


🏁 Script executed:

#!/bin/bash
set -e
python3 - <<'PY'
from itertools import product

heartbeat = ["hasKey", "put_service", "put_heartbeat", "expire_meta", "window_updates"]
leave = ["entries", "duration_sum", "duration_count", "delete_meta", "window_removals"]

# Search all actor-level interleavings while preserving each method's command order.
def interleavings(a, b):
    if not a:
        yield b
    elif not b:
        yield a
    else:
        for rest in interleavings(a[1:], b):
            yield ["H"] + rest
        for rest in interleavings(a, b[1:]):
            yield ["L"] + rest

recreate = False
duplicate_leave_count = False
recreate_example = None
duplicate_example = None

for schedule in interleavings(heartbeat, leave):
    hi = li = 0
    meta_exists = True
    h_observed_exists = False
    l_observed_meta = False
    leave_duration_updates = 0
    heartbeat_recreated = False

    for actor in schedule:
        if actor == "H":
            command = heartbeat[hi]
            hi += 1
            if command == "hasKey":
                h_observed_exists = meta_exists
            elif command.startswith("put_") and h_observed_exists and not meta_exists:
                meta_exists = True
                heartbeat_recreated = True
        else:
            command = leave[li]
            li += 1
            if command == "entries":
                l_observed_meta = meta_exists
            elif command == "duration_sum" and l_observed_meta:
                leave_duration_updates += 1
            elif command == "delete_meta" and l_observed_meta:
                meta_exists = False

    if heartbeat_recreated and not recreate:
        recreate = True
        recreate_example = schedule
    if leave_duration_updates == 2:
        duplicate_leave_count = True
        duplicate_example = schedule

print("heartbeat can recreate metadata after leave:", recreate)
print("example:", " ".join(recreate_example or []))
print("two leave calls can both count one session:", duplicate_leave_count)
print("example:", " ".join(duplicate_example or []))
PY

Repository: EntryDSM/entrydsm-platform

Length of output: 274


🏁 Script executed:

#!/bin/bash
set -e
python3 - <<'PY'
def schedules(h, l, prefix=""):
    if h == 0:
        yield prefix + "L" * l
    elif l == 0:
        yield prefix + "H" * h
    else:
        yield from schedules(h - 1, l, prefix + "H")
        yield from schedules(h, l - 1, prefix + "L")

heartbeat = ["hasKey", "put_service", "put_heartbeat", "expire_meta", "window_updates"]
leave = ["entries", "duration_sum", "duration_count", "delete_meta", "window_removals"]

for schedule in schedules(len(heartbeat), len(leave)):
    hi = li = 0
    exists = True
    heartbeat_saw_exists = False
    leave_saw_meta = False
    recreated = False

    for actor in schedule:
        if actor == "H":
            command = heartbeat[hi]
            hi += 1
            if command == "hasKey":
                heartbeat_saw_exists = exists
            elif command.startswith("put_") and heartbeat_saw_exists and not exists:
                exists = True
                recreated = True
        else:
            command = leave[li]
            li += 1
            if command == "entries":
                leave_saw_meta = exists
            elif command == "delete_meta" and leave_saw_meta:
                exists = False

    if recreated:
        print("heartbeat can recreate metadata after leave: True")
        print("example:", schedule)
        break
else:
    print("heartbeat can recreate metadata after leave: False")

for schedule in schedules(len(leave), len(leave)):
    ai = bi = 0
    a_saw = b_saw = False
    duration_updates = 0

    for actor in schedule:
        if actor == "H":
            command = leave[ai]
            ai += 1
            if command == "entries":
                a_saw = True
            elif command == "duration_sum" and a_saw:
                duration_updates += 1
        else:
            command = leave[bi]
            bi += 1
            if command == "entries":
                b_saw = True
            elif command == "duration_sum" and b_saw:
                duration_updates += 1

    if duration_updates == 2:
        print("two leave calls can both count one session: True")
        print("example:", schedule)
        break
else:
    print("two leave calls can both count one session: False")
PY

Repository: EntryDSM/entrydsm-platform

Length of output: 303


세션 lifecycle 전환을 원자적으로 처리하세요.

heartbeat()leave()의 Redis 명령이 분리되어 있습니다. leave() 이후 heartbeat()가 메타데이터와 window 멤버를 다시 생성할 수 있습니다. 두 leave() 요청이 같은 메타데이터를 읽으면 세션 시간이 중복 집계될 수 있습니다.

heartbeat()leave()를 Lua 스크립트 또는 WATCH/MULTI/EXEC 경계에서 조건부로 처리하세요. 동일 서브시스템에 두 경쟁 조건을 검증하는 결정적 동시성 테스트도 추가하세요.

📍 Affects 1 file
  • systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisSessionStoreAdapter.kt#L37-L45 (this comment)
  • systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisSessionStoreAdapter.kt#L48-L59
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisSessionStoreAdapter.kt`
around lines 37 - 45, Make the session lifecycle transition atomic across
RedisSessionStoreAdapter. In heartbeat(), conditionally update metadata, TTL,
and the activity window only if the session still exists; in leave(), atomically
verify and remove the session while preventing concurrent heartbeats or
duplicate leaves from recreating or double-counting it, using Lua or
WATCH/MULTI/EXEC. Add deterministic concurrency tests in the same subsystem
covering heartbeat-after-leave and two simultaneous leave requests; update the
anchor file at lines 37-45 and sibling location at lines 48-59 as required.

Comment on lines +41 to +44
hashOps.put(metaKey(sessionId), FIELD_SERVICE, service.name)
hashOps.put(metaKey(sessionId), FIELD_LAST_HEARTBEAT_AT, nowMillis.toString())
redis.expire(metaKey(sessionId), SESSION_TTL)
touchWindow(sessionId, service, nowMillis)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- candidate files ---'
git ls-files | rg 'RedisSessionStoreAdapter|SessionStore|observability.*redis|redis' | head -200
printf '%s\n' '--- outline ---'
ast-grep outline systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisSessionStoreAdapter.kt
printf '%s\n' '--- implementation ---'
cat -n systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisSessionStoreAdapter.kt
printf '%s\n' '--- symbols/usages ---'
rg -n -C 3 'concurrentUsers|heartbeat\\(|touchWindow|FIELD_SERVICE|SESSION_TTL|RedisSessionStoreAdapter' systems/observability

Repository: EntryDSM/entrydsm-platform

Length of output: 7846


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- files ---'
git ls-files | rg 'RedisSessionStoreAdapter|SessionStore|observability.*redis|redis' | head -200
printf '%s\n' '--- implementation ---'
cat -n systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisSessionStoreAdapter.kt
printf '%s\n' '--- usages ---'
rg -n -C 3 'concurrentUsers|heartbeat|touchWindow|FIELD_SERVICE|SESSION_TTL|RedisSessionStoreAdapter' systems/observability

Repository: EntryDSM/entrydsm-platform

Length of output: 28979


🏁 Script executed:

#!/bin/bash
set -eu
cat -n systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisSessionStoreAdapter.kt
rg -n -C 5 'concurrentUsers|heartbeat|touchWindow|FIELD_SERVICE|SESSION_TTL|RedisSessionStoreAdapter' systems/observability

Repository: EntryDSM/entrydsm-platform

Length of output: 36364


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- adapter ---'
sed -n '1,180p' systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisSessionStoreAdapter.kt
printf '%s\n' '--- related test files ---'
git ls-files | rg '(^|/)(test|tests)/|Test\\.kt$|Spec\\.kt$' | rg 'observ|redis|session' | head -200
printf '%s\n' '--- all relevant declarations ---'
rg -n -C 8 'fun (heartbeat|leave|concurrentUsers|touchWindow)|FIELD_SERVICE|ZADD|zadd|zrem|remove.*window|concurrentUsers' .

Repository: EntryDSM/entrydsm-platform

Length of output: 39421


서비스 소속 변경을 차단하세요.

heartbeat()는 세션의 FIELD_SERVICE를 덮어쓰고 새 서비스 ZSET에만 추가합니다. 기존 서비스 ZSET의 멤버가 남아 동일 세션이 여러 서비스의 concurrentUsers()에 집계됩니다. 저장된 서비스와 요청 service가 다르면 heartbeat를 실패시키고, 일치할 때만 저장된 서비스 window를 갱신하세요. 이 동작을 검증하는 Kotlin 테스트도 추가하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisSessionStoreAdapter.kt`
around lines 41 - 44, RedisSessionStoreAdapter의 heartbeat()에서 저장된 FIELD_SERVICE를
먼저 조회해 요청 service와 다르면 실패시키고 기존 서비스 값이나 ZSET을 변경하지 않도록 하세요. 서비스가 일치할 때만
FIELD_LAST_HEARTBEAT_AT, TTL, touchWindow()를 갱신하고 저장된 서비스 기준으로 window를 처리하세요. 이
서비스 소속 변경 차단 동작을 검증하는 Kotlin 테스트를 추가하세요.

Comment on lines +83 to +86
val currentMax = redis.opsForValue().get(CONCURRENT_MAX_KEY)?.toIntOrNull() ?: 0
if (current > currentMax) {
redis.opsForValue().set(CONCURRENT_MAX_KEY, current.toString())
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -e
file='systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisSessionStoreAdapter.kt'
printf '%s\n' '--- outline ---'
ast-grep outline "$file" || true
printf '%s\n' '--- implementation ---'
cat -n "$file"
printf '%s\n' '--- related files ---'
rg -l --glob '*.kt' 'RedisSessionStoreAdapter|CONCURRENT_MAX_KEY|sampleConcurrency|concurrentMax' . | head -80
printf '%s\n' '--- Redis atomic API usage ---'
rg -n --glob '*.kt' 'increment\(|decrement\(|eval\(|compareAndSet|opsForValue\(\)\.(get|set)' . | head -160
printf '%s\n' '--- build dependencies ---'
rg -n 'spring-data-redis|lettuce|jedis|redis' --glob 'build.gradle*' --glob 'gradle.properties' --glob 'libs.versions.toml' --glob 'pom.xml' .

Repository: EntryDSM/entrydsm-platform

Length of output: 10613


🏁 Script executed:

#!/bin/bash
set -e
python3 - <<'PY'
from pathlib import Path

path = Path("systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisSessionStoreAdapter.kt")
source = path.read_text()

assert "val currentMax = redis.opsForValue().get(CONCURRENT_MAX_KEY)?.toIntOrNull() ?: 0" in source
assert "if (current > currentMax)" in source
assert 'redis.opsForValue().set(CONCURRENT_MAX_KEY, current.toString())' in source

# Model two concurrent sampleConcurrency calls:
# A observes 10, B observes 20, and both read the old Redis maximum 5.
initial_max = 5
a_current, b_current = 10, 20
a_observed, b_observed = initial_max, initial_max

# B completes its conditional SET first; A completes afterward.
redis_max = initial_max
if b_current > b_observed:
    redis_max = b_current
if a_current > a_observed:
    redis_max = a_current

assert redis_max == 10
assert redis_max != max(a_current, b_current)
print("The separate GET/conditional SET sequence can leave CONCURRENT_MAX_KEY at 10 instead of 20.")
PY

Repository: EntryDSM/entrydsm-platform

Length of output: 256


CONCURRENT_MAX_KEY 갱신을 원자적으로 처리하세요.

sampleConcurrency 호출이 같은 값을 읽으면 더 큰 값이 저장된 뒤 더 작은 값이 이를 덮어쓸 수 있습니다. 비교와 갱신을 Lua 스크립트 등 하나의 Redis 원자 작업으로 묶으세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@systems/observability/observability-adapter-out/src/main/kotlin/hs/kr/entrydsm/observability/adapterout/redis/RedisSessionStoreAdapter.kt`
around lines 83 - 86, Update the CONCURRENT_MAX_KEY handling in
sampleConcurrency to perform the read, comparison, and conditional write as one
atomic Redis operation, such as a Lua script, so concurrent calls cannot
overwrite a larger maximum with a smaller value. Preserve the existing behavior
of storing only when current exceeds the recorded maximum.

Comment on lines +15 to +22
import org.springframework.stereotype.Service

@Service
class SessionCollectionService(
private val sessionStorePort: SessionStorePort,
private val rateLimitPort: RateLimitPort,
private val clock: Clock,
) : RecordSessionEventUseCase {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 3 \
  'org\.springframework|`@Service`|`@Component`|`@Configuration`|`@Bean`' \
  systems/observability/observability-application/src/main/kotlin \
  systems/observability/observability-bootstrap/src/main/kotlin

Repository: EntryDSM/entrydsm-platform

Length of output: 4137


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- application and bootstrap files ---'
git ls-files 'systems/observability/observability-application/src/main/kotlin' \
  'systems/observability/observability-bootstrap/src/main/kotlin' | sort

printf '%s\n' '--- service references and bean declarations ---'
rg -n -C 4 \
  'SessionCollectionService|RecordSessionEventUseCase|`@Bean`|`@ComponentScan`|`@SpringBootApplication`' \
  systems/observability

printf '%s\n' '--- module dependencies ---'
fd -i -t f 'build.gradle|build.gradle.kts|pom.xml|settings.gradle|settings.gradle.kts' systems/observability \
  --exec sh -c 'echo "--- $1"; cat -n "$1"' sh {}

Repository: EntryDSM/entrydsm-platform

Length of output: 15328


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- relevant build files ---'
git ls-files | rg '(^|/)(build\.gradle(\.kts)?|settings\.gradle(\.kts)?|pom\.xml)$' | sort

printf '%s\n' '--- observability module configuration ---'
git ls-files systems/observability | rg '(^|/)(build\.gradle(\.kts)?|settings\.gradle(\.kts)?|pom\.xml)$' |
  while IFS= read -r file; do
    echo "--- $file"
    cat -n "$file"
  done

printf '%s\n' '--- application ExampleApplication ---'
cat -n systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/ExampleApplication.kt

printf '%s\n' '--- bootstrap Kotlin source ---'
git ls-files systems/observability/observability-bootstrap/src/main/kotlin |
  while IFS= read -r file; do
    echo "--- $file"
    cat -n "$file"
  done

Repository: EntryDSM/entrydsm-platform

Length of output: 193


애플리케이션 계층에서 Spring 의존성을 제거하세요.

SessionCollectionService에서 @Serviceorg.springframework.stereotype.Service import를 제거하세요. Bean 등록은 observability-bootstrap의 설정에서 처리하세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@systems/observability/observability-application/src/main/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionService.kt`
around lines 15 - 22, SessionCollectionService의 애플리케이션 계층 Spring 의존성을 제거하세요.
클래스에서 `@Service` 어노테이션과 org.springframework.stereotype.Service import를 삭제하고, 해당 빈
등록은 observability-bootstrap 설정에서 유지하세요.

Source: Coding guidelines

Comment on lines +30 to +46
@Test
fun heartbeatOnUnknownSessionThrowsSessionNotFound() {
val service = SessionCollectionService(sessionStore, FakeRateLimitPort(true), clock)

assertThrows(MonitorDomainException::class.java) {
service.record(SessionEventType.HEARTBEAT, "sess_unknown", ServiceName.APPLICATION, null, "127.0.0.1")
}
}

@Test
fun rateLimitExceededThrowsTooManyRequests() {
val service = SessionCollectionService(sessionStore, FakeRateLimitPort(false), clock)

assertThrows(MonitorDomainException::class.java) {
service.record(SessionEventType.ENTER, null, ServiceName.APPLICATION, null, "127.0.0.1")
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

HEARTBEAT 및 LEAVE 정상 흐름을 테스트하세요.

현재 테스트는 HEARTBEAT의 실패 경로만 검증합니다. LEAVE 경로는 검증하지 않습니다.

ENTER 후 반환된 session ID로 HEARTBEAT를 호출하는 테스트를 추가하세요. ENTER 후 LEAVE를 호출하고, 이후 HEARTBEAT가 SESSION_NOT_FOUND가 되는 상태 전이 테스트를 추가하세요.

As per coding guidelines, "If production logic is changed in Kotlin or Go files, require corresponding test updates in the same subsystem unless the PR description explicitly justifies why tests are unnecessary." As per path instructions, "Ask for deterministic tests and meaningful assertions, not only happy-path checks."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/observability/application/SessionCollectionServiceTest.kt`
around lines 30 - 46, Add deterministic tests for the normal HEARTBEAT and LEAVE
flows in SessionCollectionServiceTest. Capture the session ID returned by ENTER,
assert HEARTBEAT succeeds for that ID, and add a state-transition test that
performs ENTER then LEAVE and verifies a subsequent HEARTBEAT fails with
SESSION_NOT_FOUND using a meaningful exception assertion.

Sources: Coding guidelines, Path instructions

Comment on lines +3 to +8
import org.junit.runner.RunWith
import org.junit.runners.Suite

class ObservabilityApplicationModuleTest {
@Test
fun moduleLoads() {
assertTrue(true)
}
}
@RunWith(Suite::class)
@Suite.SuiteClasses(
SessionCollectionServiceTest::class,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

SessionCollectionServiceTest import를 추가하세요.

SessionCollectionServiceTesths.kr.entrydsm.observability.application 패키지에 있습니다. 이 파일은 hs.kr.entrydsm 패키지에 있습니다. Kotlin은 하위 패키지의 클래스를 자동으로 import하지 않습니다.

현재 suite 선언은 Unresolved reference: SessionCollectionServiceTest로 컴파일에 실패합니다.

수정 예시
+import hs.kr.entrydsm.observability.application.SessionCollectionServiceTest
 import org.junit.runner.RunWith
 import org.junit.runners.Suite
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import org.junit.runner.RunWith
import org.junit.runners.Suite
class ObservabilityApplicationModuleTest {
@Test
fun moduleLoads() {
assertTrue(true)
}
}
@RunWith(Suite::class)
@Suite.SuiteClasses(
SessionCollectionServiceTest::class,
import hs.kr.entrydsm.observability.application.SessionCollectionServiceTest
import org.junit.runner.RunWith
import org.junit.runners.Suite
`@RunWith`(Suite::class)
`@Suite.SuiteClasses`(
SessionCollectionServiceTest::class,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@systems/observability/observability-application/src/test/kotlin/hs/kr/entrydsm/TestMain.kt`
around lines 3 - 8, TestMain.kt의 suite 선언에서 사용하는 SessionCollectionServiceTest를
hs.kr.entrydsm.observability.application 패키지에서 명시적으로 import하도록 추가하세요.

Comment on lines +7 to +10
@Configuration(proxyBeanMethods = false)
class ClockConfig {
@Bean
fun clock(): Clock = Clock.systemUTC()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

UTC Clock bean의 테스트를 추가하세요.

이 변경은 생산 Kotlin 로직을 추가합니다. 제공된 변경에는 observability-bootstrap subsystem의 대응 테스트가 없습니다.

ClockConfig().clock().zone이 UTC인지 검증하는 단위 테스트를 추가하세요. 테스트가 불필요하면 PR 설명에 근거를 명시하세요.

As per coding guidelines, "If production logic is changed in Kotlin or Go files, require corresponding test updates in the same subsystem unless the PR description explicitly justifies why tests are unnecessary." As per path instructions, "Highlight behavior-changing code that lacks corresponding unit/integration tests."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@systems/observability/observability-bootstrap/src/main/kotlin/hs/kr/entrydsm/observability/config/ClockConfig.kt`
around lines 7 - 10, ClockConfig의 clock()이 UTC를 반환하는지 검증하는
observability-bootstrap 단위 테스트를 추가하세요. ClockConfig().clock().zone을 확인해 UTC와
일치하는지 단언하고, 해당 테스트가 같은 subsystem의 기존 테스트 관례를 따르도록 구성하세요.

Sources: Coding guidelines, Path instructions

Comment on lines +12 to +16
fun heartbeat(now: Instant, service: ServiceName): Session =
copy(service = service, lastHeartbeatAt = now)

fun isExpired(now: Instant, windowSeconds: Long): Boolean =
lastHeartbeatAt.plusSeconds(windowSeconds).isBefore(now)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Session 동작 테스트를 추가하십시오.

heartbeatisExpired는 새 production logic입니다. 제공된 suite에는 이 동작을 검증하는 SessionTest가 없습니다. heartbeat의 필드 보존 및 갱신을 검증하십시오. 만료 직전, 정확한 만료 시각, 만료 직후의 경계 조건을 검증하십시오.

As per coding guidelines, “If production logic is changed in Kotlin or Go files, require corresponding test updates in the same subsystem unless the PR description explicitly justifies why tests are unnecessary.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@systems/observability/observability-domain/src/main/kotlin/hs/kr/entrydsm/observability/domain/model/Session.kt`
around lines 12 - 16, Session의 새 동작을 검증하는 SessionTest를 추가하십시오. heartbeat 테스트에서는
service와 lastHeartbeatAt이 갱신되고 나머지 필드는 보존되는지 확인하십시오. isExpired 테스트에서는 만료 직전에는
false, 정확한 만료 시각에는 false, 만료 직후에는 true가 되는 경계 조건을 검증하십시오.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant