Files
document-haness/.agents/skills/writing-tech-log-records/references/ai-tells.md
T

6.8 KiB

AI가 쓴 티

문장 하나하나는 멀쩡한데 문서군 전체가 같은 리듬으로 굴러갈 때 티가 난다. 개별 문장을 일부러 어눌하게 만드는 방향은 역효과다. 아래는 실제 리뷰에서 지적된 것들이다.

억지 구어체를 만들지 않는다

AI 티를 지우려고 넣은 질문체와 청유형이 오히려 「AI 문장을 억지로 인간화한 것」으로 읽힌다. 제목은 명사구로 두고, 본문은 무엇을 봤는지로 시작한다.

억지로 사람처럼 그냥 제목
무엇이 서버로 책임 이전을 했지? 서버로 옮겨진 책임
AP2_SESSION은 언제 생기지? AP2_SESSION이 생성되는 시점
one-time handoff인가..? /token/access는 일회성 전달이 아니다
memory-only가 위험을 막아주나..? memory-only가 줄이는 위험
세 겹으로 나눠서 막아보자 세 개의 독립된 경계
그래서 이 패턴의 문제는 받은 헤더를 어떻게 믿지? upstream은 헤더의 출처를 구분할 수 없다

..?, ~하지?, ~해보자, ~하나?, 확인하자가 보이면 지운다.

결론을 먼저 정리하지 않는다

관측한 사실이 결론을 만들게 둔다. 정리된 대구 문장은 한 문서에 한 번이면 충분하다.

✗ BFF는 token을 브라우저에서 제거하는 대신 session과 CSRF 책임을 갖게 된다.

○ 브라우저 network에서 token endpoint 호출과 Authorization Bearer가 사라졌다.
  대신 `/bff/api/me` 요청에는 AP3_SESSION이 자동으로 붙었다.
  상태 변경 요청을 추가하면서 이 cookie 때문에 CSRF 검증이 필요해졌다.

Case는 튜토리얼이 아니라 사건의 순서를 따라간다.

처음 예상 → 실제 요청·코드에서 본 것 → 예상과 달랐던 지점 → 왜 그런지 → 확인한 범위

「처음에는 ~라고 봤다. 그런데 ~를 따라가 보니 ~였다」는 실제로 그렇게 생각한 기록이 있을 때만 쓴다. 없으면 지어낸 1인칭이다.

같은 문형을 문서마다 되풀이하지 않는다

한 문서군에서 아래 두 구조가 반복되면 그것 자체가 티다.

A를 얻는다. 대신 B를 내준다. 그래서 C를 해야 한다.
A와 B는 다르다. 둘을 나눠야 한다. 같은 이름으로 부르면 안 된다.

다 쓰고 나면 세어 본다. 대신·그래서·함께·그대로·따로·하게 된다·정해야가 문서마다 비슷한 횟수로 나오면 문형이 굳은 것이다.

grep -o '대신\|그래서\|함께\|그대로\|따로\|하게 된다' *.md | sort | uniq -c | sort -rn

길이를 고르게 맞추지 않는다

규칙 11개를 같은 길이로 쓰면 사람이 고른 것으로 읽히지 않는다. 중요한 규칙은 길게 쓰고 자명한 규칙은 한 줄로 끝낸다. 항목이 축으로 정리되는 내용이면 산문 대신 표 하나가 낫다.

endpoint | 브라우저가 접근 | credential | secret | 검증 주체

표로 정리한 뒤 특이사항만 문장으로 쓴다.

Question은 균형 잡힌 비교표가 아니다

선택지마다 「장점. 대신 단점.」을 똑같이 붙여 놓으면 아직 모르는 문제가 아니라 비교를 요청받고 답한 문서가 된다. 실제 설계 기록은 이렇게 생겼다.

지금 확인한 사실
지금 모르는 것
유력한 후보와 그 후보에서 확인할 항목
제외한 후보와 제외한 이유
무엇으로 결정할지

후보를 균등하게 나열하는 대신 지금 위치에서 한 단계 앞의 결정만 본다. 그리고 저장소 선택과 구조 변경처럼 층이 다른 선택지는 같은 목록에 넣지 않는다.

검증 전 결과를 결론으로 쓰지 않는다

Question이 재현하지 않은 일을 단정하면 답을 이미 아는 문서가 된다.

✗ 두 replica가 같은 refresh token으로 동시에 갱신하면 한쪽은 거부되게 된다.
○ 두 replica가 같은 refresh token으로 동시에 갱신할 수 있다. rotation 정책 때문에 두 번째
  사용이 거부될 가능성이 있고, 실제 응답과 session 영향은 아직 재현하지 않았다.

제약에 「이 전제는 바꾸지 않는다」고 써 놓고 그 전제를 바꾸는 선택지를 나란히 두지 않는다. 비교용으로 남기려면 「제약상 제외」로 따로 뺀다.

가짜 정량성을 만들지 않는다

측정할 수 없는 것을 숫자처럼 쓰지 않는다.

✗ 헤더 계약 수가 BFF 계약 수를 넘는 지점이 되돌릴 기준이다.
○ 전달하려는 claim이 계속 늘어나는가. role·tenant 변경이 즉시 반영돼야 하는가.
  정책이 애플리케이션 도메인을 알아야 하는가.

범위를 넓히는 단정을 쓰지 않는다

넓힌 것 좁힌 것
token이 memory 밖으로 나가는 유일한 구간 현재 SPA 코드에서 access token이 외부 요청으로 나가는 지점
짧은 수명이 사실상 유일한 방어 이 구성에는 denylist도 introspection도 없다. 그래서 노출 시간을 줄이는 주된 수단이 짧은 TTL이다
배포 한 번에 전원이 로그아웃된다 상태가 process-local이라 그 instance가 종료되면 그 instance가 들고 있던 session과 authorized client가 사라진다
JDBC는 컬럼 암호화 수단이 대부분 이미 갖춰져 있다 (환경마다 다르다. 확인한 것만 쓴다)

「~하면 ~을 우회할 수 있다」도 실제 구성에서 확인한 범위까지만 쓴다.

기록되지 않은 과거를 만들지 않는다

~하던 관행을 버리게 된다, 그동안 ~라고 불러 왔다. 그런 이력이 자료에 없으면 지운다.

Reference는 Case의 재설명이 아니다

같은 프로젝트의 Case를 문장만 바꿔 옮기면 규칙 수만 늘어난다. 다른 프로젝트에서 다시 적용할 수 있는 기준만 남기고, 사건은 Case에 두고 관계로 가리킨다. 규칙이 10개를 넘으면 축이 겹치는지 본다.

현재 검증과 운영 권고를 섞지 않는다

secret manager, network policy, mTLS처럼 지금 구성에 없는 것을 규칙에 그냥 적으면 Best Practice를 덧붙인 문서가 된다. 두 묶음으로 나눈다.

현재 확인한 것
운영에서 추가로 필요한 것

테스트가 무엇을 단정하는지 쓴다

✗ 이 요청이 200을 받는지 아닌지는 중요하지 않다.
○ 이 테스트의 assertion은 status code가 아니다. 정상 session을 함께 보냈으니 요청은 200이
  될 수 있다. 확인할 값은 응답의 `user`가 `spoofed-admin`으로 바뀌지 않았는지다.

설정 → 실제 요청 → 실제 status를 잇는 문장을 늘리고 일반론을 줄인다.