75 lines
5.0 KiB
Markdown
75 lines
5.0 KiB
Markdown
---
|
|
title: "병렬 comment→README subagent 가 런타임 문자열(exception/log/marker) 에서 tracking ID 까지 제거 — behavior change"
|
|
source_type: error-note
|
|
status: raw
|
|
tags: [parallel-subagents, refactoring, comment-cleanup, behavior-preserving, diff-audit, app-bootstrap, ca-skeleton]
|
|
created: 2026-06-19
|
|
---
|
|
|
|
# 병렬 comment→README subagent 의 런타임 문자열 over-strip
|
|
|
|
## Parent
|
|
|
|
- `[[raw/branch-notes/chore-app-bootstrap-comment-cleanup]]`
|
|
|
|
## 맥락
|
|
|
|
`app-bootstrap` 61파일의 결정-근거 주석을 README 로 이전하는 작업을, 패키지 그룹별 8개 general-purpose subagent 에 병렬 분산했다. 각 subagent 지시: **"주석/JavaDoc 만 수정. 코드·시그니처·애너테이션·import·field 명·logic 변경 금지."** 추적 ID(`D7`, `feature-…-contract`, `branch-note §`)는 코드에서 제거 대상으로 명시.
|
|
|
|
## 현상 — "주석"의 경계를 넘은 4건
|
|
|
|
subagent 들이 추적 ID 를 제거하면서, 주석이 아니라 **런타임 문자열 리터럴**에서도 ID 를 떼어냈다:
|
|
|
|
1. `FlywayProdSafetyValidator` — startup 예외 메시지
|
|
`"prod profile forbids these Flyway options (feature-migration-startup-contract D2/D4): " + violations …`
|
|
→ `"prod profile forbids these Flyway options: " + violations …` (`(…D2/D4)` 제거)
|
|
2. `SecretSourceValidator` — startup 예외 메시지
|
|
`"… empty secret is forbidden (feature-secrets-config-source-contract §테스트 계약)."`
|
|
→ `"… empty secret is forbidden."`
|
|
3. `OutboxLeaderElectionToken.STRATEGY_DESCRIPTION` — `private static final String` 상수
|
|
`"… SKIP LOCKED, I3/D8)"` → `"… SKIP LOCKED)"`
|
|
4. `MeteredDistributedLockPort` — `log.warn(...)` 메시지
|
|
`"… critical section (D6 efficiency-lock boundary)"` → `"… critical section"`
|
|
|
|
모두 컴파일은 통과하고, 해당 메시지를 assert 하는 테스트도 없었다(`grep` 으로 확인). 즉 **조용한 behavior change** — 컴파일/테스트로는 안 잡힌다. exception/log 메시지는 운영자-facing 출력이고, marker 상수는 `strategyDescription()` 반환값이라 관측 가능한 프로그램 상태다.
|
|
|
|
## 왜 위험한가
|
|
|
|
- "comment-only refactor" 라고 보고하면서 실제로는 런타임 출력을 바꾼다 → 리뷰어/사용자 신뢰 위반.
|
|
- 직전 모듈 선례(adapter-web commit 029e972)는 `ClientSafeErrorMessages` 의 string 값을 **이동만** 하고 값은 byte-identical 보존했다 → 팀 표준은 "문자열 값 불변".
|
|
- LLM 에이전트는 "주석"과 "주석처럼 생긴 문자열(괄호 안 ID 가 든 메시지)"을 자연스럽게 동일시한다. 지시에 "string literal/exception message/log message 도 보존"을 **명시하지 않으면** 넘어간다.
|
|
|
|
## 탐지 — non-comment changed-line diff audit
|
|
|
|
working-tree 에 무관한 사전 작업(`*Properties`→`*Settings` rename 등)이 섞여 있어 `git checkout` 류 통째 비교가 불가. 대신 diff 에서 **주석 마커로 시작하지 않는** 변경 라인만 추출:
|
|
|
|
```bash
|
|
git diff -- <module>/src/main/java | grep -E '^[+-]' | grep -vE '^(\+\+\+|---)' \
|
|
| grep -vE '^[+-][[:space:]]*(\*|//|/\*)' \
|
|
| grep -vE '^[+-][[:space:]]*\*/' \
|
|
| grep -vE '^[+-][[:space:]]*$'
|
|
```
|
|
|
|
잔여를 3분류:
|
|
- **(a) trailing inline 주석 변경** — `code; // old` → `code; // new`. `+`/`-` 의 `//` 앞 코드부가 동일하면 OK(주석만 바뀜).
|
|
- **(b) 사전 working-tree 변경** — rename/feature work. 이 작업 무관, OK.
|
|
- **(c) string 리터럴 값 변경** — `"…"` 안의 텍스트가 바뀜. ← **revert 대상**.
|
|
|
|
`"` 포함 변경 라인만 따로 좁히면 (c) 식별이 빨라진다. 단 배열 요소의 trailing 주석 제거(`"KEY", // note` → `"KEY",`)는 string 값 동일이므로 (a)로 분류(오탐 주의).
|
|
|
|
## 해결
|
|
|
|
(c) 4건을 각각 HEAD 원문으로 surgical `Edit` revert(주석 변경은 보존). revert 후 audit 재실행 → string-literal 변경 0, (a)(b)만 잔존 확인. `compileJava`/`compileTestJava` 재확인 BUILD SUCCESSFUL.
|
|
|
|
## 교훈 / 재발 방지
|
|
|
|
1. **subagent 지시에 명시**: "exception message·log message·`static final String` 상수 등 **런타임 문자열 리터럴은 byte-identical 보존**. 문자열 안의 tracking ID 도 건드리지 말 것 — 그건 주석이 아니라 프로그램 출력이다."
|
|
2. **완료 후 non-comment diff audit 을 항상 실행** (위 grep). comment-only 를 주장하려면 non-comment 변경이 0(또는 전부 사전 작업)임을 증명해야 한다.
|
|
3. **선례 확인**: 같은 캠페인의 직전 커밋이 string 값을 보존했는지 먼저 본다(`git show <prev> | grep '"'`). 팀 관례가 SSOT.
|
|
4. 런타임 문자열의 tracking ID 정리가 정말 필요하면 그건 **별도 작업**으로 분리하고 사용자 승인을 받는다(behavior change 이므로).
|
|
|
|
## 관련
|
|
|
|
- 같은 패턴 형제 cleanup: `[[raw/branch-notes/chore-adapter-persistence-rdbms-comment-cleanup]]`, `[[raw/branch-notes/chore-shared-contract-comment-noise-cleanup]]`
|
|
- `[[memory/proportional-orchestration]]` — 병렬 dispatch 는 규모에 비례, 단 audit 으로 over-reach 상쇄
|