The keycloak project ended with four open questions that design could not
settle. A two-VM lab was built to answer them by measurement, and this is
that material: 26 experiments, 125 raw command outputs, 22 browser captures.
Follows the import procedure in README.md.
source/ the originating repository verbatim — 78 documents, 28 SVGs,
8 manifests, plus .source-revision recording the commit
final/ the SSOT
document.md 729 lines written from the 29 experiment documents, not
concatenated: what was predicted, what was measured, and
where the measurement itself was wrong
evidence/raw 125 outputs, flattened to <experiment>__<file> because
the originals collided (01-baseline.txt appeared three
times) and the audit only globs the top level
evidence/meta one per raw file; command and exitCode are null and the
README says why rather than inventing them
evidence/browser 22 captures
assets/ three diagrams through techviz
.techviz/ their VizSpecs
A separate project rather than an addition to keycloak: the B-layer answers
that project's four questions, but the A, C and D layers are about cluster
failure, SSO and operations, and one document.md should hold one subject.
The four question records there can point here through 관계.
Recorded rather than papered over: only three of the 28 diagrams were
remade. The repository forbids hand-drawn SVG and forbids titles inside the
canvas; all 28 originals carry both, so converting them is redrawing, not
reformatting. They stay in source/ and the gap is written into the document.
verify-pipeline.py passes. audit-records.py reports no issues.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
415 KiB
schemaVersion, project, sourceDocument, sourceDocumentSha256, sourceRevision, generatedAt, candidateLedger, candidateLedgerSha256, sourceManifest, sourceManifestSha256
| schemaVersion | project | sourceDocument | sourceDocumentSha256 | sourceRevision | generatedAt | candidateLedger | candidateLedgerSha256 | sourceManifest | sourceManifestSha256 |
|---|---|---|---|---|---|---|---|---|---|
| 1 | clean-architecture-backend-template | final/document.md | 10eabd7a4fd2d20444effb966e2779175cb2a1b15c53a5a3cdf8a3e3e402b2f7 | 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 | 2026-08-31T00:00:00+09:00 | candidate-ledger.json | 73c05399007e4dfd79921c309bc697f1b15352716f00ea7e869063f683d27759 | root-tree-source-manifest.json | 2cf5391ab044fc0fe0413225d0cda19bef58640d52eb3369712fb0e8e1ce3b21 |
Root Tree
PROJECT clean-architecture-backend-template
분해 기준. Topic은 공학 문제 공간이고 디렉터리가 아니다. 한 Topic 안의 Case들은 서로 다른 메커니즘을 갖되 같은 판정 기준과 어휘를 공유한다. Reference는 그 Topic의 Case를 지우고도 남는 규칙만 올린다. 빈 브랜치는 채우지 않는다.
노드 수가 많은 이유는 분모가 크기 때문이다 — 62 leaf · main Java 4,614 파일 · 320,318 LOC · 모듈 문서 18,769줄.
analysis/05(persistence-jpa) 하나가 4,674줄이고 그 안에 P1급만 열 건이 넘는다.CONCEPT 브랜치에 대하여. Studio에 개념 카테고리가 추가됐고 작성 구조는 Case와 같다(본문
bodyMarkdown을 갖는다). 저장소의 계약 파일은 아직 이 종류를 기술하지 않는다 —record-kinds.md의 종류 고르기 흐름도에 없고,review-checklist.md는 "Case is a concrete event/verification sequence, not a concept article"로 배제만 한다. 그래서 이 트리는 다음 기준으로 나눈다.
- CASE — 재현하고 검증한 하나의 사건. "이때 이런 일이 일어났고 이렇게 확인했다."
- CONCEPT — 이 코드베이스가 구현한 메커니즘의 설명. 사건이 아니라 구조다. "이것이 무엇이고 왜 이 모양인가." 본문에 코드·상태도·표가 들어간다.
- REFERENCE — 그 메커니즘에서 뽑은 재사용 기준. 본문이 없고 규칙·적용조건·예외로 나뉜다.
셋의 관계는 보통 CONCEPT ← CASE → REFERENCE다. 사건이 개념을 필요로 하고, 개념에서 규칙이 나온다. 계약이 갱신되면 이 분류를 다시 본다.
TOPIC 커밋 모호성 — "모른다"를 결과로 유지하기 commit-ambiguity-as-a-result
├── CONCEPT
│ ├── 커밋 증거 단계 — NOT_STARTED에서 UNKNOWN까지
│ └── 트랜잭션 결과 대수 — 다섯 변형이 각각 답하는 질문
├── CASE
│ ├── pg_terminate_backend가 57P01로 도착하고 커밋 레코드는 이미 WAL에 있었다
│ ├── high-water mark가 "본 위치"를 뜻해서 재전달된 변경이 영구히 사라졌다
│ ├── 번역되지 않은 경합 예외가 재시도 코디네이터의 catch를 통째로 비껴갔다
│ └── 커밋 증거 프레임을 두 주인이 pop해서 바깥 트랜잭션의 실패가 익명이 됐다
├── REFERENCE
│ ├── 모르는 것은 성공도 실패도 아닌 세 번째 결과여야 한다
│ ├── 위험한 조합은 정책이 아니라 생성자가 거부하게 만든다
│ ├── 커밋 모호성 판정은 넓혀도 좁혀도 해롭다
│ └── 실패 번역 사슬의 순서는 계약이다
├── OPEN QUESTION
│ └── 커밋 모호성 계약 레인이 이 리비전에서 통과하는지 실행으로 확인되지 않았다
└── DECISION
├── completion-unknown은 자동으로도 수동으로도 재시도하지 않는다
├── 전송·업무·스트림 증거는 세 축이고 서로를 함의하지 않는다
└── 재시도 단위는 statement가 아니라 유스케이스 전체다
TOPIC 조립 소유권 — 통제와 그 의존을 같은 곳이 소유하기 assembly-ownership
├── CONCEPT
│ ├── Spring 조립의 세 경로와 각각이 결정하는 것
│ └── 조건부 빈의 평가 시점 — 파싱 시점과 등록 시점
├── CASE
│ ├── 스캔에서 뺀 다섯 패키지의 컴포넌트 여섯을 두 자동설정 어느 쪽도 소유하지 않았다
│ ├── outbox 체인 전체가 만족될 수 없는 @ConditionalOnBean 뒤에 있었다
│ ├── 관측을 필수 생성자 인자로 만든 수정을 조립이 6인자 생성자로 되돌렸다
│ ├── 이름만 AutoConfiguration이던 세 클래스가 capability 리포트에 Stable로 올라 있었다
│ ├── @ConditionalOnBean(DataSource.class)가 클래스 파싱 시점에 평가되어 여덟 빈이 사라졌다
│ ├── 넓은 스캔을 좁히자 여덟 컴포넌트에 아무것도 도달하지 않았다
│ ├── 시작 검증기 13개 규칙이 유일한 조립 지점에서 호출되지 않는다
│ └── subsystem 전체가 미배선인데 그것을 켜는 flag는 startup 검사를 수행한다
├── REFERENCE
│ ├── @Bean이 있다는 것은 조립 증거가 아니다
│ ├── @ConditionalOnBean은 조건이 만족될 수 있는지까지 확인해야 한다
│ ├── 시작 검증기가 도는지는 그 능력에 자동설정 루트가 있는지와 일치한다
│ ├── 조립 결함을 판정하려면 조립하는 쪽을 먼저 읽어야 한다
│ ├── "꺼짐"은 조건의 반복이 아니라 구조여야 한다
│ └── 프레임워크가 기여하는 자동설정까지 세지 않으면 스위치가 아니다
├── OPEN QUESTION
│ ├── 부팅 한 번으로 확증 가능한 두 건이 아직 정적 추론으로만 남아 있다
│ └── @ConditionalOnBean 사슬의 실제 평가 순서를 확인하지 않았다
└── DECISION
├── 마스터 스위치는 루트 하나가 소유하고 자식 설정은 조건을 갖지 않는다
└── 풀이 필요한지는 "JPA가 켜졌나"가 아니라 "커넥션이 필요한 capability가 있나"로 묻는다
TOPIC 게이트가 증명하지 않는 것 what-a-gate-does-not-prove
├── CONCEPT │ ├── strict test lane — 발견하지 못하면 실패하는 레인 │ └── 증거 등급과 provenance — R1과 R2를 가르는 것 ├── CASE │ ├── "certified"라 불리던 레인이 threshold를 하나도 비교하지 않고 있었다 │ ├── 문서 계약 테스트의 단언 경계 밖에 발견된 드리프트 세 건이 전부 있었다 │ ├── 릴리스 게이트가 읽는 증거를 아무도 생산하지 않는다 │ ├── 패키지 카탈로그가 트리보다 아홉 개 적어서 사이클이 통과했다 │ ├── 릴리스 레인이 매트릭스 세 버전 중 첫 번째만 돌리고 세 개를 커버로 기록했다 │ └── 다중 타깃 검증을 확인한다는 테스트가 다른 가드에 걸려 통과했다 ├── REFERENCE │ ├── 빠뜨림이 통과가 되는 게이트는 게이트가 아니다 │ ├── 문서와 상수가 서로 일치하는 것으로는 아무것도 증명되지 않는다 │ ├── 아무도 돌리지 않는 레인의 게이트는 마지막으로 돌린 사람이 본 것을 보고한다 │ ├── 계약 테스트는 어댑터가 실제로 돌리는 statement를 실행해야 한다 │ └── 아무것도 발견하지 못한 레인은 성공이 아니라 실패여야 한다 ├── OPEN QUESTION │ └── 컨테이너가 필요한 레인의 실제 결과를 실행으로 확인하지 않았다 └── DECISION ├── 인증 레인만 Docker 가드를 달지 않는다 ├── 성능 측정은 릴리스 게이트에 넣지 않는다 └── 후보 증거는 통과해도 R1에 머무르고 R2는 별도 게이트가 판정한다
TOPIC 타입으로 카디널리티와 개인정보를 막기 bounding-by-type
├── CONCEPT │ ├── 서명된 커서의 구조와 검증 순서 │ └── 카디널리티 경계를 타입으로 표현하기 ├── CASE │ ├── 시그니처가 payload를 받지 않는데 예외 메시지로 PII가 로그에 남았다 │ ├── 진단 리포트가 살아 있는 리소스를 담지 않도록 값 타입을 좁혔다 │ └── 커서 서명 검증이 길이·상수시간·순서를 전부 지켜야 했던 이유 ├── REFERENCE │ ├── 이름은 값이 아니라 registry key다 │ ├── 관측을 위해 수집한 데이터가 관측 대상보다 위험할 수 있다 │ ├── path·identifier는 등록하고 value는 바인딩한다 │ └── sanitize가 아니라 reject가 기본이다 ├── OPEN QUESTION │ └── (없음) └── DECISION ├── tenant id는 메트릭 태그가 되지 않는다 └── 커서에 서명하는 이유는 기밀성이 아니라 무결성이다
TOPIC 중복 장치 — 조립된 쪽이 약한 쪽일 때 duplicate-mechanisms
├── CASE
│ ├── 클라이언트가 준 엔드포인트가 SSRF 가드가 아니라 약한 private 사본을 지났다
│ ├── 요청 식별자를 클라이언트가 고를 수 없다는 정책이 뒤에 도는 필터에 뒤집혔다
│ ├── JPA 플랫폼 capability 대부분에 production 소비자가 없다
│ ├── forwarded 헤더 신뢰 판정이 Nginx에만 있고 Java 정책 421 LOC은 배선되지 않았다
│ └── 재시도 구현이 둘이고 정교한 쪽을 아무도 호출하지 않는다
├── REFERENCE
│ ├── 중복 장치를 찾으면 어느 쪽이 조립됐는지 먼저 확인한다
│ ├── Atomic* 타입의 존재는 원자성의 증거가 아니다
│ └── 같은 개념의 두 어휘가 공존하면 하나를 죽은 것으로 표시한다
├── OPEN QUESTION
│ └── (없음)
└── DECISION
└── 감사 메커니즘은 엔티티당 정확히 하나여야 한다
TOPIC 문서 드리프트의 방향 drift-direction
├── CASE │ ├── 지원 매트릭스가 코드와 반대를 적었고, 그 오해가 소비자에게 자기 멱등성을 생략하게 한다 │ ├── README의 활성화 recipe를 그대로 따르면 애플리케이션이 시작되지 않는다 │ ├── 출하 default 조합이 첫 write에서 예외를 던진다 │ ├── 문서가 UUIDv7이라 말하고 생성되는 것은 v4다 │ ├── 선택할 수 없는 브로커가 지원 매트릭스에 기능 목록과 함께 실려 있다 │ └── 다섯 문서가 "exactly 19 leaf"라고 적고 레지스트리는 62다 ├── REFERENCE │ ├── 과대 진술 문서를 과소보다 먼저 고친다 │ └── 문서의 수치는 세지 말고 파생하거나 게이트로 붙든다 ├── OPEN QUESTION │ └── (없음) └── DECISION ├── 문서 계약 테스트의 단언 범위를 capability 표까지 넓힐 것인가 └── 지원 등급은 추론이 아니라 선언이고 증거 없이는 올라가지 않는다
TOPIC 플랫폼 가족 사이의 학습 전이 learning-transfer-between-families
├── CASE │ ├── 두 번째 플랫폼이 첫 번째의 bridge 부재는 막고 게이트 배선은 옮기지 않았다 │ └── 같은 자격 증명 회전 결함이 한 가족에서 닫히고 다른 가족에서 재현됐다 ├── REFERENCE │ └── 레지스트리로 표현된 규칙은 전이되고 CI로 표현된 규칙은 전이되지 않는다 ├── OPEN QUESTION │ └── (없음) └── DECISION └── gRPC 플랫폼은 build-only로 두고 애플리케이션 도달 경로를 먼저 정한다
TOPIC owner-safe 상태 기계 — 소유권을 SQL에 적기 owner-safe-state-machines
├── CONCEPT
│ ├── fenced lease — 만료 시각만으로는 부족한 이유
│ ├── CAS 튜플과 update count가 답이 되는 구조
│ └── capability_schema_registry — 스키마 적용과 사용 승인의 분리
├── CASE
│ ├── lease가 만료 시각만 기록하고 소유자를 기록하지 않아 terminal state가 되돌려졌다
│ ├── transition digest가 "누가·언제"만 덮고 "무엇"을 덮지 않아 다른 전이를 같다고 보고했다
│ ├── 활성 트랜잭션 검사가 data source를 묻지 않아 다른 커넥션에서 커밋됐다
│ ├── 만료된 CLAIMED는 takeover하고 만료된 EXECUTING은 조정을 요구하도록 갈랐다
│ └── native claim이 @Version을 올리지 않아 충돌을 보고하지 않는 낙관적 잠금이 됐다
├── REFERENCE
│ ├── CAS 튜플을 where 절에 전부 반복하고 update count를 답으로 쓴다
│ ├── 시간은 DB에서, 그리고 행을 잠근 다음에 읽는다
│ ├── 만료된 claim과 만료된 실행은 다르게 다뤄야 한다
│ └── digest는 길이 프레이밍하고 버전을 붙인다
├── OPEN QUESTION
│ └── V2 상태 기계 넷의 컨테이너 레인이 실행되지 않았다
└── DECISION
├── capability는 스키마 적용과 사용 승인을 분리한다
└── 상태 기계 구현은 Spring stereotype을 갖지 않는다
TOPIC 트랜잭션 데드라인과 커넥션 예산 transaction-deadline-and-pool
├── CONCEPT
│ ├── 호출 예산에서 DB 로컬 타임아웃까지의 데드라인 전파
│ └── REQUIRES_NEW의 커넥션 비용과 풀 사이징 제약
├── CASE
│ ├── connection-timeout: 5s가 모든 prod 배포를 시작 실패시켰고 local만 통과했다
│ ├── validator가 요청을 서비스하지 않는 datasource를 검증하고 있었다
│ └── REQUIRES_NEW가 바깥 커넥션을 핀한 채 새 커넥션을 딴다
├── REFERENCE
│ ├── 데드라인은 호출 예산에서 시작해 세 단계로 좁힌다
│ ├── 쓰기 트랜잭션에는 유한 타임아웃이 필수다
│ └── 세션 스코프 설정은 풀로 돌아간 커넥션에 남는다
├── OPEN QUESTION
│ └── 풀 계약 레인이 실행되지 않아 포화 동작이 확인되지 않았다
└── DECISION
├── 트랜잭션 템플릿은 모드별로 미리 만들어 둔다
└── inRootWrite는 suspend하지 않고 fail-fast한다
TOPIC 스키마 소유권과 capability 스트림 schema-ownership-and-capability-streams
├── CONCEPT
│ └── 독립 Flyway 스트림과 baseline version 0
├── CASE
│ ├── 두 트리가 다 V1부터 번호를 매겨 공유 history가 하나를 건너뛸 수 있었다
│ ├── char(64)와 varchar(64) 불일치를 H2가 가리고 있었다
│ ├── 레지스트리 컬럼이 38자 경로에서 짧아 "더 짧은 경로를 적는" 우회를 유혹했다
│ ├── Flyway location customizer가 운영자가 바인딩한 값을 덮어썼다
│ └── messaging 마이그레이션 두 leaf가 같은 디렉터리에서 V2를 둘 만들었다
├── REFERENCE
│ ├── 마이그레이션 스트림은 자기 history 테이블을 갖는다
│ ├── 로컬이 다른 DB면 로컬 테스트는 다른 시스템에 대한 진술이다
│ └── 적용된 마이그레이션의 checksum은 그것을 돌린 모든 배포에 대한 약속이다
├── OPEN QUESTION
│ └── (없음)
└── DECISION
├── Flyway가 스키마를 소유하고 런타임 롤은 DDL 권한을 갖지 않는다
└── Repair는 모드가 아니라 운영자가 호출하는 작업이다
TOPIC Redis 명령 admission과 카탈로그 redis-command-admission
├── CONCEPT │ └── 명령 카탈로그와 admission 아홉 단계 ├── CASE │ ├── "build gate"라 불리는 catalog drift 검사가 어디에서도 실행되지 않는다 │ ├── 의미 어댑터 다섯이 gateway를 직접 불러 admission 아홉 단계를 건너뛴다 │ ├── NOSCRIPT 복구가 다섯 벌이고 넷은 스크립트 레지스트리를 지나지 않는다 │ └── startup probe가 production에서 한 번도 실행되지 않는다 ├── REFERENCE │ ├── 서버 메타데이터가 명령의 정의이고 정책 파일은 허용 범위다 │ └── 단일 admission point는 우회 경로를 세어야 성립한다 ├── OPEN QUESTION │ └── Redis 토폴로지 레인이 실행되지 않아 key spec 드리프트가 확인되지 않았다 └── DECISION └── 분류되지 않은 명령은 fail-closed로 거부한다
TOPIC HTTP 실패 분류와 재시도 안전성 http-failure-classification
├── CONCEPT
│ └── 전송 실패의 단계와 범주 — AttemptStage와 FailureCategory
├── CASE
│ ├── 영구 TLS 실패가 재시도 가능한 CONNECT로 분류된다
│ └── 로컬 거부 경로에서 회로 브레이커 permission이 반환되지 않는다
├── REFERENCE
│ ├── 원인 사슬은 가장 구체적인 분류가 이기도록 순회한다
│ └── 재시도 가능성은 멱등성과 실패 범주를 함께 봐야 정해진다
├── OPEN QUESTION
│ └── (없음)
└── DECISION
└── 재시도 안전성은 증거에 기반해 판정한다
TOPIC 파일 상태 기계와 물리 정리의 seam fileserver-state-and-fencing
├── CONCEPT │ └── 파일 상태 기계와 READY가 뜻하는 것 ├── CASE │ ├── scriptable 콘텐츠 탐지가 BOM·NUL·주석으로 우회된다 │ ├── cleanup이 읽은 lease와 삭제 사이에 writer가 그 lease를 얻을 수 있었다 │ ├── claim이 소유자·토큰·만료를 기록하지 않아 죽은 worker의 항목이 영영 남았다 │ └── README가 "노출된 setting도 bean도 없다"고 적은 능력에 production bean 여덟이 있다 ├── REFERENCE │ ├── 공개 읽기 가능한 상태는 완전한 identity를 DB 제약으로 요구한다 │ ├── 조건부 update로 행을 claim하고 읽은 값으로 판단하지 않는다 │ └── 접두사 시작 매칭은 시그니처에는 맞고 스니핑 패턴에는 맞지 않는다 ├── OPEN QUESTION │ └── (없음) └── DECISION └── 물리 경로와 원본 파일명을 저장하지 않는다
TOPIC 객체 스토리지의 단계적 수명주기와 권한 분리 objectstorage-staged-lifecycle
├── CONCEPT │ └── staged lifecycle과 서명된 grant ├── CASE │ ├── APPLY를 켜는 설정은 있고 승인을 검증하는 bean은 없다 │ ├── 직접 multipart의 마지막 part는 grant를 받을 수 없다 │ └── 서명된 grant의 endpoint 검증이 upload 경로에만 있다 ├── REFERENCE │ ├── 권한이 센 절반이 설정 한 줄로 켜지면 안 된다 │ └── 이진 승인 코덱은 decode 후 재인코딩이 원본과 같아야 한다 ├── OPEN QUESTION │ └── (없음) └── DECISION └── legacy 채택은 서로 다른 두 승인자의 서명을 요구한다
TOPIC 자기 공시 — 등급을 스스로 낮춰 적기 self-disclosure-grading
├── CONCEPT
│ └── 네 단계 공시 등급 — modelled에서 production-verified까지
├── CASE
│ ├── 등급표 13행 중 일곱을 스스로 강등하고 한 행만 관측과 어긋났다
│ ├── 시작 검증기가 커서 서명 키를 요구하는데 그 키로 서명하는 코드가 없다
│ └── build-only 등급이 90개 파일의 미조립을 오늘의 사고에서 면제한다
├── REFERENCE
│ ├── 등급은 네 단계로 나누고 관측보다 높게 적지 않는다
│ └── runtime_memberships를 먼저 읽고 심각도를 정한다
├── OPEN QUESTION
│ └── (없음)
└── DECISION
└── 능력 등급은 코드가 아니라 실행된 증거에서 파생한다
TOPIC 멀티테넌시 격리가 조용히 무력화되는 방법 multitenancy-isolation
├── CONCEPT
│ ├── RLS가 성립하기 위한 세 전제
│ └── 네 가지 멀티테넌시 전략과 각각의 격리 경계
├── CASE
│ ├── RLS가 아무것도 하지 않는 세 가지 방법
│ ├── search_path가 풀로 돌아간 커넥션에 남아 다음 tenant가 상속한다
│ └── tenant별 풀이 개별적으로 합리적이고 그 합이 서버 상한을 넘긴다
├── REFERENCE
│ ├── Hibernate filter는 보안 경계가 아니다
│ ├── tenant 컬럼이 있는 테이블의 모든 unique 제약에 그 컬럼이 들어가야 한다
│ └── 격리 설정은 트랜잭션 로컬이어야 한다
├── OPEN QUESTION
│ └── (없음)
└── DECISION
└── 클래스패스에 있는 것은 실행 동의가 아니다
TOPIC 계약 도메인과 경계값 — 타입·문법·상한이 실제 허용 범위와 맞는가 contract-domain-and-bounds
├── CASE
│ ├── response/LRO invariant enforcement boundary
│ ├── DomainContextKey same-name different-type collision
│ ├── bounded operational record identifiers
│ ├── encode가 발급한 2046~2048-byte cursor를 decode가 거부한다
│ ├── CapabilitySupport.constraints의 bounded/report-safe 계약이 타입에서 강제되지 않음
│ ├── canonical transaction boundary documentation과 실제 dual stack 불일치
│ ├── SpecificationPolicy는 Specification.unrestricted()를 bounded로 오인한다
│ ├── normalize는 canonical이 아닌 입력을 받아 다른 UUID로 조용히 바꾼다
│ ├── 다중 키 fan-in 중 HyperLogLog merge만 budget이 없다
│ ├── requireIdentifier의 다섯 검사 중 둘은 도달할 수 없다
│ ├── BoundedDataBufferFlux의 두 연산자가 이름만 있고 아무것도 하지 않는다
│ ├── "상한을 두고 읽는다"는 본문 핸들러가 전부 읽은 뒤에 자른다
│ ├── 의미 지문이 길이 프레이밍 없이 구분자로 만들어진다
│ ├── maxArrayElements가 선언만 되고 강제되지 않으며, 바이트 예산 백스톱도 없다
│ └── 기계가 읽는 능력 매니페스트와 사람이 읽는 등급표가 커서 서명에 대해 다르게 답한다
├── CONCEPT
│ └── (추가 없음)
├── REFERENCE
│ └── (추가 없음)
├── OPEN QUESTION
│ └── (추가 없음)
└── DECISION
└── (추가 없음)
TOPIC 런타임 도달성과 조립 — 구현된 능력이 실제 경로에 설치되는가 runtime-reachability-and-composition
├── CASE
│ ├── notification consumer는 diagnostic failure를 authoritative failure로 바꿀 수 있다
│ ├── notification fail-open consumer가 logger failure를 격리하지 않는다
│ ├── TransactionProfileRegistry는 declarative retry 제거 후 legacy residue 후보
│ ├── Stable runtime-role verification이 startup에서 실제 policy를 적용하지 않는다
│ ├── V8 atomic admin claim은 production service에 연결되지 않았고 completion 모델도 미완성이다
│ ├── vendor selector의 fail-fast 계약이 shipped composition에 설치돼 있지 않다
│ ├── change-streams=true는 거부되지 않고 조용히 버려지며, 그 결과 startup validator의 한 분기가 production에서 도달 불가다
│ ├── 서버 측 deadline이 경로마다 다르게 적용되고, 문서가 지목한 메커니즘은 production 호출자가 0이다
│ ├── changeStreams flag는 false로 고정돼 있는데, 소비자 bean은 그것과 무관하게 조립된다
│ ├── 프로파일의 TLS·타임아웃·풀·Stable API가 driver에 도달하지 않는다
│ ├── 모듈의 존재 논거인 UuidCodec에 production 소비자가 없다
│ ├── production 판정이 두 개의 리터럴 프로파일 이름에 걸려 있다
│ ├── SDK가 선언한 두 진입점에 구현이 없다
│ ├── 멱등 실행 계층과 durable-operation 표면이 픽스처에서만 조립된다
│ ├── 배선된 캐시 필터의 no-store가 배선된 조건부 읽기 경로를 무력화하고, 둘을 조정하려고 만든 패키지는 참조 0이다
│ ├── VirtualThreadProfile.propertyName()이 아무것도 게이트하지 않는 이름을 반환한다
│ ├── 이 leaf의 리액티브 절반 29개 파일은 어떤 출하 배포에서도 활성화될 수 없다
│ ├── SpringMvcRouteInventoryCollector 138줄에 참조가 하나도 없다
│ ├── WebPlatformStartupValidator가 시작 시 실행되지 않는다
│ ├── 설정 바인딩이 마스터 스위치 밖에서 일어난다. 컴포지션 루트의 자기 규칙과 어긋난다
│ ├── 스키마 조립·계약 정체성·해시 사슬이 통째로 미배선이고, 그것을 발행할 액추에이터 엔드포인트도 등록되지 않는다
│ ├── 5계층 예산 모델에서 요청 계층만 강제되고, 나머지 파생이 전부 미배선이다
│ ├── 연산 이름 정책의 두 구현 중 하나만 배선되고, 미배선 쪽만 GraphQlOperationNamePolicy를 쓴다
│ ├── 설정으로 정한 파서 한계가 graphql-java에 설치되지 않는다
│ ├── 프로파일별 정책 매니페스트가 미배선이라, 자격에서 해석된 프로파일이 아무 예산도 선택하지 않는다
│ ├── "기본 비활성"은 존재하지 않는 스위치의 기본값을 서술한다
│ ├── 출하되는 web 어댑터의 스위치가 활성화 모델 밖에 있다
│ ├── 8개 profile validator 중 조립에서 실행되는 것은 3개
│ ├── messaging-cloudevents는 출하 leaf이고 starter의 의존이며 소비자가 없다
│ ├── 접근 검사가 두 갈래로 존재하고, 조립된 쪽이 진단이 약한 쪽이다 (§8.3)
│ ├── claim-check는 starter에 배선 코드가 한 줄도 없다
│ ├── admin 스위치가 가드를 켜고 서비스는 켜지 않는다
│ └── 조립 경계가 정책 객체 9개를 만들고 서버를 만들지 않는다
├── CONCEPT
│ └── (추가 없음)
├── REFERENCE
│ └── (추가 없음)
├── OPEN QUESTION
│ └── (추가 없음)
└── DECISION
└── (추가 없음)
TOPIC 검증 경로 커버리지 — 초록불이 실제 production 경로를 검증하는가 verification-path-coverage
├── CASE
│ ├── cache-redis/httpclient의 support project dependency 필요성 재검증
│ ├── Stable completion-evidence capability가 shipped composition에 설치되지 않는다
│ ├── collection-fetch-pagination blocking release gate가 실제 위험을 증명하지 않는다
│ ├── nightly workflow가 광고하는 세 가지 중 하나를 lane이 실제로 관측하지 않는다
│ ├── selected base card jpa-flyway-migration의 producer가 현재 revision에서 실패한다
│ ├── selected base card 3개의 evidence tag가 production code 없는 fixture로 충족된다
│ ├── D3 gateway가 문서화한 검사 순서에 존재하지 않는 단계가 있다
│ ├── admin gateway의 두 audit 경로 중 하나만 fail-closed다
│ ├── sharding admin gateway의 네 작업 중 셋은 어떤 입력으로도 완료될 수 없다
│ ├── promotion 증거 어휘가 둘이고, gate는 하나만 검사한다
│ ├── 커버리지 gate 둘이 나란히 있고, 하나는 발화할 수 없다
│ ├── release gate가 실제로 차단하는 것은 hermetic test 3개이고, mongo용 CI workflow는 없다
│ ├── 소비자가 없는 fixture 셋
│ ├── R1과 R2의 설정 취급이 비대칭이고, 검증된 쪽은 하나뿐이다
│ ├── 문서가 지목한 기본값 위치와 test 목록이 실제와 다르다
│ ├── TransferBufferPool.maxBorrowedBytes()가 자기 회귀 test를 지목하는데 그 test가 읽지 않는다
│ ├── README readiness 표와 build.gradle 주석이 실제 소스와 어긋난다
│ ├── Pub/Sub 채널만 렌더 크기 검증을 받지 않는다
│ ├── 위반 코드 34종 중 22종이 어떤 test에서도 이름으로 확인되지 않는다
│ ├── 용량 보호 계층 전체(41 main files)가 자기 테스트 픽스처 안에서만 실행된다
│ ├── 크로스 스택 게이트가 검증하는 조립은 픽스처의 조립이고, 플랫폼의 조립이 아니다
│ ├── @oneOf 게이트와 런타임 검증기가 미배선이고, "플랫폼이 강제한다"는 서술이 그것을 넘어선다
│ ├── 파싱·검증 실패에 플랫폼 매퍼가 없다
│ ├── 실패는 환경 원인이며, 그 테스트의 도구 가드가 불완전하다
│ ├── messaging-reliability-api는 main 13파일 · 817 LOC에 테스트가 0개다
│ ├── 스키마 호환성 검증기는 출하 leaf에 있고, main 코드에서 호출되지 않는다
│ ├── messaging-admin-api는 main 25파일 · 1,613 LOC에 테스트 파일이 1개다
│ └── MessagingPublicSurfaceContractTest가 가족 밖(app-bootstrap)에 있다
├── CONCEPT
│ └── (추가 없음)
├── REFERENCE
│ └── (추가 없음)
├── OPEN QUESTION
│ └── (추가 없음)
└── DECISION
└── (추가 없음)
TOPIC 상태 소유권과 동시성 — claim·lease·replay·메모리 상태의 소유자를 지키기 state-ownership-and-concurrency
├── CASE
│ ├── notification admin atomic claim contract가 service에서 사용되지 않음
│ ├── notification derived idempotency key가 32-bit hash
│ ├── inspect()와 claim()이 만료된 COMPLETED row를 동시에 다른 상태로 해석한다
│ ├── complete()의 replay 판정이 replayTtl 변경을 무시한다
│ ├── persistent byte quota가 실제 admission에서 집행되지 않는다
│ ├── quota reclaim은 최대 64개 committed row만 처리하고 남은 byte를 조용히 버린다
│ ├── direct FileQuotaService.commit()은 만료 reservation을 commit한다
│ ├── recovery queue의 enqueue()는 concurrent upsert가 아니다
│ ├── cleanup crash-reclaim은 MAXIMUM_ATTEMPTS를 우회해 poison item을 무한 재시도할 수 있다
│ ├── provider 호출 뒤 recipient projection write가 lease fencing을 우회한다
│ ├── reconciliation FOR UPDATE SKIP LOCKED는 worker 처리 구간을 claim하지 않는다
│ ├── recordApplied는 문서화된 fence 계약을 구현하지 않고, 보호를 역전시킨다
│ ├── Flamingock lease로는 어떤 migration도 실행할 수 없고, javadoc은 다르게 적는다
│ ├── recovery package에 쓰이는 어휘와 쓰이지 않는 어휘가 나란히 있다
│ ├── nonce replay 경계가 결과를 읽고 버린다
│ ├── Number가 허용 목록에 있어 가변 숫자 타입이 REPLAYABLE로 인증된다
│ ├── GrpcAdmissionController.tryAdmit()의 동시성 경계가 동시성 아래에서 성립하지 않는다
│ ├── GrpcOutcomeReplay가 제거 경로 없는 인메모리 저장소다
│ └── GrpcCompletionReconciler가 요청 경로에서 동기화 없는 ArrayList를 변경한다
├── CONCEPT
│ └── (추가 없음)
├── REFERENCE
│ └── (추가 없음)
├── OPEN QUESTION
│ └── (추가 없음)
└── DECISION
└── (추가 없음)
TOPIC 스키마와 데이터 계약 — migration·index·collection 규칙의 실행 의미 schema-and-data-contracts
├── CASE
│ ├── messaging schema qualification boundary
│ ├── schema version 실패는 두 경로 중 어느 쪽도 온전하지 않다
│ ├── collection 이름 불변식이 aggregation executor의 서명에서 깨진다
│ ├── index diff가 실제로 비교하는 것은 두 필드뿐이다
│ ├── TTL이 두 곳에 선언되고, 규칙을 가진 쪽은 아무도 쓰지 않는다
│ ├── check에 붙은 verifyJsonSchemaRuntimeGraph가 실행되면 실패한다
│ └── 음수 Retry-After 헤더가 throttle 결과 대신 IllegalArgumentException을 만든다
├── CONCEPT
│ └── (추가 없음)
├── REFERENCE
│ └── (추가 없음)
├── OPEN QUESTION
│ └── (추가 없음)
└── DECISION
└── (추가 없음)
TOPIC 전송·프로바이더 의미론 — timeout·TLS·서명·실패 분류의 경계 transport-and-provider-semantics
├── CASE
│ ├── timeout 초과 경로가 한 observation에 success와 failure를 모두 기록한다
│ ├── 동적 대상 DNS 핀 능력 검사가 블로킹 오버로드에만 있다
│ ├── SigV4가 서명한 host에 포트가 없어, 기본 포트가 아닌 엔드포인트에서 서명이 어긋난다
│ ├── SigV4 서명 키 파생이 비밀을 지울 수 없는 String으로 승격시킨다
│ ├── FCM만 "커밋 후 응답 손실 = ambiguous" 규칙 밖에 있고, 그 FCM이 두 계약 집합 어디에도 없다
│ └── 공유 provider 계약이 8종 중 3종에서만 상속되고, 강제 장치가 없다
├── CONCEPT
│ └── (추가 없음)
├── REFERENCE
│ └── (추가 없음)
├── OPEN QUESTION
│ └── (추가 없음)
└── DECISION
└── (추가 없음)
TOPIC 보안 정책 강제 — 감사·PII·권한·SSRF 규칙이 실제 요청에 닿는가 security-policy-enforcement
├── CASE
│ ├── permission component grammar
│ ├── property-access IDENTITY entity가 batch guard를 우회한다
│ ├── "모든 reveal은 감사된다"고 선언한 AccessContext를 읽는 코드가 저장소에 하나도 없다
│ ├── publicPaths가 RestrictedPathRule보다 먼저 등록되어, 넓은 공개 경로 하나가 관리 평면 규칙을 조용히 덮는다
│ └── 연결 티켓·origin 정책·메시지 권한·연결 예산이 요청 경로 밖이고, 그중 일부는 STOMP 어댑터가 다른 방식으로 대체한다
├── CONCEPT
│ └── (추가 없음)
├── REFERENCE
│ └── (추가 없음)
├── OPEN QUESTION
│ └── (추가 없음)
└── DECISION
└── (추가 없음)
TOPIC 선언과 구현 드리프트 — 문서·매트릭스·설정 이름이 현재 코드와 같은가 declaration-and-document-drift
├── CASE
│ ├── support README가 current architecture registry/history와 drift
│ ├── RetryDecision.reason의 “bounded” 설명과 constructor contract 불일치
│ ├── 폐기된 namespace guard의 탐색 domain이 operator가 읽는 두 문서를 덮지 않는다
│ ├── 예외 계층의 "cause를 붙이지 않는다" 규칙에 문서화되지 않은 예외가 하나 있다
│ ├── CLAUDE.md의 의존성 서술이 세 항목 모두 틀렸다
│ ├── README의 세 가지 사실 오류
│ ├── CLAUDE.md가 대는 두 가드 중 하나는 저장소에 없다
│ ├── 그러나 R0 경계가 문서에만 있고 compile 경로에서 닫히지 않는다
│ ├── README의 jackson-databind 부재 주장이 현재 상태와 어긋난다
│ ├── 능력 프로퍼티 이름을 만드는 코드와 실제 게이트가 다른 접두사를 쓴다
│ ├── 지원 매트릭스가 "모든 messaging leaf는 build-only"라고 적고, 가족 권위 문서는 그 문장이 틀렸다고 이미 기록했다
│ ├── 한 아티팩트 안의 서로 모르는 Kafka 스택 두 개 (MSG-015, 가족 문서가 미해결로 표시)
│ ├── CompatibilityMatrix에 EXTENSION 등급이 있고 항목이 없으며, bridge leaf가 표 밖에 있다
│ └── 증거 등급 모델 전체가 자동 실행 경로 밖에 있고, CLAUDE.md는 현재 시제로 서술한다
├── CONCEPT
│ └── (추가 없음)
├── REFERENCE
│ └── (추가 없음)
├── OPEN QUESTION
│ └── (추가 없음)
└── DECISION
└── (추가 없음)
TOPIC 런타임 계약 정확성 — 개별 정책·가드·연산이 자기 계약을 지키는가 runtime-contract-correctness
├── CASE
│ ├── IdFactory.newId()의 “never-before-used” 문구 정밀화
│ ├── legacy storage/notification compatibility surface의 제거 조건 추적
│ ├── isolation vocabulary와 legacy routing capability의 시차
│ ├── application-supplied JpaRetryPolicy가 valid execution에서 무시된다
│ ├── query SQL naming/observability composition 부재
│ ├── export surface split SSOT
│ ├── MongoRegexPolicy.forbidden()은 금지하지 않는다
│ ├── 태그 allowlist는 규약이지 강제가 아니다
│ ├── 구현 없는 4개의 계약 중 셋은 그 사실을 적고, 하나는 적지 않는다
│ ├── 발행 rename만 경로 기반이고, 그것을 지키는 것은 이 모듈이 "근사에 불과하다"고 적은 사전검사다
│ ├── 패턴 구독의 R2 승인만 호출자가 아니라 배포에 대해 이루어진다
│ ├── permit 정책 이름이 세 곳에 문자열로 존재하고 교차 검사가 없다
│ ├── close()가 실패하면 drain 스케줄러 스레드가 남는다
│ ├── POOL_ROUTE_EXCEEDS_TOTAL 위반 코드는 발화할 수 없다
│ ├── AUTHENTICATION_FAILED를 지우지 않는다는 resumeHealthy의 보장이, 관리자 평면에 노출된 2단계 시퀀스로 우회된다
│ ├── Thymeleaf 예외 메시지 삭제 가드가 프로덕션이 타지 않는 오버로드에만 있다
│ ├── WebProblemSanitizer.alreadySafe가 죽은 메서드이고 그 안의 조건도 죽어 있다
│ ├── 플랫폼 요청 컨텍스트가 서블릿에는 생산자가 없고, 리액티브에는 익명 액터로 고정되어 있다
│ ├── 프레임워크 자유 신원 모델과 교차 테넌트 가드가 프로덕션에서 한 번도 참조되지 않는다
│ ├── 리액티브 전송에는 속도 제한 경로가 하나도 없다
│ ├── 선언된 Advanced 능력 11개 중 9개는 켜는 방법이 없다
│ ├── 원인 사슬 순회가 2-순환에서 무한 루프에 빠지고, 저장소는 이미 그 사례를 이름으로 적어 두었다
│ ├── capability 12개 중 main 코드가 읽는 것은 3개, 거부하는 것은 1개
│ ├── 호환성 게이트를 가진 두 포맷은 build-only이고, 출하되는 유일한 코덱에는 게이트가 없다
│ ├── 브로커 ACL 매니페스트의 자기 점검이 존재하지 않는다
│ ├── 저장소 어디에도 참조가 없는 타입 3개
│ ├── GrpcStreamAdmission도 같은 형태이고, per-caller 맵이 줄지 않는다
│ └── GrpcSerializedStreamWriter의 DROP_OLDEST가 잘못된 메시지의 바이트를 뺀다
├── CONCEPT
│ └── (추가 없음)
├── REFERENCE
│ └── (추가 없음)
├── OPEN QUESTION
│ └── (추가 없음)
└── DECISION
└── (추가 없음)
Node Specifications
각 노드는 계약(root-tree-contract.md)이 요구하는 필드를 갖는다. evidence가 "없음"인 노드는 그
이유를 함께 적는다 — 명령 출력이 아닌 근거(코드·javadoc·ADR)로 충분한 경우와, 실행이 필요한데
하지 못한 경우를 구별한다. 후자는 missing-verification에 남고 대응하는 Open Question을 갖는다.
TOPIC 1 — commit-ambiguity-as-a-result
CASE — pg_terminate_backend가 57P01로 도착하고 커밋 레코드는 이미 WAL에 있었다
- slug:
commit-ambiguity-is-not-only-sqlstate-08 - readiness:
READY - source:
final/document.md#4-1·analysis/05§3.5 - code:
.../persistence-jpa/.../transaction/CommitFailureClassifier.java·docs/jpa/repository-adaptation.md§4 - evidence: 없음 — 저장소가 기록한 컨테이너 레인 관측을 인용한다. 이 분석에서 재실행하지 않았다
- classification: 설계가 "커밋 모호성은 SQLSTATE class 08뿐"이라고 적었고, in-flight 커밋 중인 백엔드를
pg_terminate_backend로 끊었을 때57P01(admin_shutdown)이 도착하며 그 시점에 커밋 레코드가 이미 WAL에 있을 수 있음을 컨테이너 레인이 보였다. 규칙이57P01/57P02/57P03까지 넓어졌고CommitAmbiguityContractTest가 SQLSTATE를 직접 assert해 다시 좁아지지 못하게 고정했다. - missing-verification:
jpaPlatformFailureTest를 이 리비전에서 실행하지 않았다. 확인한 것은 규칙의 현재 코드 형태와 그 근거 문장이다. - relations:
reference:unknown-is-a-third-result·reference:ambiguity-rule-hurts-both-ways·decision:completion-unknown-is-never-retried·open-question:commit-ambiguity-lane-not-executed
CASE — high-water mark가 "본 위치"를 뜻해서 재전달된 변경이 영구히 사라졌다
- slug:
high-water-mark-swallowed-a-redelivered-change - readiness:
READY - source:
final/document.md#4-2·analysis/06§67 - code:
.../persistence-mongo/.../MongoChangeStreamPipeline.java:49,58-63·.../ReactiveMongoChangeStreamConsumer.java:199 - evidence:
evidence/raw/135a-mongo-change-stream-probe.java·evidence/raw/135-mongo-change-stream-probe.txt - classification: mark가 이벤트 수신 즉시 전진하므로 "완료된 위치"가 아니라 "본 위치"를 뜻하고, resume이 같은 pipeline 인스턴스를 재사용해 mark가 남는다. probe 세 개(A/B/C)가 worker 하나·평범한 failover에서 checkpoint 없이 지나간 이벤트를 다음 이벤트의 checkpoint가 추월하고 그 사이 재전달분을 pipeline이 삼키는 것을 관측했다. 구독은
RUNNING, runbook 비어 있음, caller의Flux는 정상 완료. 세 테스트가 각각 절반씩만 보고 "본 적 있지만 완료되지 않은 위치"라는 제3의 상태가 없다. - missing-verification: probe는 in-memory 구성이다. 실제 replica set failover에서 같은 순서가 재현되는지는 확인하지 않았다.
- relations:
reference:unknown-is-a-third-result·case:outbox-chain-behind-an-unsatisfiable-condition
CASE — 번역되지 않은 경합 예외가 재시도 코디네이터의 catch를 통째로 비껴갔다
- slug:
untranslated-contention-bypassed-the-retry-catch - readiness:
READY - source:
final/document.md#4-1·analysis/05§3.6 - code:
.../persistence-jpa/.../transaction/PersistenceFailureTranslatorChain.java·.../OptimisticConflictTranslator.java - evidence: 없음 — 저장소의 javadoc이 사후 기록으로 남긴 회귀다
- classification: 재시도 코디네이터가
JpaPersistenceException만 catch하는데 executor는 아무것도 번역하지 않고 템플릿을 돌렸다. 그래서 경합이 실제로 만들어내는 실패 — HibernateOptimisticLockException, SpringOptimisticLockingFailureException, raw 직렬화/데드락DataAccessException— 이 번역되지 않은 채 executor를 떠나 catch를 완전히 비껴갔고 production 경합은 재시도되지 않았다. 단위 픽스처는 이미 번역된 예외를 던져서 초록불이었다. - missing-verification: 현재 사슬이 네 단계 순서를 지키는지는 코드로 확인했으나, 경합을 실제로 일으켜 재시도를 관측하지 않았다.
- relations:
reference:translation-chain-order-is-a-contract·reference:unknown-is-a-third-result·case:a-certifying-lane-that-compared-nothing
CASE — 커밋 증거 프레임을 두 주인이 pop해서 바깥 트랜잭션의 실패가 익명이 됐다
- slug:
two-owners-popped-the-evidence-frame - readiness:
READY - source:
final/document.md#4-1·analysis/05§3.4 - code:
.../persistence-jpa/.../transaction/TransactionEvidenceContext.java·.../TransactionEvidenceScope.java - evidence: 없음 — javadoc의 사후 기록
- classification: 증거 프레임을 pop하는 주인이 둘이었다 — 트랜잭션 매니저가 commit/rollback에서, executor의
finally가 operation·attempt 일치 시. 바깥과 같은 operation·같은 attempt를 가진REQUIRES_NEW안쪽 트랜잭션(기본 경로에서는 둘 다 attempt 1이라 항상 그렇다)에서 안쪽 매니저가 안쪽을 pop하고 executor가 바깥을 자기 것으로 오인해 pop했다. 이후 바깥의 커밋 실패는 operation 없이, attempt 1로, reconciliation key 없이 보고됐다. 해결은 깊이로 소유권을 식별하고 자기 프레임이 top일 때만 pop하는 것이다. - missing-verification: 없음 — 수정된 형태와 그 테스트(
hasRawThreadLocalValue)를 코드로 확인했다. - relations:
reference:unknown-is-a-third-result·decision:retry-unit-is-the-use-case
REFERENCE — 모르는 것은 성공도 실패도 아닌 세 번째 결과여야 한다
- slug:
unknown-is-a-third-result - readiness:
READY - source:
final/document.md#9규칙 13 ·analysis/05§16 ·analysis/19§3.2 - classification: 완료 여부를 알 수 없는 상태를 성공이나 실패로 접으면 그 정보가 영원히 사라진다. 기준은 "결과 타입에 세 번째 변형이 있는가"이고, 그것이 있을 때만 caller가 재시도와 조정을 구별한다.
- scope: 커밋·발행·전달처럼 관측이 결과를 확정하지 못할 수 있는 모든 경계. 구현 예:
RetryDisposition.RECONCILE·TransactionResult.Indeterminate·WriteDisposition.UNDETERMINED·PublishCompletion.AMBIGUOUS·GrpcBusinessEvidence.COMMIT_UNKNOWN·ReplicaLagMonitor.replayedThrough(): Optional - exceptions: 아무것도 프로세스를 떠나지 않은 실패는 확정적이므로 세 번째 변형이 아니다. 남발하면 조정 큐가 커지고 운영자가 읽지 않고 비우게 된다.
- relations:
case:commit-ambiguity-is-not-only-sqlstate-08·case:high-water-mark-swallowed-a-redelivered-change·decision:completion-unknown-is-never-retried
REFERENCE — 위험한 조합은 정책이 아니라 생성자가 거부하게 만든다
- slug:
make-the-unsafe-state-unrepresentable - readiness:
READY - source:
final/document.md#9규칙 14 ·analysis/05§2.2 ·analysis/19§3.2 - classification: "이 조합은 하면 안 된다"를 리뷰 규칙이나 정책 코드로 두면 정책 버그가 그것을 만들 수 있다. 생성자가 거부하면 그 값이 존재할 수 없다.
- scope: 두 필드의 조합이 의미상 불가능한 모든 값 타입. 예:
JpaFailureContext가completionUnknown && retryable거부 + 예외가 안전 팩토리로 컨텍스트 재구성(이중 방어) ·PublishEvidence의 두 거부 ·GrpcExecutionEvidence가 "unary call with stream evidence" 거부 - exceptions: 생성자 검증은 값 타입에서만 싸다. 유효성이 다른 객체의 상태에 의존하면 조립 지점의 시작 검증기가 자리다.
- relations:
reference:unknown-is-a-third-result·decision:completion-unknown-is-never-retried·decision:three-axes-of-evidence
REFERENCE — 커밋 모호성 판정은 넓혀도 좁혀도 해롭다
- slug:
ambiguity-rule-hurts-both-ways - readiness:
READY - source:
analysis/05§3.5 ·docs/adr/ADR-JPA-003-completion-unknown.md - classification: 모호성 규칙을 좁게 두면 실제로 모호한 실패가 확정 롤백으로 오인돼 재시도된다. 넓게 두면 평범한 풀 고갈과 서버 재시작이 조정 큐로 밀려들고 운영자가 그 큐를 읽지 않고 비우는 습관을 배운다. 기준은 "이 SQLSTATE가 커밋 단계에서 발생했고 드라이버가 어느 쪽인지 말해주지 못하는가" 두 조건의 교집합이다.
- scope: 커밋 실패 분류를 갖는 모든 데이터 계층. 판정 재료는 관측된 트랜잭션 phase와 SQLSTATE·예외 타입 두 가지이며 어느 하나만으로는 부족하다.
- exceptions: 커넥션 유실이 커밋을 불명으로 남겼는지는 phase에 달렸으므로
08*을 그 자체로 completion-unknown으로 두면 안 된다 — 벤더 분류기가CONNECTION_UNAVAILABLE로만 두고 phase를 아는 커밋 분류기가 따로 판정하는 이유다. - relations:
case:commit-ambiguity-is-not-only-sqlstate-08·decision:completion-unknown-is-never-retried
REFERENCE — 실패 번역 사슬의 순서는 계약이다
- slug:
translation-chain-order-is-a-contract - readiness:
READY - source:
analysis/05§3.6 - classification: 번역 사슬은 (1) 이미 분류된 실패를 그대로 통과, (2) provider 고유 예외(SQLSTATE 없음), (3) 벤더 SQLSTATE, (4) 나머지는 손대지 않고 반환의 순서를 지켜야 한다. 재번역은 이미 들고 있는 attempt·key·completion 판정을 잃고, 무분류 포장은 프로그래밍 에러를 retryable로 보이게 한다.
- scope: 예외를 계층 간에 옮기는 모든 어댑터. 정책의 화이트리스트는 어떤 범주가 재시도될 수 있는지를 넓히지, 이 실패에 대한 분류기의 판정을 뒤집지 않는다.
- exceptions: 도메인 예외·assertion 실패·
NullPointerException은 퍼시스턴스 실패가 아니므로 번역 대상이 아니다. - relations:
case:untranslated-contention-bypassed-the-retry-catch·reference:unknown-is-a-third-result
OPEN QUESTION — 커밋 모호성 계약 레인이 이 리비전에서 통과하는지 실행으로 확인되지 않았다
- slug:
commit-ambiguity-lane-not-executed - readiness:
OPEN - source:
final/document.md#6-2·analysis/05§3.5, §15 - known:
CommitFailureClassifier의 현재 규칙 형태(40003·08*·57P0x· transport break 4종)를 코드로 확인 ·CommitAmbiguityContractTest가 SQLSTATE를 직접 assert함 · 해당 레인은 Docker 부재 시 skip이 아니라 실패한다 - unknown: 이 리비전에서 실제로 통과하는지 ·
57P01관측이 PostgreSQL 16/17/18 전부에서 재현되는지 - next-verification:
./gradlew :adapter:outbound:persistence-jpa:jpaPlatformFailureTest --console=plain, 매트릭스는-Pjpa.matrix.versions=16식으로 major당 한 번(다중 선택은 거부된다) - decision-criterion: 세 major 통과 시 이 질문을 닫고 해당 Case의
missing-verification을 지운다. 실패하면 그 실패가 새 Case가 된다 - relations:
case:commit-ambiguity-is-not-only-sqlstate-08·open-question:container-lanes-not-executed
DECISION — completion-unknown은 자동으로도 수동으로도 재시도하지 않는다
- slug:
completion-unknown-is-never-retried - readiness:
READY· decision-status:ADOPTED - source:
final/document.md#10-2·analysis/05§2.2, §3.5 - decision-evidence:
docs/adr/ADR-JPA-003-completion-unknown.md— Status: Accepted, Date: 2026-08-11, Design §17 - grounds:
case:commit-ambiguity-is-not-only-sqlstate-08·reference:unknown-is-a-third-result·reference:make-the-unsafe-state-unrepresentable - classification: ADR이
TransactionCompletionUnknownException을 자동으로도 수동으로도 재시도하지 않으며COMMITTING단계 관측 실패에 한해,40003·08*·transport break에 한해 생성한다고 명시한다. 근거는 "Retrying a possibly-committed write is the most damaging thing this platform could do... the failure is epistemic rather than transient"이고, 불변식을 정책이 아니라 타입 수준에서 강제한다. - relations:
case:commit-ambiguity-is-not-only-sqlstate-08·decision:three-axes-of-evidence·reference:ambiguity-rule-hurts-both-ways
DECISION — 전송·업무·스트림 증거는 세 축이고 서로를 함의하지 않는다
- slug:
three-axes-of-evidence - readiness:
READY· decision-status:ADOPTED - source:
final/document.md#10-5·analysis/20§2.4 - decision-evidence:
docs/adr/ADR-GRPC-003-three-axis-execution-evidence.md— Status: accepted, Date: 2026-08-30 - grounds:
reference:unknown-is-a-third-result·reference:make-the-unsafe-state-unrepresentable - classification: 상태 코드가 caller의 실제 질문에 대한 답이 아니라는 관찰에서 출발한다.
GrpcTransportEvidence가NOT_SENT와UNOBSERVED를 구별하고,GrpcBusinessEvidence가COMMIT_UNKNOWN을 실제 상태로 두며,GrpcExecutionEvidence가 관측 불가능한 조합을 거부한다. 응답 헤더를 확정 커밋으로 승격하는 것은 한 메서드를 고쳐야만 가능하다. - relations:
decision:completion-unknown-is-never-retried·reference:unknown-is-a-third-result
DECISION — 재시도 단위는 statement가 아니라 유스케이스 전체다
- slug:
retry-unit-is-the-use-case - readiness:
READY· decision-status:ADOPTED - source:
analysis/05§3.6 - decision-evidence:
docs/adr/ADR-JPA-002-full-transaction-retry.md·.../FullTransactionRetryCoordinator.java의 근거 javadoc - grounds:
case:untranslated-contention-bypassed-the-retry-catch·reference:unknown-is-a-third-result - classification: 낙관적 충돌·직렬화 실패에서 시도가 실패한 이유는 "계산의 기준이 된 상태가 더 이상 커밋된 상태가 아니라서"이므로, 같은 statement를 다시 쏘면 같은 틀린 답을 계산한다. executor에 다시 들어가면 새 트랜잭션과 새 Persistence Context를 받고 도메인 규칙이 reload된 데이터에 대해 다시 돈다. 예산은 attempt 수와 elapsed 두 상한을 모두 갖는다 — "타임아웃에 10초 걸리는 락에 3번 시도하면 30초짜리 호출이고 상류 호출자는 한참 전에 포기했다."
- relations:
case:a-retry-implementation-nobody-calls·reference:unknown-is-a-third-result
CONCEPT — 커밋 증거 단계 — NOT_STARTED에서 UNKNOWN까지
- slug:
commit-evidence-phases - readiness:
READY - source:
final/document.md#3-2·analysis/05§3.4, §2.3 - code:
.../persistence-jpa/.../api/transaction/TransactionCompletionEvidence.java·.../transaction/EvidenceAwareJpaTransactionManager.java·.../TransactionEvidenceContext.java - classification: 트랜잭션이 어디까지 갔는지를 여섯 단계로 기록하는 메커니즘의 설명이다.
NOT_STARTED → ACTIVE → COMMITTING → COMMITTED | ROLLED_BACK | UNKNOWN이고,COMMITTING단계에서 관측된 실패만 completion-unknown이 될 수 있다는 것이 전체 모델의 핵심이다.EvidenceAwareJpaTransactionManager가mark(COMMITTING)을 provider commit 직전에 찍는 이유(그 안에서 프로세스가 죽으면 마지막 기록이 "물어봤고 모른다"여야 한다), 그리고 컨텍스트가 단일 슬롯이 아니라ArrayDeque스택인 이유(REQUIRES_NEW가 같은 스레드에서 바깥을 suspend한다)를 함께 설명한다.ThreadLocal.withInitial을 쓰지 않는 이유도 이 개념의 일부다 — 읽을 때마다 값을 설치하면clear()가 방금 제거한 것을 다시 등록한다. - missing-verification: 없음 — 구조와 근거를 코드로 확인했다
- relations:
case:two-owners-popped-the-evidence-frame·case:commit-ambiguity-is-not-only-sqlstate-08·reference:unknown-is-a-third-result·decision:completion-unknown-is-never-retried
CONCEPT — 트랜잭션 결과 대수 — 다섯 변형이 각각 답하는 질문
- slug:
transaction-result-algebra - readiness:
READY - source:
final/document.md#3-2·analysis/05§3.2 - code:
.../persistence-jpa/.../api/transaction/TransactionResult.java·.../transaction/SpringPolicyTransactionPort.java - classification:
TransactionResult<T>가 sealed interface로 다섯 변형을 갖는 이유의 설명이다.Committed(물리 커밋 확인) ·Participating(바깥 트랜잭션에 참여 — 커밋을 주장하지 않는다) ·DeterminateRollback·Indeterminate(replay 권한을 주지 않는다) ·CommittedWithPostCommitFailure. 각 변형이 caller에게 허용하는 행동이 다르고, 특히Participating과Indeterminate는 boolean이나 예외로는 표현할 수 없는 상태다.PhaseSentinel이Ordered.HIGHEST_PRECEDENCE로 등록되어 물리 소유자일 때만 phase를 기록하는 구조도 함께 다룬다. - missing-verification: 없음
- relations:
concept:commit-evidence-phases·reference:unknown-is-a-third-result·reference:make-the-unsafe-state-unrepresentable
TOPIC 2 — assembly-ownership
CONCEPT — Spring 조립의 세 경로와 각각이 결정하는 것
- slug:
three-assembly-paths - readiness:
READY - source:
final/document.md#1-4,#7-1·analysis/18·analysis/14§7.1 - code:
.../app-bootstrap/.../CaSkeletonApplication.java· 8개AutoConfiguration.imports·.../app-bootstrap/src/main/resources/META-INF/spring.factories - evidence:
evidence/raw/tl-web-six-unowned-components.txt - classification: 이 저장소에서 빈이 컨텍스트에 들어오는 경로가 셋이고 각각 다른 질문에 답한다. 컴포넌트 스캔(
@ComponentScan+AUTO_CONFIGURED_PACKAGES제외 정규식) ·.imports(8개 파일 / 13개 클래스가 전부) ·spring.factories(EnvironmentPostProcessor 6 · SpringBootExceptionReporter · AutoConfigurationImportFilter · ApplicationListener). 조립 결함을 판정할 때 세 경로를 다 읽어야 하는 이유와, main Java 4,614개 중 스테레오타입 보유가 206개(4.5%)뿐이라 조립 표면이 매우 좁다는 사실을 함께 설명한다. - missing-verification: 없음 — 세 경로의 내용을 전수 판독했다
- relations:
case:scan-exclusion-without-an-owner·reference:read-the-assembling-side-first·reference:a-bean-is-not-composition-evidence
CONCEPT — 조건부 빈의 평가 시점 — 파싱 시점과 등록 시점
- slug:
when-conditions-are-evaluated - readiness:
READY - source:
analysis/05§14.4 - code:
.../app-bootstrap/.../persistencejpa/JpaPlatformRuntimeAutoConfiguration.java의 근거 javadoc ·.../PersistenceJpaRootAutoConfiguration.java의jpaResolvedDataSourceCheck - classification:
@ConditionalOnBean은 그 클래스가 언제 평가되는가에 따라 답이 달라진다.@AutoConfiguration으로 등록되면 다른 자동설정 이후에 평가되지만, plain@Configuration이@Import로 들어오면 클래스가 파싱되는 동안 — 대상 빈 정의가 존재하기 전에 평가된다. 이 저장소가 그 함정을 실제로 밟았고(여덟 빈이 조용히 사라짐), 회피 방법 두 가지를 남겼다 — 검증기를 주입받지 않고 직접 생성하기, 그리고 조건을 루트로 올리기. - missing-verification: 현재 리비전의 각 조건부 빈이 어느 시점에 평가되는지는
ConditionEvaluationReport로 확인하지 않았다 - relations:
case:conditionalonbean-evaluated-at-parse-time·reference:conditionalonbean-must-be-satisfiable·open-question:conditional-evaluation-order-unverified
CASE — 스캔에서 뺀 다섯 패키지의 컴포넌트 여섯을 두 자동설정 어느 쪽도 소유하지 않았다
- slug:
scan-exclusion-without-an-owner - readiness:
READY - source:
final/document.md#3-1·analysis/14§8.1, §51 - code:
.../CaSkeletonApplication.javaAUTO_CONFIGURED_PACKAGES·.../mvc/autoconfigure/WebMvcPlatformAutoConfiguration.java·.../webflux/autoconfigure/WebFluxPlatformAutoConfiguration.java - evidence:
evidence/raw/tl-web-six-unowned-components.txt·evidence/meta/tl-web-six-unowned-components.json·evidence/terminal/tl-web-six-unowned-components.svg - classification: 합성 루트가 다섯 web 패키지를 스캔에서 제외하고 근거를 "Ownership by auto-configuration is what ties a control's presence to its dependency's"로 적었다. 두 자동설정은 실제로 존재하고
.imports에 있고 출하 컨텍스트에 도달하는데, 등록하는@Bean13개·10개가 전부 협력자이고 제외된 패키지의 컴포넌트 여섯은 어느 쪽도 소유하지 않는다(전부 main 참조 0).ProblemCatalog와WebProblemFactory는 빈이고 그것을 쓰는@RestControllerAdvice는 빈이 아니다. - missing-verification:
/actuator/beans로 여섯 타입의 부재를 직접 확인하지 않았다 - relations:
concept:three-assembly-paths·reference:a-bean-is-not-composition-evidence·reference:read-the-assembling-side-first·case:observation-downgraded-by-the-composition
CASE — outbox 체인 전체가 만족될 수 없는 @ConditionalOnBean 뒤에 있었다
- slug:
outbox-chain-behind-an-unsatisfiable-condition - readiness:
READY - source:
final/document.md#4-3·analysis/19§7.1 - code:
.../messaging-spring-boot-starter/.../MessagingReliabilityAutoConfiguration.java·.../messaging-outbox-jdbc-postgresql/.../JdbcOutboxRepository.java·.../messaging-inbox-jdbc-postgresql/.../JdbcInboxRepository.java - evidence:
evidence/raw/tl-outbox-unsatisfiable-condition.txt·evidence/meta/…json·evidence/terminal/…svg - classification: 조건 사슬의 뿌리인 두 port의 유일한 구현이 main 참조 0이다 — 구현은 저장소 안에 있는데 만드는 자동설정이 없다. relay·worker·lifecycle·cleanup·transactional inbox handler 19개 파일 / 2,818 LOC가 조립되지 않고, Spring이 이것을 오류로 보고하지 않으므로 "꺼져 있음"과 "조립될 수 없음"이 런타임에서 구별되지 않는다. 같은 starter가
MessageCodecRegistry에는@ConditionalOnMissingBean기본 구현을 준다는 점이 비일관을 결함으로 만든다. - missing-verification:
ConditionEvaluationReport로 미충족 사유를 확인하지 않았다 - relations:
reference:conditionalonbean-must-be-satisfiable·concept:when-conditions-are-evaluated·open-question:one-boot-would-settle-two-findings
CASE — 관측을 필수 생성자 인자로 만든 수정을 조립이 6인자 생성자로 되돌렸다
- slug:
observation-downgraded-by-the-composition - readiness:
READY - source:
final/document.md#5-2·analysis/19§5.1 - code:
.../messaging-runtime-core/.../DefaultMessagePublisher.java(NO_OBSERVATION필드 javadoc) ·.../MessagingCoreAutoConfiguration.java:445 - evidence:
evidence/raw/tl-messaging-observation-noop.txt·evidence/meta/…json·evidence/terminal/…svg - classification: 관측을 선택적 데코레이터가 아니라 필수 생성자 인자로 만든 수정이 runtime-core에 있고 그 javadoc이 "an unobserved publish path is how 'the dashboards were empty during the incident' happens"로 이유를 적는다. 자동설정은 6인자 생성자를 골라
NO_OBSERVATION(다섯 메서드 전부 빈 본문)을 주입하고, 방출자 넷은 main 참조 0이며 등록되는 것은 협력자 둘뿐이다. 같은 경로가 예외 메시지를 의도적으로 버리므로 둘이 합쳐지면 진단 흔적이 남지 않는다. - missing-verification: 부팅 후
/actuator/metrics에messaging.*시리즈 부재를 확인하지 않았다 - relations:
reference:a-bean-is-not-composition-evidence·case:scan-exclusion-without-an-owner·open-question:one-boot-would-settle-two-findings
CASE — 이름만 AutoConfiguration이던 세 클래스가 capability 리포트에 Stable로 올라 있었다
- slug:
autoconfiguration-in-name-only - readiness:
READY - source:
analysis/05§14.4 - code:
.../app-bootstrap/.../persistencejpa/JpaPlatformRuntimeAutoConfiguration.java의 javadoc - evidence: 없음 — javadoc의 사후 기록
- classification: 세 클래스가
...AutoConfiguration으로 이름 붙었고 plain factory였다 —@AutoConfiguration도,@Bean도,.imports엔트리도 없었고 합성 루트는 그 패키지를 스캔에서 제외한다. 그래서 capability 리포트는 transaction retry·completion evidence·observability를 Stable로 나열했고 돌고 있는 컨텍스트에는 그중 아무것도 없었다. 개발자가 재시도되지 않는 재시도에 의존하는 코드를 배포할 수 있었다. - missing-verification: 없음 — 수정 후 형태를 코드로 확인했다
- relations:
reference:a-bean-is-not-composition-evidence·case:a-retry-implementation-nobody-calls·decision:capability-grade-is-declared-not-inferred
CASE — @ConditionalOnBean(DataSource.class)가 클래스 파싱 시점에 평가되어 여덟 빈이 사라졌다
- slug:
conditionalonbean-evaluated-at-parse-time - readiness:
READY - source:
analysis/05§14.4 - code:
.../persistencejpa/JpaPlatformRuntimeAutoConfiguration.java·.../PersistenceJpaRootAutoConfiguration.java - evidence: 없음 — javadoc의 사후 기록
- classification: 이 클래스는 루트가 import하지 auto-configure하지 않으므로, 그 조건이 클래스 파싱 중 — datasource 빈 정의가 존재하기 전에 — 평가됐고 따라서 모든 실제 배포에서 false였다. 아래 여덟 빈이 조용히 사라졌고 아무것도 그중 어느 것에도 의존하지 않아 아무것도 보고하지 않았다. datasource validator가 caller에 배선되고 Compose 레인이 "No qualifying bean"이라고 답했을 때에야 드러났다. 같은 함정을 피하려고 루트의 검사가 validator를 주입받지 않고 직접 생성한다.
- missing-verification: 현재 리비전에서 재발하지 않는지
ConditionEvaluationReport로 확인하지 않았다 - relations:
concept:when-conditions-are-evaluated·open-question:conditional-evaluation-order-unverified
CASE — 넓은 스캔을 좁히자 여덟 컴포넌트에 아무것도 도달하지 않았다
- slug:
narrowing-the-scan-orphaned-eight-components - readiness:
READY - source:
analysis/05§14.2 - code:
.../persistence-jpa/.../config/JpaAdapterComponentsConfig.java의 javadoc - evidence: 없음 — javadoc의 사후 기록
- classification: 합성 루트의 스캔이 persistence 트리를 정규식으로 제외했고 그 제외는 옳다 — 그것이 optional capability를 optional하게 만든다. 빠진 것은 나머지 절반이다. 이 leaf의 여덟 클래스가 scanned component로 쓰여 있는데(
SpringTransactionPort·PersistenceExceptionTranslator·StandardSqlStateErrorMapping·DomainContextAuditContextPort·idempotency store와 reaper·outbox store와 reaper) 넓은 스캔이 멈추자 아무것도 도달하지 않았다. 특히TransactionPort는 구현이 전혀 없어서 트랜잭션을 여는 모든 유스케이스가 열 포트를 갖지 못했고, 단위 테스트는 각 클래스를 직접 생성하므로 볼 수 있는 것이 없었다. - missing-verification: 없음 — 수정된
@ComponentScan대상 6개를 코드로 확인했다 - relations:
concept:three-assembly-paths·reference:read-the-assembling-side-first·reference:off-must-be-structural
CASE — 시작 검증기 13개 규칙이 유일한 조립 지점에서 호출되지 않는다
- slug:
thirteen-startup-rules-never-run - readiness:
READY - source:
final/document.md#5-3·analysis/20§3.1 - code:
.../grpc-spring-boot-starter/.../GrpcPlatformStartupValidator.java(188줄) ·.../GrpcPlatformAutoConfiguration.java(106줄,@Bean9) - evidence:
evidence/raw/267-grpc-family-reachability.txt - classification: validator가 5개 그룹 13개 규칙을 갖고(transport·security 4 / executor 2 / methods 4 / channels 2 / advanced isolation 1) javadoc이 그 13개를 고른 기준을 "None of them fails a smoke test"로 적는다. 유일한 조립 지점인 자동설정은
@Bean9개를 만들면서 이 validator를 부르지 않고, static 메서드라 빈이 될 수도 없다. CLAUDE.md가 인용한 "streaming method가 Stable catalog에 등록되면 startup을 거부한다"와 §2.2의 runtime 강제가 둘 다 이 validator를 통해서만 성립하므로 둘 다 실행되지 않는다. - missing-verification: build-only 가족이라 부팅 확인이 불가능하다 — 채택 시점에만 관측 가능
- relations:
reference:the-startup-validator-follows-the-autoconfiguration-root·reference:a-bean-is-not-composition-evidence·case:a-release-gate-with-no-evidence-producer
CASE — subsystem 전체가 미배선인데 그것을 켜는 flag는 startup 검사를 수행한다
- slug:
a-flag-that-validates-an-unwired-subsystem - readiness:
READY - source:
final/document.md#4-2·analysis/06§49 - code:
.../persistence-mongo/.../MongoRootAutoConfiguration.java및 그 조건 - evidence: 없음 — 도달성 전수 확인
- classification: mongo leaf(497 파일 / main 351)가 배선돼 있지 않은데, 그것을 켜는 flag는 startup 검사를 수행한다. 즉 운영자가 스위치를 켜면 검사가 돌고 통과하지만 그 뒤에 아무 능력도 조립되지 않는다 — "켰고 검사도 통과했다"가 "작동한다"로 읽히는 조합이다.
- missing-verification: 부팅으로 빈 목록을 확인하지 않았다
- relations:
reference:off-must-be-structural·reference:a-bean-is-not-composition-evidence·case:mongo-default-throws-on-first-write
REFERENCE — @Bean이 있다는 것은 조립 증거가 아니다
- slug:
a-bean-is-not-composition-evidence - readiness:
READY - source:
final/document.md#9규칙 2·3 ·analysis/99§2 - classification: 타입이 로드된다는 것은 jar가 클래스패스에 있다는 뜻이고,
@Bean이 있다는 것은 그 값이 컨텍스트에 있다는 뜻이다. 둘 다 그것이 실행 경로에 놓였다는 뜻이 아니다. 기준은 "그 빈을 주입받아 호출하는 코드가 있는가"다. - scope: 프레임워크가 조립하는 모든 통제 — 검증기·인터셉터·필터·어드바이스·정책 객체. 판정은 선언 파일을 제외한 main 참조 수를 세는 것이다.
- exceptions: build-only leaf의 공개 API 표면은 예외다(채택자가 부를 타입). 구분 기준은 "가족 내부의 다른 코드가 불러야 하는 조립·기계 타입인가".
@ConfigurationProperties처럼 프레임워크가 소비하는 타입도 참조 0이 정상이다. - relations:
case:scan-exclusion-without-an-owner·case:observation-downgraded-by-the-composition·case:thirteen-startup-rules-never-run·reference:conditionalonbean-must-be-satisfiable
REFERENCE — @ConditionalOnBean은 조건이 만족될 수 있는지까지 확인해야 한다
- slug:
conditionalonbean-must-be-satisfiable - readiness:
READY - source:
final/document.md#9규칙 4 ·analysis/19§7.1 - classification: 조건이 만족되지 않으면 조용히 아무것도 만들어지지 않고 프레임워크는 이것을 오류로 보고하지 않는다. 기준은 두 질문이다 — "X의 구현이 저장소 안에 있는가", "그것을 만드는 자동설정이 있는가". 사슬이면 뿌리까지 따라간다.
- scope: 조건부 조립을 쓰는 모든 지점. 평가 시점 문제와 뿌리 부재 문제가 같은 증상을 내므로
ConditionEvaluationReport가 둘을 가른다. - exceptions: 애플리케이션이 직접 등록하기를 기대하는 확장점이면 정당하다. 다만 그때는 저장소가 그 port의 구현을 제공하지 않거나, 제공한다면
@ConditionalOnMissingBean기본값으로 함께 등록하는 것이 일관된다. - relations:
case:outbox-chain-behind-an-unsatisfiable-condition·concept:when-conditions-are-evaluated·open-question:conditional-evaluation-order-unverified
REFERENCE — 시작 검증기가 도는지는 그 능력에 자동설정 루트가 있는지와 일치한다
- slug:
the-startup-validator-follows-the-autoconfiguration-root - readiness:
READY - source:
final/document.md#5-3·analysis/18(SRC-158) - classification: 루트가 있으면 검증기를 매달 자리가 있고, 없으면 검증기는 컴포넌트 스캔이 닿기를 기대하는데 그 스캔이 그 패키지를 제외하고 있을 수 있다. 기준은 "이 검증기를 부르는 조립 지점이 어디인가"를 능력 단위로 묻는 것이다.
- scope: 자동설정과 컴포넌트 스캔을 함께 쓰는 조합. 확인 결과: app-bootstrap 12종 배선(고아 0), messaging 6종 배선, web·websocket 미배선.
- exceptions: 루트가 있는데도 부르지 않는 경우가 둘 있다(
WebPlatformStartupValidator,GrpcPlatformStartupValidator). 필요조건이지 충분조건이 아니다. - relations:
case:thirteen-startup-rules-never-run·case:scan-exclusion-without-an-owner·reference:a-bean-is-not-composition-evidence
REFERENCE — 조립 결함을 판정하려면 조립하는 쪽을 먼저 읽어야 한다
- slug:
read-the-assembling-side-first - readiness:
READY - source:
final/document.md#13항목 5 ·analysis/99§8 - classification: 통제가 작동하지 않는다는 판정은 그 통제의 코드만 읽어서는 내릴 수 없다. 조립하는 쪽을 먼저 읽어야 원인이 "루트 없음" / "루트는 있는데 소유하지 않음" / "조건 만족 불가"로 갈리고, 세 경우의 조치가 다르다.
- scope: 프레임워크 조립을 쓰는 모든 분석. 순서는 (1) 레지스트리의 runtime membership, (2)
.imports·spring.factories전량, (3) 합성 루트의 스캔 경계와 import filter, (4) 그 다음 개별 통제. - exceptions: 없음. 이 분석의 정정 네 건이 전부 이 순서를 어겨서 생겼다.
- relations:
concept:three-assembly-paths·case:scan-exclusion-without-an-owner·reference:a-bean-is-not-composition-evidence
REFERENCE — "꺼짐"은 조건의 반복이 아니라 구조여야 한다
- slug:
off-must-be-structural - readiness:
READY - source:
final/document.md#10-3·analysis/05§14.1 ·analysis/19§6.1 - classification: 능력이 꺼졌다는 것은 빈이 없고, 소켓·풀·스레드가 없고, 설정이 바인딩되지 않고, 스키마 기대치도 없다는 뜻이어야 한다. 조건을 빈마다 반복하면 다음 달에 추가된 빈이 게이트 밖에 남는다. 기준은 "조건이 몇 곳에 적혀 있는가"이고 답이 하나여야 한다.
- scope: optional capability를 갖는 모든 조립. 구현 형태는 루트 하나가 조건을 갖고 자식을
@Import하는 것이다. - exceptions: 기본 켜짐인 능력(
matchIfMissing=true)은 이 모델에 그대로 들어가지 않는다 — web의 네 스위치가 그 경우이고, 그것을 활성화 모델에 넣으려면 조립 소유권을 먼저 정해야 한다. - relations:
case:narrowing-the-scan-orphaned-eight-components·case:a-flag-that-validates-an-unwired-subsystem·decision:one-root-owns-the-master-switch
REFERENCE — 프레임워크가 기여하는 자동설정까지 세지 않으면 스위치가 아니다
- slug:
count-the-frameworks-own-autoconfigurations - readiness:
READY - source:
analysis/05§14.1 - classification: 프로젝트 자신의 설정만 조건화하는 것으로는 부족하다 — starter가 프레임워크의 import metadata를 통해 자기 것을 기여하므로, 평범한
@EnableAutoConfiguration애플리케이션은 프로젝트 조건이 무엇이라 하든 풀을 열고 마이그레이션을 돌린다. 기준은 "이 능력이 꺼졌을 때 프레임워크가 여전히 무엇을 만드는가"다. - scope: starter를 클래스패스에 두는 모든 optional capability. 이 저장소의 형태는
AutoConfigurationImportFilter가 10종을 막는 것이다. - exceptions: 필터 목록의 오타는 조용히 fail-open한다(매치하지 않을 뿐). 그래서 테스트가 필터의 반환값이 아니라 빈 부재로 assert해야 한다.
- relations:
case:narrowing-the-scan-orphaned-eight-components·reference:off-must-be-structural·decision:pool-need-is-a-capability-question
OPEN QUESTION — 부팅 한 번으로 확증 가능한 두 건이 아직 정적 추론으로만 남아 있다
- slug:
one-boot-would-settle-two-findings - readiness:
OPEN - source:
final/document.md#11항목 3 ·analysis/19§5.1, §7.1 - known:
messagingPublisher가 6인자 생성자를 호출하고 그것이NO_OBSERVATION을 넘긴다 · 방출자 넷의 main 참조 0 · 두 repository 구현의 main 참조 0 - unknown:
/actuator/metrics에messaging.*시리즈가 실제로 없는지 ·ConditionEvaluationReport가 outbox 사슬 미충족을 어떤 사유로 보고하는지 - next-verification:
app.messaging.enabled=true,app.messaging.broker=kafka,debug=true로 한 번 부팅해 (1)/actuator/metrics시리즈 목록, (2) 시작 로그의Negative matches중MessagingReliabilityAutoConfiguration항목을 캡처. 브로커가 없어도 둘 다 가능하다 - decision-criterion: 두 관측이 정적 판정과 일치하면 두 Case의
missing-verification을 지우고 닫는다. 어긋나면 모듈 분석을 먼저 갱신한다 - relations:
case:observation-downgraded-by-the-composition·case:outbox-chain-behind-an-unsatisfiable-condition
OPEN QUESTION — @ConditionalOnBean 사슬의 실제 평가 순서를 확인하지 않았다
- slug:
conditional-evaluation-order-unverified - readiness:
OPEN - source:
final/document.md#11항목 4 ·analysis/05§14.4 - known: 이 저장소에 그 함정의 실제 사례가 기록돼 있다(파싱 시점 평가로 여덟 빈 소실) · 회피를 위해 루트가 validator를 직접 생성한다
- unknown: 현재 리비전의 조건부 빈들이 각각 어느 시점에 평가되는지 · outbox 사슬이 "뿌리 부재"인지 "평가 시점"인지 — 정적으로는 전자로 보이지만 둘은 같은 증상을 낸다
- next-verification:
debug=true부팅의Negative matches사유 문자열을 읽는다.OnBeanCondition이 남기는 사유가 뿌리 타입 부재를 지목하는지가 갈림점이다 - decision-criterion: 사유가 뿌리 부재를 지목하면 해당 Case의 원인 분류가 확정된다. 평가 시점 문제면 분류와 조치가 바뀐다
- relations:
case:outbox-chain-behind-an-unsatisfiable-condition·case:conditionalonbean-evaluated-at-parse-time·concept:when-conditions-are-evaluated·open-question:one-boot-would-settle-two-findings
DECISION — 마스터 스위치는 루트 하나가 소유하고 자식 설정은 조건을 갖지 않는다
- slug:
one-root-owns-the-master-switch - readiness:
READY· decision-status:ADOPTED - source:
final/document.md#10-3·analysis/19§6.1 ·analysis/05§14.1 - decision-evidence:
.../MessagingPlatformRootAutoConfiguration.java의 조건과 javadoc ·.../PersistenceJpaRootAutoConfiguration.java+JpaOffAutoConfigurationImportFilter·.../GrpcPlatformAutoConfiguration.java(matchIfMissing=false) - grounds:
reference:off-must-be-structural·reference:count-the-frameworks-own-autoconfigurations - classification: 다섯 어댑터가 같은 형태로 교정된 결정이고 근거가 코드에 있다 — "one root owning the condition, importing children that carry none, so a bean added to any child next month is gated without anyone remembering to repeat a condition."
- relations:
case:scan-exclusion-without-an-owner·reference:off-must-be-structural·decision:pool-need-is-a-capability-question
DECISION — 풀이 필요한지는 "JPA가 켜졌나"가 아니라 "커넥션이 필요한 capability가 있나"로 묻는다
- slug:
pool-need-is-a-capability-question - readiness:
READY· decision-status:ADOPTED - source:
final/document.md#10-3·analysis/05§14.1 - decision-evidence:
.../app-bootstrap/.../persistencejpa/DataSourceRequirement.java의reasons(environment)6개 조건과 javadoc - grounds:
reference:count-the-frameworks-own-autoconfigurations·reference:off-must-be-structural - classification: 풀은 JPA의 사유물이 아니다 — outbox·JDBC idempotency store·multi-instance lock·notification store·Fileserver 트랜잭션 경로가 전부 필요로 한다. "JPA 꺼졌나?"만 물으면 아무도 안 쓰는 풀을 열거나 정당하게 쓰던 capability를 조용히 망가뜨린다. 그래서 여섯 조건의 OR로 판정한다.
- relations:
decision:one-root-owns-the-master-switch·reference:count-the-frameworks-own-autoconfigurations
TOPIC 3 — what-a-gate-does-not-prove
CONCEPT — strict test lane — 발견하지 못하면 실패하는 레인
- slug:
strict-test-lane - readiness:
READY - source:
final/document.md#6-3·analysis/05§15.1 ·analysis/19§2.3 ·analysis/20§2.5 - code:
src/build-logic/src/main/groovy/ca.strict-test-lane.gradle· 각 leaf의strictTestLanes { lane(...) } - classification: 이 저장소가 "레인"을 정의하는 방식의 설명이다.
failOnNoDiscoveredTests = true(발견 0은 성공이 아니라 실패),outputs.upToDateWhen { false }(이전 실행 결과를 서빙하지 않음), 태그 기반 선택, 그리고 태그 필터가 discovery 이후에 적용되므로failOnNoDiscoveredTests만으로는 부족해 executed-selector 검사가 따로 있다는 것까지. Docker 부재를 skip이 아니라 에러로 두는 결정과, 반대로 랩톱 빌드를 깨지 않으려test에서 태그를 제외하는 결정이 어떻게 공존하는지 설명한다. - missing-verification: 플러그인이
check에 레인을 연결하지 않는다는 것은 확인했으나, TestKit 픽스처가 검증하는 범위는 읽지 않았다 - relations:
reference:a-lane-that-discovers-nothing-must-fail·reference:a-gate-nobody-runs-reports-the-last-run·decision:only-the-certification-lane-carries-no-docker-guard
CONCEPT — 증거 등급과 provenance — R1과 R2를 가르는 것
- slug:
evidence-grades-and-provenance - readiness:
READY - source:
final/document.md#6-5·analysis/05§15.6 - code:
gradle/jpa-evidence.gradle(917줄) ·config/jpa/readiness-cards.yaml(718줄) - classification: 증거가 "돌았다"에서 "믿을 수 있다"로 올라가는 조건의 설명이다. 후보 검증(R1)은 JUnit XML에서 exact selector와 executed/skipped/failure 수를 읽고 canonical JSON SHA-256으로 manifest를 만들지만, candidate profile·dirty source·아직 R2가 아닌 prerequisite를
readinessBlockers에 보존해서 통과해도 R1에 머문다. R2는 clean revision + CI provenance(JPA_EVIDENCE_CI_JOB, artifact location) + immutable PostgreSQL image digest + prerequisite DAG를 요구한다. 로컬 dirty worktree에서worktree-is-dirty로 실패하는 것이 정식 동작이다. - missing-verification: 두 프로파일 모두 실행하지 않았다 — 컨테이너 필요
- relations:
decision:candidate-evidence-stays-at-r1·reference:agreement-between-documents-proves-nothing·open-question:container-lanes-not-executed
CASE — "certified"라 불리던 레인이 threshold를 하나도 비교하지 않고 있었다
- slug:
a-certifying-lane-that-compared-nothing - readiness:
READY - source:
final/document.md#6-6·analysis/05§15.3 - code:
.../persistence-jpa/build.gradle(jpaPlatformPoolContractTest등록부 주석) - evidence: 없음 — build.gradle 주석이 자기 이력을 기록한다
- classification: 레인 이름이
jpaPlatformPerformanceTest였고 "certify"한다고 기술됐으며 나타나는 모든 곳에서 off가 기본인 boolean 뒤에 있었다 — 이 파일에서도, 명시적으로 off로 설정한 nightly workflow에서도. 릴리스 게이트가 "유일한 threshold assertion이 'threshold를 assert하지 않고 있다'인 레인"에 의존했다. 수정은 이름을 약속하지 않는 것으로 바꾸고 프로퍼티를 제거하는 것이었고, 주석이 그 이름을 반복하지 않는 이유까지 적는다. - missing-verification: 현재 레인을 실행하지 않았다(실 PostgreSQL 필요)
- relations:
concept:strict-test-lane·reference:a-gate-nobody-runs-reports-the-last-run·decision:performance-measurement-is-not-a-release-gate
CASE — 문서 계약 테스트의 단언 경계 밖에 발견된 드리프트 세 건이 전부 있었다
- slug:
doc-contract-test-boundary-predicted-the-drift - readiness:
READY - source:
final/document.md#7-4·analysis/19§9.3 - code:
.../messaging-testkit/src/test/.../MessagingDocumentationContractTest.java·docs/messaging/support-matrix.md - evidence:
evidence/raw/261-messaging-documentation-contract-test-coverage.txt·evidence/raw/255-messaging-capability-doc-vs-code-drift.txt - classification: doc rot를 막기 위해 존재하는 계약 테스트의 단언 여덟 개가 붙드는 것(등급 이름·Kafka 버전 문자열·존재하지 않는 두 enum 상수)은 전부 정확하고, 붙들지 않는 것(capability 표 60칸·runtime membership 문장·브로커 등급표의 "제한" 칸)에 드리프트 세 건이 전부 있다. 테스트 javadoc은 좁은 단언을 고른 이유까지 옳게 적는다. 결함은 좁게 고른 것이 아니라 그 경계가 어디에도 적혀 있지 않다는 점이다.
- missing-verification: 없음 — 단언 목록과 문서를 전수 대조했다
- relations:
reference:agreement-between-documents-proves-nothing·case:support-matrix-said-the-opposite-of-the-code·decision:widen-doc-contract-assertions
CASE — 릴리스 게이트가 읽는 증거를 아무도 생산하지 않는다
- slug:
a-release-gate-with-no-evidence-producer - readiness:
READY - source:
final/document.md#7-6·analysis/20§3.2 - code:
.../grpc-testkit/.../release/GrpcStableReleaseGate.java·.../GrpcReleaseEvidence.java·docs/compatibility/grpc-support-matrix.md - evidence:
evidence/raw/tl-grpc-release-gate-no-producer.txt·evidence/meta/…json·evidence/terminal/…svg - classification: 지원 매트릭스가 "게이트가 릴리스를 막는다"고 현재 시제로 적는다. 게이트 설계는 정교하고 문서 부재를 차단 사유로 두는 근거까지 적지만,
GrpcReleaseEvidence의 다섯 성분이 전부 호출자가 넘기는 값이고runbookPresent는 파일 시스템을 보지 않으며 유일한 생성 지점 넷이 자기 단위 테스트다. Gradle 태스크 0, CI 워크플로 0/28. - missing-verification: 없음 — 생성 지점·태스크·워크플로를 전수 확인했다
- relations:
reference:a-gate-nobody-runs-reports-the-last-run·case:the-second-platform-carried-the-design-not-the-wiring·concept:evidence-grades-and-provenance
CASE — 패키지 카탈로그가 트리보다 아홉 개 적어서 사이클이 통과했다
- slug:
a-catalog-nine-entries-short - readiness:
READY - source:
analysis/05§1 - code:
.../persistence-jpa/src/testkit/.../JpaModuleBoundaryTest.java - evidence: 없음 — 테스트 javadoc의 사후 기록
- classification: 경계 테스트가 production root의 직계 자식 패키지 닫힌 카탈로그를 들고 실제 트리와 비교하는데, 카탈로그에 13개가 적혀 있고 트리에는 22개가 있었다. 그래서 아홉 패키지가 아무 규칙의 지배도 받지 않았고
transaction → postgresql/postgresql → transaction사이클이 통과했다. 수정은 "카탈로그와 트리의 정확한 동등성" 검사를 추가하는 것이었다. - missing-verification: 없음
- relations:
reference:omission-that-passes-is-not-a-gate·concept:strict-test-lane
CASE — 릴리스 레인이 매트릭스 세 버전 중 첫 번째만 돌리고 세 개를 커버로 기록했다
- slug:
three-versions-declared-one-executed - readiness:
READY - source:
analysis/05§15.4 - code:
config/jpa/release-registry.json·.../JpaPlatformContractSupport.start()·.github/workflows/jpa-release.yml - evidence: 없음 — 레지스트리
_comment와 javadoc의 사후 기록 - classification: 릴리스 레인이
-Pjpa.matrix.versions=16,17,18을selectedVersions().get(0)을 쓰는 지원 클래스에 넘겼고 통합 suite 전체가 PostgreSQL 16에 대해 돌았으며, 지원 표는 3개 assertion짜리 smoke test의 힘으로 17과 18을 완전 커버로 기록했다. 수정은start()가 다중 선택을 아예 거부하고, workflow가 major당 job으로 fan-out하며, promotion job이 세 major의 증거가 같은 commit SHA를 담기를 요구하는 것이다. - missing-verification: 현재 릴리스 워크플로를 실행하지 않았다
- relations:
reference:agreement-between-documents-proves-nothing·concept:evidence-grades-and-provenance·decision:capability-grade-is-declared-not-inferred
CASE — 다중 타깃 검증을 확인한다는 테스트가 다른 가드에 걸려 통과했다
- slug:
a-test-that-passed-on-the-wrong-guard - readiness:
READY - source:
final/document.md#3-5·analysis/13§9 - code:
.../notification/.../NotificationBindingCompiler.java:141,159-184 - evidence: 없음 — 코드 경로와 테스트 단언 비교
- classification:
SINGLE전용 가드가 먼저 던져 다중 타깃 검증 전체(순환 탐지 포함)가 도달 불가인데, 그것을 검증한다는 테스트가hasMessageContaining("strategy")로 단언한다. 그 문자열은 먼저 던지는 다른 가드의 메시지에도 있어서 테스트는 통과하고, 검증하려던 코드는 한 번도 실행되지 않는다. - missing-verification: 없음
- relations:
reference:omission-that-passes-is-not-a-gate·reference:a-contract-test-must-run-the-adapters-statement
REFERENCE — 빠뜨림이 통과가 되는 게이트는 게이트가 아니다
- slug:
omission-that-passes-is-not-a-gate - readiness:
READY - source:
final/document.md#9규칙 12 ·analysis/05§1, §16 - classification: 목록·카탈로그·selector로 대상을 고르는 게이트는 새 항목 추가를 잊었을 때 초록불이 된다. 기준은 "대상 집합을 어떻게 정하는가"이고, 답이 "손으로 유지하는 목록"이면 그 목록과 실제 트리의 정확한 동등성을 별도로 검사해야 한다.
- scope: 아키텍처 규칙·entity scan·문서 수치 진술·테스트 레인 selector. 구현 예:
JpaModuleBoundaryTest동등성 ·PersistenceEntityScanCoverageTest·failOnNoDiscoveredTests·verifyDocumentedLeafCount - exceptions: 대상 집합을 파생하는 게이트에는 필요 없다(
CertifiedEvidence.knownGaps가all() − covered). 파생이 가능하면 목록을 두지 않는 것이 항상 낫다. 그리고 walk 범위 자체가 목록이면 그 범위가 같은 문제를 갖는다. - relations:
case:a-catalog-nine-entries-short·case:a-build-gate-that-is-not-in-the-build·reference:numbers-in-docs-should-be-derived
REFERENCE — 문서와 상수가 서로 일치하는 것으로는 아무것도 증명되지 않는다
- slug:
agreement-between-documents-proves-nothing - readiness:
READY - source:
final/document.md#9규칙 5 ·analysis/19§6.6 - classification: 지원 문서와 코드 상수를 대조하는 검사는 둘이 같이 틀렸을 때 통과한다. 기준은 "이 값이 실행된 무언가에서 왔는가"다. 원문: "the matrix and the support document agreed with each other and with nothing that had executed."
- scope: 지원 매트릭스·capability 선언·버전 표·커버리지 표. 파생 형태의 예:
hasLiveBrokerCertification()이 레인 증거에서 계산되고, 증거는 레인이 쓴 manifest이며, Gradle이 양방향 대조하고 CI가 돌린다. - exceptions: 실행이 불가능하거나 지나치게 비싼 주장은 파생할 수 없다. 그때는 부재를 명시적으로 이름 붙이는 것이 대안이다(
knownGaps). - relations:
case:doc-contract-test-boundary-predicted-the-drift·case:three-versions-declared-one-executed·case:a-release-gate-with-no-evidence-producer
REFERENCE — 아무도 돌리지 않는 레인의 게이트는 마지막으로 돌린 사람이 본 것을 보고한다
- slug:
a-gate-nobody-runs-reports-the-last-run - readiness:
READY - source:
final/document.md#9규칙 6 ·analysis/18§4.1c ·analysis/20§3.3 - classification: 게이트의 신뢰도는 단언이 아니라 도는 경로가 정한다. 두 질문 — "
check에 연결돼 있는가", "이것을 이름으로 부르는 CI 워크플로가 있는가". 둘 다 아니면 상태는 마지막으로 명령을 입력한 사람이 본 것이다. - scope: 별도 레인·소스셋·태그로 분리된 모든 검증. 판정은
check의존 그래프와 CI YAML의 명령 문자열을 직접 읽는 것이다. - exceptions: 의도적으로
test에서 뺀 레인은 그 자체로 결함이 아니다. 구분 기준은 대체 경로의 존재다 — messaging 인증 레인은 전용 워크플로가 있고, gRPC의 네 레인은 없다. - relations:
case:a-certifying-lane-that-compared-nothing·case:a-release-gate-with-no-evidence-producer·case:a-build-gate-that-is-not-in-the-build·decision:only-the-certification-lane-carries-no-docker-guard
REFERENCE — 계약 테스트는 어댑터가 실제로 돌리는 statement를 실행해야 한다
- slug:
a-contract-test-must-run-the-adapters-statement - readiness:
READY - source:
final/document.md#9규칙 19 ·analysis/05§11.2, §17 P2 - classification: SQL을 테스트가 재타이핑하면 증명되는 것은 "테스트 작성자와 어댑터 작성자가 쿼리에 대해 합의했다"이고, 그것은 아무도 필요로 하지 않는 속성이다. 기준은 "이 테스트가 실행하는 statement가 production 상수에서 왔는가"다.
- scope: native SQL·스크립트·와이어 포맷을 갖는 모든 어댑터. 구현 형태는 SQL을 production 상수로 뽑고 테스트가 그 상수를 실행하는 것이다(
RecipientClaimSql). - exceptions: statement가 아니라 정책 판정을 검증하는 테스트는 재타이핑 문제가 없다. 다만 그때도 판정 대상이 production 타입이어야 한다 — 이 저장소에는 production 타입을 하나도 실행하지 않는 계약 테스트가 여섯 개 있다.
- relations:
case:a-test-that-passed-on-the-wrong-guard·reference:omission-that-passes-is-not-a-gate·case:jpa-platform-capabilities-have-no-consumer
REFERENCE — 아무것도 발견하지 못한 레인은 성공이 아니라 실패여야 한다
- slug:
a-lane-that-discovers-nothing-must-fail - readiness:
READY - source:
final/document.md#6-3·analysis/05§15.1 - classification: 선택된 레인이 테스트를 하나도 발견하지 못하면 기본 동작은 성공 보고다. 조용히 돌기를 멈춘 계약 suite는 통과하는 것과 구별되지 않는다. 기준은 "이 레인이 0건을 발견했을 때 무엇을 보고하는가"다.
- scope: 태그·소스셋·selector로 좁힌 모든 테스트 태스크. 두 층이 필요하다 — 발견 단계의
failOnNoDiscoveredTests와, 태그 필터가 discovery 이후에 적용되므로 executed-selector 검사가 추가로 필요하다. - exceptions: 환경 부재로 skip하는 것이 정당한 레인(컨테이너 없는 랩톱)은 예외이되, 인증처럼 부재가 곧 거짓 성공이 되는 레인은 예외의 예외다 — 그런 레인은 가드를 달지 않고 실패한다.
- relations:
concept:strict-test-lane·decision:only-the-certification-lane-carries-no-docker-guard
OPEN QUESTION — 컨테이너가 필요한 레인의 실제 결과를 실행으로 확인하지 않았다
- slug:
container-lanes-not-executed - readiness:
OPEN - source:
final/document.md#6-2·#11항목 1 - known: 실행한 레인과 결과가 기록돼 있다(messaging 851 · grpc 579 · 증거 레인 7·9·9 · graphql 1,603 · websocket 720 · grpc adapter 48 · app-bootstrap 1,016 중 1 환경 실패) · 미실행 레인의 목록과 각각의 요구사항 · messaging 인증만 CI가 강제한다
- unknown: JPA 6개 레인 + readiness task 15종, mongo/redis/fileserver/objectstorage/httpclient Testcontainers, websocket 커스텀 4종,
grpcPerformanceTest의 실제 결과 · 그중 어느 것이 지금 빨간지 - next-verification:
./gradlew jpaPlatformReleaseGate와./gradlew :messaging:messaging-kafka:verifyMessagingCertificationEvidence두 개를 먼저 돌린다 - decision-criterion: 두 명령의 exit code와 실패 목록이 나오면 닫는다. 실패가 나오면 각각이 새 Case 후보가 되고
a-gate-nobody-runs-reports-the-last-run의 예측과 대조한다 - relations:
open-question:commit-ambiguity-lane-not-executed·open-question:redis-topology-lane-not-executed·open-question:v2-state-machine-lanes-not-executed·reference:a-gate-nobody-runs-reports-the-last-run
DECISION — 인증 레인만 Docker 가드를 달지 않는다
- slug:
only-the-certification-lane-carries-no-docker-guard - readiness:
READY· decision-status:ADOPTED - source:
final/document.md#6-4·analysis/19§2.3 - decision-evidence:
.../messaging-kafka/build.gradle의 등록부 주석과excludeTags·.github/workflows/messaging-certification.yml헤더 주석 - grounds:
reference:a-lane-that-discovers-nothing-must-fail·reference:a-gate-nobody-runs-reports-the-last-run - classification: 모든 컨테이너 suite가 Docker 부재 시 이유를 적고 skip하는데 인증 레인만 예외다 — "a lane that skipped would report success for a broker nobody started." 대신
test에서 태그로 제외해 랩톱 빌드를 깨지 않고, 전용 워크플로가 레인이 아니라 게이트를 돌리며 게이트가 레인에 의존한다. - relations:
case:a-release-gate-with-no-evidence-producer·concept:strict-test-lane
DECISION — 성능 측정은 릴리스 게이트에 넣지 않는다
- slug:
performance-measurement-is-not-a-release-gate - readiness:
READY· decision-status:ADOPTED - source:
final/document.md#10-5·analysis/20§2.5 ·analysis/05§15.3 - decision-evidence:
.../grpc-testkit/build.gradle의excludeTags 'grpc-performance'와 주석 ·.../persistence-jpa/build.gradle의 개명 기록 - grounds:
case:a-certifying-lane-that-compared-nothing·reference:a-gate-nobody-runs-reports-the-last-run - classification: 두 가족이 같은 결론에 다른 경로로 도달했다. gRPC는 처음부터 빼고 이유를 적었고("flaky gates get turned off"), JPA는 사고로 배웠다. 두 경우 모두 진짜 성능 게이트는 전용 러너·warmup/sample 수·기록된 baseline이 필요하고 그때는 별도 레인이어야 한다는 조건을 남겼다.
- relations:
case:a-certifying-lane-that-compared-nothing·open-question:container-lanes-not-executed
DECISION — 후보 증거는 통과해도 R1에 머무르고 R2는 별도 게이트가 판정한다
- slug:
candidate-evidence-stays-at-r1 - readiness:
READY· decision-status:ADOPTED - source:
final/document.md#6-5·analysis/05§15.6 - decision-evidence:
gradle/jpa-evidence.gradle의 두 태스크와readinessBlockers구현 ·config/jpa/readiness-cards.yaml의state필드 - grounds:
concept:evidence-grades-and-provenance·reference:agreement-between-documents-proves-nothing - classification: 후보 검증이 zero-skip·schema·content hash·prerequisite link를 전부 만족해도
attainedReadiness=R1을 유지하고, 각 manifest가 candidate profile·dirty source·비R2 prerequisite를 blocker로 보존해 후보 통과를 R2로 오인할 수 없게 한다. R2는 clean revision과 CI provenance와 immutable image digest를 요구하며, 로컬 dirty worktree에서 실패하는 것이 정식 동작이다. - relations:
concept:evidence-grades-and-provenance·decision:capability-grade-is-declared-not-inferred
TOPIC 4 — bounding-by-type
CONCEPT — 서명된 커서의 구조와 검증 순서
- slug:
signed-cursor-structure - readiness:
READY - source:
final/document.md#10-4·analysis/05§2.4 ·analysis/19§4.x - code:
.../persistence-jpa/.../api/query/SignedJsonCursorCodec.java·.../grpc-policy/.../streaming/GrpcResumeTokenCodec.java·.../GrpcResumeToken.java - classification: 이 저장소에 서명 커서 구현이 둘 있고 같은 다섯 단계를 지킨다. (1) 길이 검사가 substring/decode/MAC 이전 첫 줄에 온다 — 페이징 엔드포인트는 public이고 그 아래 모든 코드가 caller가 보낸 크기에 비례해 할당한다. (2) base64 확장률로 decode 후 크기를 할당 전에 bound한다. (3) MAC 길이를 먼저 확인한다 —
MessageDigest.isEqual은 같은 길이 입력에 대해서만 상수 시간이다. (4) 상수 시간 비교. (5) 서명 검증 후에야 payload를 파싱한다. gRPC 쪽은 여기에 두 가지를 더한다 — 알 수 없는 key id를 현재 키로 폴백하지 않고 거부(폴백은 rotation을 "탈취된 키가 여전히 검증되는 창"으로 만든다), 그리고 malformed·unknown key·verify 실패를 구별 불가능하게 반환(구별은 probing oracle이다). - missing-verification: 없음 — 두 구현을 코드로 확인했다
- relations:
case:cursor-verification-order·decision:cursors-are-signed-for-integrity·reference:names-are-registry-keys-not-values
CONCEPT — 카디널리티 경계를 타입으로 표현하기
- slug:
cardinality-bounds-as-types - readiness:
READY - source:
final/document.md#4-1,#5-2·analysis/05§2.1, §12.2 ·analysis/02 - code:
.../api/PersistenceOperationName.java계열 ·.../observation/JpaMetricTags.java·.../shared-contract/.../ForbiddenMetricTags.java·.../CardinalityBounds.java - classification: metric tag·trace·retry policy의 키가 되는 문자열을 값 타입으로 만들고 정규식을 생성자에 두는 패턴의 설명이다.
PersistenceOperationName·QueryName·ConstraintCode·FetchPlanName·WorkQueueName·JsonPathName·TenantId가 전부 같은 모양이고, 목적은 엔티티 id·tenant id·SQL 조각·요청 스코프 값이 그 자리에 올 수 없게 하는 것이다. 검증이 registry가 아니라 생성자에 있는 이유(대시보드가 안 뜰 때까지 살아남지 않게)와 sanitize가 아니라 reject인 이유(sanitize하면 caller가 계속 넘기고 눈치채지 못한다)를 함께 다룬다. - missing-verification: 없음
- relations:
reference:names-are-registry-keys-not-values·reference:reject-rather-than-sanitize·decision:tenant-id-is-never-a-metric-tag
CASE — 시그니처가 payload를 받지 않는데 예외 메시지로 PII가 로그에 남았다
- slug:
pii-through-an-exception-message - readiness:
READY - source:
final/document.md#8-1·analysis/04§4 - code:
.../support/.../FailOpenDependencyLogger.java(logFailure) ·.../app-bootstrap/.../LogMaskingPatterns.java - evidence:
evidence/raw/021a-support-logger-pii-probe.java·evidence/raw/021-support-logger-pii-probe.txt - classification: source와 README가 "logger method가 body/recipient/payload를 받지 않기 때문에 PII가 log에 닿지 않는다"고 주장하고 테스트도 그것을 검사하는데, fixture의 예외가
"connection refused"라 marker가 어떤 argument에도 없다 — 테스트는 payload object가 직접 전달되지 않는다는 것만 확인한다. PII를 담은 예외를 넣는 probe를 실행하니 formatted WARN에 그대로 남았다. 소비자 SPI 넷은 arbitraryException을 던질 수 있고, 전역 masking은 password/token 계열만 덮으며 README 스스로 "보증이 아니라 defence-in-depth"라고 적는다. - missing-verification: 실제 provider SDK가 recipient/body를 예외 메시지에 넣는지는 확인하지 않았다. probe는 그것이 가능할 때 logger가 막지 못한다는 것만 보인다
- relations:
concept:cardinality-bounds-as-types·reference:telemetry-can-be-more-dangerous-than-its-subject·reference:reject-rather-than-sanitize
CASE — 진단 리포트가 살아 있는 리소스를 담지 않도록 값 타입을 좁혔다
- slug:
a-report-that-cannot-carry-a-datasource - readiness:
READY - source:
analysis/05§2.5, §12.1 - code:
.../api/capability/CapabilitySupport.java·.../security/DatabasePrivilegeReport.java - evidence: 없음 — 값 타입 정의와 그 javadoc
- classification:
CapabilitySupport가 provider 객체(DataSource·EntityManagerFactory·SessionFactory)를 절대 담지 않고,DatabasePrivilegeReport가 JDBC URL·패스워드·호스트를 담지 않는다. 이유가 같다 — 이 값들은 리포트로 직렬화되고 actuator로 publish될 수 있어야 하는데, 살아 있는 리소스를 값 타입에 끌고 들어가면 리포트가 자격증명을 흘린다. "publish 전에 마스킹해야 할 것은 애초에 들어가지 않는다"가 설계 문장이다. - missing-verification: 없음
- relations:
concept:cardinality-bounds-as-types·reference:telemetry-can-be-more-dangerous-than-its-subject
CASE — 커서 서명 검증이 길이·상수시간·순서를 전부 지켜야 했던 이유
- slug:
cursor-verification-order - readiness:
READY - source:
analysis/05§2.4 - code:
.../api/query/SignedJsonCursorCodec.java - evidence: 없음 — 구현과 그 javadoc
- classification: 다섯 방어가 각각 다른 공격을 막고 순서가 계약이다.
MAX_ENCODED_LENGTH(4096)검사가 첫 줄에 없으면 decode가 caller가 보낸 크기만큼 할당하고, MAC 길이 확인이 없으면MessageDigest.isEqual의 상수 시간 보장이 깨지며, 서명 검증 전에 파싱하면 서명 없는 토큰이 애플리케이션 JSON 파서에 도달한다. 그리고 MAC이 버전과 payload를 함께 덮어 prefix 재작성으로 옛 포맷으로 다운그레이드하는 것을 막는다. - missing-verification: 없음
- relations:
concept:signed-cursor-structure·decision:cursors-are-signed-for-integrity
REFERENCE — 이름은 값이 아니라 registry key다
- slug:
names-are-registry-keys-not-values - readiness:
READY - source:
final/document.md#9규칙 15 ·analysis/05§2.1, §5, §7 - classification: 쿼리·큐·업서트·JSON path·정렬 필드처럼 파라미터로 바인딩할 수 없는 것을 caller가 문자열로 주게 하면 caller가 statement의 일부를 쓰는 것이다. 기준은 "이 값이 statement를 선택하는가, statement의 일부가 되는가"다.
- scope: SQL/JSON path/정렬/스키마 이름 등 바인딩 불가능한 위치 전부, 그리고 metric tag·trace·retry policy 키가 되는 모든 문자열.
- exceptions: 값(비교 대상·payload)은 반대다 — 바인딩할 수 있으므로 registry에 넣으면 유연성만 잃는다.
- relations:
concept:cardinality-bounds-as-types·reference:register-paths-bind-values·decision:tenant-id-is-never-a-metric-tag
REFERENCE — 관측을 위해 수집한 데이터가 관측 대상보다 위험할 수 있다
- slug:
telemetry-can-be-more-dangerous-than-its-subject - readiness:
READY - source:
final/document.md#5-2·analysis/05§12.2 ·analysis/02 - classification: 메트릭 태그·로그·트레이스·예외 메시지는 원본보다 오래 살고 더 널리 퍼지는 사본이다. 기준은 "이 진단 경로에 들어가는 값의 도메인을 누가 정하는가"이고, 답이 "외부 라이브러리" 또는 "요청 내용"이면 그 경로는 개인정보 경계다.
- scope: metric tag·structured log field·trace attribute·exception message·actuator 응답. 구현:
JpaMetricTags다섯 개 ·ForbiddenMetricTags·SqlDiagnosticRedactor(문자열 리터럴·숫자·이메일 통째 치환, 512자 절단) ·sanitized(Throwable)가 타입만 남김. - exceptions: 이미 외부에서 들어온 값(드라이버 예외 메시지)은 거부할 수 없으므로 그때만 redaction이 자리이고, 그것을 "보증"이 아니라 defence-in-depth로 부르는 것이 정확하다.
- relations:
case:pii-through-an-exception-message·case:a-report-that-cannot-carry-a-datasource·decision:tenant-id-is-never-a-metric-tag
REFERENCE — path·identifier는 등록하고 value는 바인딩한다
- slug:
register-paths-bind-values - readiness:
READY - source:
final/document.md#9규칙 16 ·analysis/05§7.5 - classification: JSON path·정렬 필드·스키마 이름·conflict 컬럼은 파라미터로 바인딩할 수 없으므로 registry로 고정하고, 비교 값은 바인딩할 수 있으므로 항상 bound parameter로 넘긴다. 원문: "path는 registry에서 오고 고정 statement의 일부다. value는 caller에서 오고 항상 bound parameter다."
- scope: native SQL·JSONB 질의·동적 정렬·업서트 대상 컬럼. 등록 항목도 정규식으로 검증한다(예: unquoted PostgreSQL identifier
[a-z_][a-z0-9_]{0,62}) — 등록 실수조차 함수 호출이나 두 번째 절을 넣지 못하게. - exceptions: 스키마 이름처럼 statement의 일부이면서 tenant에서 유도되는 값은 registry 사전 등록이 유일한 안전 형태다 — 유도하면 외부 영향 데이터로 SQL을 만드는 것이다.
- relations:
reference:names-are-registry-keys-not-values·case:jpa-platform-capabilities-have-no-consumer
REFERENCE — sanitize가 아니라 reject가 기본이다
- slug:
reject-rather-than-sanitize - readiness:
READY - source:
analysis/05§12.2 - classification: 경계를 넘는 값을 조용히 자르거나 치환하면 caller가 계속 그 값을 넘기고 절대 눈치채지 못한다. 거부하면 도입된 자리에서 실패한다. 기준은 "이 값의 생산자를 우리가 고칠 수 있는가"이고, 답이 예면 reject다.
- scope: metric tag·이름 값 타입·설정 키·헤더 이름.
LowCardinality.REGISTERED가 정규식을 통과하지 못하면 던지는 것이 그 형태다. - exceptions: 생산자를 고칠 수 없는 경우(외부 드라이버의 예외 메시지, 서드파티 응답 본문)는 redaction이 맞고, 그때는 그것이 보증이 아님을 문서에 적어야 한다.
- relations:
case:pii-through-an-exception-message·reference:telemetry-can-be-more-dangerous-than-its-subject·concept:cardinality-bounds-as-types
DECISION — tenant id는 메트릭 태그가 되지 않는다
- slug:
tenant-id-is-never-a-metric-tag - readiness:
READY· decision-status:ADOPTED - source:
final/document.md#10-4·analysis/05§13.2 ·analysis/02 - decision-evidence:
.../experimental/multitenancy/TenantId.java의 정규식과 근거 javadoc ·.../shared-contract/.../ForbiddenMetricTags.java·.../observation/JpaMetricTags.java - grounds:
reference:telemetry-can-be-more-dangerous-than-its-subject·concept:cardinality-bounds-as-types - classification: tenant 카디널리티는 정의상 unbounded이고 텔레메트리 안의 tenant id는 그렇게 취급되지 않는 시스템 안의 고객 데이터다. 두 층으로 강제된다 —
TenantId가[a-z0-9][a-z0-9_-]{1,62}로 스키마 이름·set_config값·라우팅 키에 들어갈 수 있는 형태만 허용하고, 메트릭 태그 집합이 다섯 개로 닫혀 있으며 검증이 생성자에 있다.request_id가 baggage에는 허용되고 metric label에는 금지되는 비대칭이 테스트에서 pin돼 있다. - relations:
reference:telemetry-can-be-more-dangerous-than-its-subject·case:pii-through-an-exception-message
DECISION — 커서에 서명하는 이유는 기밀성이 아니라 무결성이다
- slug:
cursors-are-signed-for-integrity - readiness:
READY· decision-status:ADOPTED - source:
final/document.md#10-4·analysis/05§2.4 - decision-evidence:
.../api/query/SignedJsonCursorCodec.java의 클래스 javadoc이 결정과 근거를 적는다 - grounds:
concept:signed-cursor-structure·case:cursor-verification-order - classification: payload는 읽을 수 있고 그것이 의도다. 막는 것은 무결성 침해다 — "서명 없는 커서는 클라이언트가 제어하는 정렬 상태이고, 그걸 고쳐 쓰면 임의의 키로 seek할 수 있다. 스캔이 어디서 시작했는지에 predicate가 의존하는 곳이라면 이건 접근 제어 우회다." gRPC의 resume token이 같은 결정을 다른 필드 집합(caller·filter fingerprint·snapshot version·expiry·key id)으로 반복한다.
- relations:
concept:signed-cursor-structure·case:cursor-verification-order
TOPIC 5 — duplicate-mechanisms
CASE — 클라이언트가 준 엔드포인트가 SSRF 가드가 아니라 약한 private 사본을 지났다
- slug:
a-weaker-private-copy-on-the-wired-path - readiness:
READY - source:
final/document.md#7-3·analysis/13§25.1 - code:
.../notification/.../WebPushSubscriptionValue.java:27-28,50-57 - evidence: 없음 — 두 검사의 코드 비교와 호출 경로 확인
- classification: Web Push 구독 엔드포인트는 클라이언트가 제공하는 URL이고, 저장소에 SSRF 가드가 존재하는데 이 경로는 그것을 지나지 않고 같은 클래스 안의 약한 검사 private 사본을 쓴다. 중복 장치 중 조립된 쪽이 약한 쪽인 패턴에서 결과가 보안 경계에 닿는 사례다.
- missing-verification: 실제 SSRF 시도를 재현하지 않았다
- relations:
reference:check-which-duplicate-is-wired·case:a-policy-reversed-by-a-later-filter
CASE — 요청 식별자를 클라이언트가 고를 수 없다는 정책이 뒤에 도는 필터에 뒤집혔다
- slug:
a-policy-reversed-by-a-later-filter - readiness:
READY - source:
final/document.md#7-3·analysis/14§32.1 - code:
.../adapter/inbound/web/.../WebMvcRequestIdFilter.java - evidence: 없음 — 등록 지점과 두 구현 비교
- classification: 정책이 한 필터에 구현돼 있고 뒤에 도는 다른 배선 필터가 클라이언트 제공 값을 채택한다. 둘 다 조립돼 있어서 정책 쪽이 먼저 실행되고 나중 것이 덮는다.
- missing-verification: 부팅해서 실제 필터 체인 순서를 확인하지 않았다
- relations:
reference:check-which-duplicate-is-wired·case:a-weaker-private-copy-on-the-wired-path
CASE — JPA 플랫폼 capability 대부분에 production 소비자가 없다
- slug:
jpa-platform-capabilities-have-no-consumer - readiness:
READY - source:
final/document.md#8-2항목 8 ·analysis/05§17 P8, §11.0 - code:
.../persistence-jpa/.../springdata/**·.../hibernate/**·.../postgresql/{write,lock,json,array,range,copy}/**·.../cache/**·.../envers/**·.../querydsl/** - evidence: 없음 — leaf 밖 참조 전수 계수
- classification:
JpaKeysetQuerySupport·SafeSortMapper·JpaStreamExecutor·FetchPlanApplier·batch/bulk/stateless executor·업서트·work claim·JSON/range/array·COPY·CacheRegionCatalog·EntityGraphCatalog가 전부 leaf 밖 참조 0이다. 결정적 증거는 같은 leaf 안의 두 스토어다 —fileserver(25파일)와notification(53파일)이 합쳐 350KB이고 매일 PostgreSQL에 쓰는데 JPA 플랫폼 타입을 하나도 import하지 않고, 같은 문제(SKIP LOCKED 큐 클레임·안전 정렬·충돌 판정)를 각자 다시 만들었다. 즉 "아직 소비자가 없어서"가 아니라 소비자가 있는데도 안 쓴다.support-matrix.md가 대부분을 Advanced로 표기하므로 거짓말은 아니지만, 이 조합은 §P2(계약 테스트가 production 타입을 실행하지 않음)와 겹쳐 "구현됐고, 안 쓰이고, 테스트도 없다"가 된다. - missing-verification: 파생 프로젝트가 실제로 이 capability들을 가져다 쓰는지 확인할 방법이 이 저장소 안에 없다 — 그것이 세 선택지(채택 / 명시적 라이브러리 선언 / 제거) 중 무엇이 맞는지를 정한다
- relations:
reference:check-which-duplicate-is-wired·reference:a-contract-test-must-run-the-adapters-statement·reference:a-bean-is-not-composition-evidence·decision:capability-grade-is-declared-not-inferred
CASE — forwarded 헤더 신뢰 판정이 Nginx에만 있고 Java 정책 421 LOC은 배선되지 않았다
- slug:
trust-policy-lives-in-nginx-not-in-the-code - readiness:
READY - source:
final/document.md#3-1·analysis/14§32.2 - code:
.../adapter/inbound/web/...의 forwarded 헤더 정책(421 LOC) ·infra/nginx설정 - evidence: 없음 — 도달성 확인과 설정 파일 대조
- classification: forwarded 헤더를 어디까지 믿을지 판정하는 Java 정책이 421 LOC 작성돼 있고 배선되지 않는다. 실제 판정은 Nginx 설정이 한다. 두 곳이 어긋나면 코드 리뷰가 잡을 수 없고, Java 쪽을 고쳐도 동작이 바뀌지 않는다.
- missing-verification: Nginx 설정이 실제로 어떤 hop을 신뢰하는지 런타임에서 확인하지 않았다
- relations:
reference:check-which-duplicate-is-wired·reference:a-bean-is-not-composition-evidence
CASE — 재시도 구현이 둘이고 정교한 쪽을 아무도 호출하지 않는다
- slug:
a-retry-implementation-nobody-calls - readiness:
READY - source:
final/document.md#8-2항목 7 ·analysis/05§17 P1 - code:
.../transaction/FullTransactionRetryCoordinator.java·.../SpringPolicyTransactionPort.java·.../DefaultJpaRetryPolicy.java - evidence: 없음 — 참조 전수 확인
- classification:
FullTransactionRetryCoordinator는 빈으로 등록되지만 production 코드 어디에서도 주입되지 않는다(참조는 app-bootstrap의 자기 config 둘과 테스트뿐). 실제로 도는 재시도는SpringPolicyTransactionPort+TransactionRetryBackoff이고COMMAND_SERIALIZABLE_REPLAY_SAFE정책에만 적용된다. 둘이 다른 설정을 읽고(전자는 하드코딩 3회/20~500ms, 후자는ca-skeleton.jpa.transaction.retry-*기본 2회),DefaultJpaRetryPolicy의 6단계 순서·IrreversibleSideEffectContext확인·RetryBudget의 elapsed 상한이 전부 호출되지 않는 경로에 있다.support-matrix.md는 "Full-transaction retry | Stable"이라고 선언한다. - missing-verification: 없음 — 참조 전수 확인
- relations:
reference:check-which-duplicate-is-wired·case:autoconfiguration-in-name-only·decision:retry-unit-is-the-use-case
REFERENCE — 중복 장치를 찾으면 어느 쪽이 조립됐는지 먼저 확인한다
- slug:
check-which-duplicate-is-wired - readiness:
READY - source:
final/document.md#7-3·analysis/99§4 - classification: 같은 문제를 푸는 장치가 둘이면 "하나를 지우자"가 아니라 "요청 경로에 놓인 쪽이 어느 쪽인가"를 먼저 묻는다. 이 저장소의 13건 중 11건에서 조립된 쪽이 더 약했다. 이유는 잘 만든 정책 객체가 프레임워크 표면 밖(95.5%)에 있고 실제로 경로에 놓이는 것은 좁은 표면에서 손으로 배선한 것이기 때문이다.
- scope: 검증·가드·정책·복구 로직의 중복. 판정은 두 구현의 호출 경로를 각각 따라가는 것이고 코드 품질 비교보다 먼저 한다.
- exceptions: 방향이 반대인 경우도 있다 — 자격 증명 회전은 조립된 쪽이 동시성 계약 테스트까지 갖춘 더 나은 구현이고 죽은 쪽이 참조 0이다. 그때 조치는 "약한 쪽을 조립"이 아니라 "죽은 쪽을 제거"다.
- relations:
case:a-weaker-private-copy-on-the-wired-path·case:a-retry-implementation-nobody-calls·reference:two-vocabularies-for-one-concept
REFERENCE — Atomic* 타입의 존재는 원자성의 증거가 아니다
- slug:
atomic-type-is-not-atomicity - readiness:
READY - source:
final/document.md#5-5·analysis/20§7 - classification:
get()으로 비교하고 별도로incrementAndGet()을 부르는 것은 원자적 타입을 쓴 check-then-act다. 기준은 "읽은 값과 쓰는 값 사이에 다른 스레드가 끼어들 수 있는가"이고, 판정은compareAndSet/updateAndGet/synchronized의 존재를 세는 것이다. - scope: 카운터·레지스트리·상태 홀더. 특히 경계를 강제하는 카운터에서 결정적이다 — 경계는 동시성이 높을 때 필요하고 check-then-act는 정확히 그때 샌다.
- exceptions: 단일 스레드 전용임이 문서화됐거나 구조적으로 보장되는 경우. 다만 그 보장이 javadoc에 없으면 다음 사람이 알 수 없다.
- relations:
case:the-same-rotation-defect-closed-once-and-reproduced·reference:check-which-duplicate-is-wired
REFERENCE — 같은 개념의 두 어휘가 공존하면 하나를 죽은 것으로 표시한다
- slug:
two-vocabularies-for-one-concept - readiness:
READY - source:
analysis/19§5.4 ·analysis/06§58, §69 ·analysis/05§12.5 - classification: 같은 개념을 두 타입이 표현하고 하나만 조립돼 있으면, 남은 쪽은 다음 사람이 어느 것을 써야 할지 알 수 없게 만든다. 기준은 "이 둘이 같은 질문에 답하는가"이고, 그렇다면 조립된 쪽을 정본으로 표시하고 나머지를 제거 대상으로 명시한다.
- scope: 감사 메타데이터·자격 증명 회전·TTL 선언·recovery 어휘. 표시 방법은 support matrix의 상태 컬럼("Candidate, not composed")과 ArchUnit 규칙(엔티티가 둘 다 쓰는 것을 금지)이다.
- exceptions: 두 어휘가 다른 계층에 속하고 각각 소비자가 있으면 중복이 아니다 —
api.error의 안정 예외 계층과failure의 웹 표면 매핑이 그 경우다. - relations:
reference:check-which-duplicate-is-wired·decision:one-audit-mechanism-per-entity
DECISION — 감사 메커니즘은 엔티티당 정확히 하나여야 한다
- slug:
one-audit-mechanism-per-entity - readiness:
READY· decision-status:ADOPTED - source:
analysis/05§12.5 - decision-evidence:
.../testkit/arch/JpaArchitectureRules.java의entitiesUseExactlyOneAuditMechanism·docs/jpa/support-matrix.md의 두 메커니즘 상태 표기 - grounds:
reference:two-vocabularies-for-one-concept·reference:check-which-duplicate-is-wired - classification:
audit/AuditableEntity(canonical,@MappedSuperclass,created_at/by·updated_at/by, actor 256)와auditing/AuditMetadata(candidate,@Embeddable,modified_*, actor 64)가 공존하고 ArchUnit이 한 엔티티가 둘 다 쓰는 것을 막는다. 근거: "둘 다 고른 엔티티는 하나의 의미에 두 writer, 하나의 사실에 두 컬럼 계열, 그리고 어느 엔티티가 뭘 골랐는지 알아야 하는 마이그레이션을 얻는다." 어느 메커니즘도 bulk/native update에 도달하지 않으므로bulkUpdatesOfAuditedEntitiesStampAudit가 별도로 그것을 강제한다. - relations:
reference:two-vocabularies-for-one-concept
TOPIC 6 — drift-direction
CASE — 지원 매트릭스가 코드와 반대를 적었고, 그 오해가 소비자에게 자기 멱등성을 생략하게 한다
- slug:
support-matrix-said-the-opposite-of-the-code - readiness:
READY - source:
final/document.md#8-1·analysis/19§6.3 - code:
.../messaging-kafka/.../KafkaMessagingTransport.java:62-64(CAPABILITIES와 javadoc) ·docs/messaging/support-matrix.md - evidence:
evidence/raw/tl-kafka-dedup-drift.txt·evidence/meta/…json·evidence/terminal/…svg·evidence/raw/255-messaging-capability-doc-vs-code-drift.txt - classification: capability 표 60칸을 코드 배열과 전수 대조한 결과 불일치가 정확히 한 칸이다 — Kafka
deduplicatedPublish가 문서O, 코드false. 그 플래그가 12개 중 유일하게 실제 거부를 발생시키는 것이고, 코드 javadoc이true→false변경 이력과 피해를 직접 이름 붙인다 — "the caller believes the broker is deduplicating and skips the idempotency it would otherwise build." 나머지 48칸은 일치하고, Pulsar의keyedOrdering은 문서가 두 배열 차이까지 반영해 코드보다 정밀하다. - missing-verification: 없음 — 60칸 전수 대조
- relations:
reference:fix-overstatement-before-understatement·case:doc-contract-test-boundary-predicted-the-drift·decision:widen-doc-contract-assertions
CASE — README의 활성화 recipe를 그대로 따르면 애플리케이션이 시작되지 않는다
- slug:
the-readme-recipe-does-not-start - readiness:
READY - source:
final/document.md#7-4·analysis/06§4, §23 - code:
.../persistence-mongo/README.md·.../MongoMappingConfiguration.java·.../PolicyAwareMongoTypeMapper.java - evidence:
evidence/raw/129-mongo-empty-type-registry-write-probe.txt·evidence/raw/129a-…java - classification: README가 제시하는 활성화 절차를 그대로 따르면 시작은 하고 첫 write에서 예외가 난다. 빈 registry가 기본 bean이고 policy-aware mapper가 모든 converter에 무조건 설치되며 미등록 타입 write가
IllegalStateException을 던진다. probe가 shipped default 조합을 실제로 구성해 확인했고, 같은 converter에 Spring 기본 mapper를 두면 같은 write가 성공한다 — 실패는 문서·엔티티 형태가 아니라 이 leaf가 설치한 mapper에서 온다. - missing-verification: 실제 MongoDB에 붙이지 않았다(probe는 converter 수준)
- relations:
reference:fix-overstatement-before-understatement·case:mongo-default-throws-on-first-write
CASE — 출하 default 조합이 첫 write에서 예외를 던진다
- slug:
mongo-default-throws-on-first-write - readiness:
READY - source:
final/document.md#4-2·analysis/06§23 - code:
.../persistence-mongo/.../MongoMappingConfiguration.java·.../MongoTypeMetadataConfigurer.java·.../PolicyAwareMongoTypeMapper.java:75,134·.../MongoTypeMetadataRegistry.java - evidence:
evidence/raw/129-mongo-empty-type-registry-write-probe.txt·evidence/raw/129a-…java - classification: 세 사실이 겹친다 — (1) 기본 bean이 비어 있는 registry이고 그 javadoc이 "An empty registry so a deployment with no long-lived collection still starts"로 의도를 적는다, (2) configurer가 policy-aware mapper를 모든
MappingMongoConverter에 무조건 설치한다, (3) 그 mapper가 미등록 타입 write에IllegalStateException을 던진다. 그래서 module을 켜고 type metadata를 등록하지 않은 배포는 시작은 하고 첫 write에서 실패한다. probe가 shipped default 조합을 실제로 구성해 확인했고 Spring 기본 mapper로 바꾸면 같은 write가 성공한다. 그리고 같은 컴포넌트가 같은 질문에 세 가지로 답한다 — 미등록 타입의 정책은CLASS_METADATA_ALLOWED, type-restricted query는 class name을 predicate에 쓰고, write는 예외다. 읽기와 쓰기가 정반대로 답하고 어느 쪽도 registry가 문서화한 기본값과 일치하지 않는다. - missing-verification: 실제 MongoDB에 붙이지 않았다 — probe는 converter 수준이다
- relations:
case:the-readme-recipe-does-not-start·case:a-flag-that-validates-an-unwired-subsystem·reference:fix-overstatement-before-understatement
CASE — 문서가 UUIDv7이라 말하고 생성되는 것은 v4다
- slug:
documented-uuidv7-generates-v4 - readiness:
READY - source:
final/document.md#7-4·analysis/07§5, §6, §7, §8 ·analysis/01§11 - code:
.../adapter/outbound/identifier/...· 해당 leaf의CLAUDE.md·README.md - evidence: 없음 — 문서 문장과 구현 대조
- classification: 문서는 UUIDv7을 말하고 구현은 v4를 만든다. 같은 leaf에서
CLAUDE.md의 의존성 서술 세 항목이 모두 틀렸고, README에 사실 오류가 셋 있으며,CLAUDE.md가 근거로 대는 두 가드 중 하나는 저장소에 없다. 10 파일짜리 leaf에서 문서 오류가 이만큼 나오는 것은 이 leaf에 production 소비자가 없다는 사실(§7 §3)과 함께 읽어야 한다 — 아무도 쓰지 않으면 문서도 검증되지 않는다. - missing-verification: 없음
- relations:
reference:fix-overstatement-before-understatement·reference:numbers-in-docs-should-be-derived
CASE — 선택할 수 없는 브로커가 지원 매트릭스에 기능 목록과 함께 실려 있다
- slug:
an-unselectable-broker-listed-with-features - readiness:
READY - source:
final/document.md#2-4,#3-3·analysis/19§6.2 - code:
.../messaging-spring-boot-starter/.../MessagingProviderSelection.java(BROKERS_WITHOUT_A_TRANSPORT) ·docs/messaging/support-matrix.mdRabbitMQ 행 - evidence:
evidence/raw/256-messaging-provider-selection-and-rabbit-transport.txt - classification:
app.messaging.broker=rabbit은 startup 오류다 —RabbitChannelPublisher구현이 저장소에 없고 선택기가 그것을 이름으로 거부한다. 코드는 fail-closed로 정직하고 근거까지 적는다. 그런데 지원 매트릭스의 RabbitMQ 행은 "제한" 칸에 "장애 시나리오 미실행"만 적고 기능 칸에 publisher confirm·quorum queue·DLQ를 나열한다. 읽는 사람은 "검증은 덜 됐지만 쓸 수는 있는 옵션"으로 이해하고, 20 main / 2,443 LOC가 출하 아티팩트에 들어 있다. - missing-verification: 없음
- relations:
reference:fix-overstatement-before-understatement·case:support-matrix-said-the-opposite-of-the-code
CASE — 다섯 문서가 "exactly 19 leaf"라고 적고 레지스트리는 62다
- slug:
five-documents-say-nineteen-leaves - readiness:
READY - source:
final/document.md#7-4·analysis/05§17 P3 - code:
docs/jpa/repository-adaptation.md·docs/mongodb/…·docs/httpclient/…·docs/notification/module-mapping.md·docs/adr/ADR-MONGO-001-platform-boundary.md·src/settings.gradle(16줄, 수 검사 없음) - evidence: 없음 — 문서 문장과 레지스트리 대조
- classification: 다섯 문서가 "exactly 19 leaf identities"와 "
src/settings.gradlethrows when the registry does not contain exactly 19 modules"를 적는데, 레지스트리는 62개이고settings.gradle에는19도 수 검사도 없다(검증은 플러그인에 위임됐다).verifyDocumentedLeafCount가 존재하지만CLAUDE.md·AGENTS.md·leafbuild.gradle만 walk하고docs/**는 대상이 아니다 — 그 게이트 자신의 주석이 "이름이 적힌 목록은 다섯 개의 모듈 CLAUDE.md와 네 개의 leaf build.gradle을 놓쳤다"고 같은 형태의 사고를 기록한다. - missing-verification: 없음
- relations:
reference:numbers-in-docs-should-be-derived·reference:omission-that-passes-is-not-a-gate
REFERENCE — 과대 진술 문서를 과소보다 먼저 고친다
- slug:
fix-overstatement-before-understatement - readiness:
READY - source:
final/document.md#7-4·analysis/19§6.3 - classification: 문서 드리프트에는 방향이 있고 위험이 대칭이 아니다. 과대 진술은 독자가 자기 안전장치를 생략하게 만들고, 과소 진술은 능력을 못 찾거나 잘못 고르게 만들지만 있는 방어를 제거하게 만들지는 않는다. 기준은 "이 문장을 믿은 팀이 무엇을 하지 않게 되는가"다.
- scope: 지원 매트릭스·capability 표·README의 보장 진술·javadoc의 현재 시제 서술. 이 저장소의 문서 드리프트 14건 중 P1은 하나뿐이고 그것이 과대 방향이다.
- exceptions: 과소 진술이라도 선택을 바꾸는 경우는 같은 우선순위다 — 선택 불가 브로커가 기능 목록과 함께 실린 경우가 그것이다.
- relations:
case:support-matrix-said-the-opposite-of-the-code·case:an-unselectable-broker-listed-with-features·case:the-readme-recipe-does-not-start
REFERENCE — 문서의 수치는 세지 말고 파생하거나 게이트로 붙든다
- slug:
numbers-in-docs-should-be-derived - readiness:
READY - source:
analysis/19§6.4 ·analysis/05§17 P3 - classification: 산문에 적은 수치는 다음 변경에서 드리프트한다. 기준은 "이 수치의 정본이 어디인가"이고, 정본이 있으면 문서는 세지 말고 가리켜야 한다. messaging 가족 문서가 자기 오류를 고치며 남긴 문장이 그 규칙이다 — "정확한 목록은 registry가 소유하므로 여기서 세지 않는다 — 세는 순간 다시 drift한다."
- scope: leaf 수·버전 목록·커버리지 수치·capability 개수. 대안은 (1) 세지 않고 SSOT를 가리키기, (2) 세야 한다면 그 수치를 검사하는 게이트를 두고 그 게이트의 walk 범위를 문서까지 넓히기.
- exceptions: 스냅샷임을 명시한 문서는 갱신하지 않는 것이 오히려 정확하다 —
00-project-overview.md가 초기 sizing을 그대로 두고 헤더에 그 사실을 적는 형태다. - relations:
case:five-documents-say-nineteen-leaves·reference:omission-that-passes-is-not-a-gate
DECISION — 문서 계약 테스트의 단언 범위를 capability 표까지 넓힐 것인가
- slug:
widen-doc-contract-assertions - readiness:
NEEDS_DECISION· decision-status:NOT_DECIDED - source:
final/document.md#7-4·analysis/19§9.3 - decision-evidence: 없음 — 분석의 권고이고 프로젝트가 선택한 기록이 없다
- grounds:
case:doc-contract-test-boundary-predicted-the-drift·case:support-matrix-said-the-opposite-of-the-code·reference:agreement-between-documents-proves-nothing - classification: capability 표는 기계로 검증 가능하다 —
CompatibilityMatrix.entries()가 어댑터 이름을 갖고, 각 어댑터의MessagingCapabilities는 코드 상수이며, 표는 마크다운 파이프 테이블이다. 그러나 프로젝트가 이 확장을 선택한 기록이 없고, 테스트 javadoc은 오히려 좁게 유지하는 근거를 적는다("Asserting on wording would make every edit a test failure and the check would be deleted"). 결정이 필요한 지점이며 이 노드는 생성되지 않는다. - relations:
case:doc-contract-test-boundary-predicted-the-drift·reference:fix-overstatement-before-understatement
DECISION — 지원 등급은 추론이 아니라 선언이고 증거 없이는 올라가지 않는다
- slug:
capability-grade-is-declared-not-inferred - readiness:
READY· decision-status:ADOPTED - source:
final/document.md#10-5·analysis/05§2.5 ·analysis/19§6.6 ·analysis/20§2.6 - decision-evidence:
.../api/capability/CapabilitySupport.java의 javadoc("evidence suite가 돌지 않은 capability는 컴파일이 된다는 이유로 STABLE이 되지 않는다") ·CompatibilityMatrix.Entry.hasLiveBrokerCertification()의 파생 구현 ·docs/adr/ADR-GRPC-ADV-001-capability-promotion-is-per-capability.md - grounds:
reference:grades-may-understate-never-overstate·reference:agreement-between-documents-proves-nothing - classification: 세 가족이 같은 결정을 각자 구현했다. JPA는
SupportLevel을 선언으로 두고 evidence card로 승격을 게이트하며, messaging은 등급을 boolean 필드에서 레인 증거 파생으로 바꿨고(그 전에는 Rabbit이true를 달고 있었는데 fault scenario가 한 번도 실행된 적 없었다), gRPC는 capability별 promotion gate를 ADR로 고정했다. - relations:
reference:grades-may-understate-never-overstate·case:three-versions-declared-one-executed·decision:candidate-evidence-stays-at-r1
TOPIC 7 — learning-transfer-between-families
CASE — 두 번째 플랫폼이 첫 번째의 bridge 부재는 막고 게이트 배선은 옮기지 않았다
- slug:
the-second-platform-carried-the-design-not-the-wiring - readiness:
READY - source:
final/document.md#7-6·analysis/20§2, §3 ·analysis/99§7.6 - code:
src/grpc/CLAUDE.md·src/config/architecture/modules.json·.../messaging-kafka/build.gradle·.github/workflows/messaging-certification.yml - evidence:
evidence/raw/tl-platform-suite-results.txt·evidence/raw/266-grpc-family-claims-verification.txt·evidence/raw/250-messaging-certification-gate-chain.txt - classification: gRPC 가족이 messaging을 명시적으로 참조하며 만들어졌고 목표를 문서에 적었다. 옮겨진 셋(bridge를 먼저 정함·framework-free 계약 leaf·속성 기반 전송 선택)은 전부 레지스트리와
build.gradle로 표현되는 규칙이고, 옮겨지지 않은 셋(증거 파생 등급·CI가 돌리는 게이트·조립에 연결된 시작 검증기)은 전부 Gradle 태스크와 CI YAML로 표현되는 규칙이다. - missing-verification: 이 분류가 인과인지 상관인지는 확인할 수 없다 — 작성자가 왜 후자를 옮기지 않았는지에 대한 기록이 없다
- relations:
reference:registry-rules-transfer-ci-rules-do-not·case:a-release-gate-with-no-evidence-producer·case:the-same-rotation-defect-closed-once-and-reproduced·decision:grpc-stays-build-only-until-the-bridge-is-decided
CASE — 같은 자격 증명 회전 결함이 한 가족에서 닫히고 다른 가족에서 재현됐다
- slug:
the-same-rotation-defect-closed-once-and-reproduced - readiness:
READY - source:
final/document.md#5-5·analysis/19§5.4 ·analysis/20§7.4 - code:
.../messaging-security/.../CredentialRuntimeRegistry.java·.../src/test/.../CredentialRotationContractTest.java·.../grpc-policy/.../security/GrpcCredentialRotationManager.java:89,103,119,120 - evidence:
evidence/raw/tl-rotation-defect-reproduced.txt·evidence/meta/…json·evidence/terminal/…svg - classification: messaging이 get → fetch → put → clear를 동기화 없이 하던 결함을 닫고 이력을 계약 테스트 javadoc에 남겼다 — "one replacement was dropped from the map without ever being cleared — a secret left in memory that nothing owns." gRPC의 회전 매니저는
AtomicReference를 쓰면서compareAndSet을 한 번도 쓰지 않고get()→set()으로만 다룬다(synchronized도 0). javadoc이 그 경합의 존재를 이미 알고 있다 — "the usual reason for one is two rotators racing".grpc-policy의 테스트 16개 중 동시성을 다루는 것이 없다. - missing-verification: 경합을 재현하는 동시성 테스트를 작성하지 않았다
- relations:
reference:atomic-type-is-not-atomicity·reference:registry-rules-transfer-ci-rules-do-not
REFERENCE — 레지스트리로 표현된 규칙은 전이되고 CI로 표현된 규칙은 전이되지 않는다
- slug:
registry-rules-transfer-ci-rules-do-not - readiness:
READY - source:
final/document.md#7-6·analysis/99§7.6 - classification: 한 팀이 배운 규칙을 다음 팀이 이어받는지는 그 규칙이 어디에 적혀 있는가로 갈린다. 레지스트리 항목·
build.gradle선언·타입 시그니처로 표현된 규칙은 다음 사람이 같은 파일을 편집하면서 마주치고 따라 한다. Gradle 태스크와 CI YAML로 표현된 규칙은 마주치지 않는다. - scope: 플랫폼 가족·모듈 템플릿·복제되는 구조. 관측: gRPC가 messaging에서 옮겨온 셋은 전부 레지스트리/
build.gradle표현이고 옮기지 않은 셋은 전부 Gradle 태스크/CI YAML 표현이다. - exceptions: 표본이 두 가족뿐이라 인과인지 상관인지 확정할 수 없다. 안전한 형태는 "전이되지 않는다"가 아니라 **"CI로만 표현된 규칙은 복제 시 명시적으로 옮겨야 한다"**는 체크리스트 항목이다.
- relations:
case:the-second-platform-carried-the-design-not-the-wiring·case:a-release-gate-with-no-evidence-producer
DECISION — gRPC 플랫폼은 build-only로 두고 애플리케이션 도달 경로를 먼저 정한다
- slug:
grpc-stays-build-only-until-the-bridge-is-decided - readiness:
READY· decision-status:ADOPTED - source:
final/document.md#2-3·analysis/20§2.3, §2.6 - decision-evidence:
docs/adr/ADR-GRPC-001-platform-family-and-registry-shape.md·modules.json(18 leaf 전부runtime_memberships: [],adapter-inbound-grpc의allowed_dependencies에 이 가족 없음) ·src/grpc/CLAUDE.md·docs/compatibility/grpc-support-matrix.md("Not released … build-only") - grounds:
case:the-second-platform-carried-the-design-not-the-wiring·reference:registry-rules-transfer-ci-rules-do-not - classification: messaging이 starter를
app-bootstrap의존으로 넣으면서 18 leaf가 출하 아티팩트에 실렸고 MSG-015가 실재 문제가 됐다. gRPC 가족은 그 선을 넘지 않기로 하고 레지스트리로 강제하며, 배선하려면runtime_memberships를 먼저 바꾸고verifyRuntimeModuleMembership을 통과시켜야 한다. 운영 문서가 그 상태를 정확히 공시한다는 점에서 messaging의 지원 매트릭스와 대비된다. - relations:
case:the-second-platform-carried-the-design-not-the-wiring·case:a-release-gate-with-no-evidence-producer·reference:runtime-membership-decides-severity
TOPIC 8 — owner-safe-state-machines
CONCEPT — fenced lease — 만료 시각만으로는 부족한 이유
- slug:
fenced-lease - readiness:
READY - source:
final/document.md#4-1,#4-3·analysis/05§10 ·analysis/19§7.3 - code:
db/migration/jpa/…및db/migration/messaging/V2__messaging_outbox_lease_fencing.sql헤더 ·.../fileserver/…V3 마이그레이션 - classification: lease가 "언제 끝나는가"만 기록하고 "누가 들고 있는가"를 기록하지 않으면 만료를 지난 worker가 여전히 쓸 수 있다. V2 마이그레이션 헤더가 그 시나리오를 3단계로 적는다 — relay A가 claim하고 브로커를 부름 / lease 만료, relay B가 재claim하고 발행하고 PUBLISHED 기록 / relay A가 타임아웃 후 그 위에 AMBIGUOUS를 씀. 그리고 결정적 문장 — "Making the lease longer than the publish timeout lowers the odds; it does not turn a GC pause, a scheduler stall..." 해법은 소유자와 fencing token을 행에 기록하고 terminal write가 그 튜플로 매칭하는 것이다. 같은 결함이 이 저장소에서 최소 세 곳(messaging outbox·fileserver cleanup·notification dispatcher)에 나타났다.
- missing-verification: 없음 — 마이그레이션과 claim SQL을 코드로 확인했다
- relations:
case:a-lease-without-an-owner·reference:cas-tuple-in-the-where-clause·reference:expired-claim-and-expired-execution-differ
CONCEPT — CAS 튜플과 update count가 답이 되는 구조
- slug:
cas-tuple-and-update-count - readiness:
READY - source:
final/document.md#4-1·analysis/05§10.1, §10.3 - code:
.../postgresql/idempotency/IdempotencyTransitionGateway.java의 다섯 statement ·.../outbox/PostgreSqlPollingDeliveryAdapter.java의 completion CAS 셋 - classification: 상태 전이를 "읽고 → 판단하고 → PK로 update"하면 그 사이에 takeover한 worker의 상태를 덮어쓴다. 이 저장소의 형태는 소유권 튜플 전체(scope · owner token · attempt · claim operation id · state revision)를 where 절에 반복하고 update count 자체를 답으로 쓰는 것이다 — 1이면 이 owner가 그 revision에서 여전히 owner였고, 0이면 다른 무언가가 record를 움직였으니 caller는 자기 view를 현재로 취급하면 안 된다. polling delivery는 여기에 authority
EXISTS서브쿼리를 더해 cutover를 가로지르지 못하게 한다. - missing-verification: 컨테이너 레인 미실행 — 동시 claim에서 실제로 0행이 나오는지 관측하지 않았다
- relations:
reference:cas-tuple-in-the-where-clause·case:native-claim-did-not-bump-the-version·open-question:v2-state-machine-lanes-not-executed
CONCEPT — capability_schema_registry — 스키마 적용과 사용 승인의 분리
- slug:
capability-schema-registry - readiness:
READY - source:
final/document.md#4-1·analysis/05§8.4, §11.3 - code:
db/migration/postgresql/V6__capability_schema_registry_adoption.sql· 각 capability 스트림의 V1 ·.../notification/NotificationSchemaActivation.java - classification: 독립 Flyway 스트림 설계의 접착제다. 각 capability 스트림의 V1이 (1) 선행조건 검사(
DO $$ ... RAISE EXCEPTION으로 core epoch가 ACTIVE인지), (2) 테이블 생성, (3) 자기를INSTALLED_INACTIVE로 등록의 세 단계를 밟고, 어댑터가 런타임에capability_id+core_epoch+feature_revision+lifecycle_state='ACTIVE'를 조회해 확인한다. 그래서 "스키마가 적용됐다"와 "capability를 써도 된다"가 분리된다. 확인은 startup에서만 하고 호출마다 하지 않는 이유("승격되지 않은 스트림은 배포 상태이고, 매 호출마다 묻는 것은 프로세스가 도는 동안 바뀔 수 없는 질문에 round trip을 넣는 것")도 이 개념의 일부다. - missing-verification: 없음
- relations:
decision:capability-separates-installation-from-activation·concept:independent-flyway-streams·case:registry-column-too-short-for-its-own-path
CASE — lease가 만료 시각만 기록하고 소유자를 기록하지 않아 terminal state가 되돌려졌다
- slug:
a-lease-without-an-owner - readiness:
READY - source:
final/document.md#4-3·analysis/19§7.3 ·analysis/08§V3 - code:
db/migration/messaging/V2__messaging_outbox_lease_fencing.sql· fileserver V3 마이그레이션 - evidence: 없음 — 마이그레이션 헤더가 사후 기록이다
- classification: V1이
lease_expires_at만 기록해 claim이 "언제 끝나는가"만 말하고 "누가"를 말하지 않았고, relay의 terminal write가message_id만으로 매칭했다. 그래서 lease를 지나 멈춰 있던 worker가 다른 worker가 이미 PUBLISHED로 옮긴 행에 AMBIGUOUS를 덮어썼고 행이 다시 claim 가능해져 메시지가 두 번 발행됐다. 같은 결함이 fileserver cleanup(claim이 owner·token·expiry를 기록하지 않아 죽은 worker의 항목이 영영 IN_PROGRESS로 남음)과 notification dispatcher에도 있었다. - missing-verification: 컨테이너 레인 미실행
- relations:
concept:fenced-lease·reference:cas-tuple-in-the-where-clause·open-question:v2-state-machine-lanes-not-executed
CASE — transition digest가 "누가·언제"만 덮고 "무엇"을 덮지 않아 다른 전이를 같다고 보고했다
- slug:
a-digest-that-covered-who-but-not-what - readiness:
READY - source:
analysis/05§10.1 - code:
.../postgresql/idempotency/IdempotencyDigestPolicy.java - evidence: 없음 — javadoc의 사후 기록
- classification: digest가
transition|operationId|ownerToken|attempt|stateRevision이었고 무엇을 했는지를 전혀 덮지 않았다. retryable로 기록된FAIL과 abandoned로 기록된FAIL이 같은 digest를 냈고, 서로 다른 응답이나 서로 다른 retention을 가진 두 completion도 그랬다. digest를 비교하는 replay는 "차이 전체가 중요한 부분인 두 전이"를 같다고 결론지었다. 수정은semanticArguments(disposition·retention·response digest·codec identity)를 포함하고 길이 프레이밍으로 구성하는 것 — 모든 구성요소가 가변 폭 텍스트이고 최소 하나(owner token)는 플랫폼이 제약할 것이 아니므로 delimiter join은 서로 다른 목록을 같은 문자열로 렌더링할 수 있다.VERSION을 붙여 구성이 바뀌면 저장된 digest가 가로질러 비교되지 않게 한다. - missing-verification: 없음
- relations:
reference:digest-must-be-length-framed-and-versioned·concept:cas-tuple-and-update-count
CASE — 활성 트랜잭션 검사가 data source를 묻지 않아 다른 커넥션에서 커밋됐다
- slug:
an-active-transaction-check-that-asked-the-wrong-question - readiness:
READY - source:
final/document.md#4-1·analysis/05§10.1 - code:
.../postgresql/idempotency/IdempotencyCapabilityGuard.java - evidence: 없음 — javadoc의 사후 기록
- classification: store가 스레드에 활성 read-write 트랜잭션이 있는지 확인했는데 그건 어떤 data source에서든 어떤 트랜잭션이든 열려 있으면 참이다. 형제 outbox/inbox 어댑터는
hasResource(dataSource)를 확인하는데 그것이 실제로 중요한 질문이다. 차이는 data source가 둘인 애플리케이션에서 드러난다 — 다른 쪽의 트랜잭션 안에서 발행된 mutation이 옛 검사를 통과하고, 이 store의 커넥션에서 트랜잭션 밖으로 돌고, 원자적이어야 했던 작업과 독립적으로 커밋됐다. - missing-verification: 두 data source 구성으로 재현하지 않았다
- relations:
reference:cas-tuple-in-the-where-clause·concept:cas-tuple-and-update-count
CASE — 만료된 CLAIMED는 takeover하고 만료된 EXECUTING은 조정을 요구하도록 갈랐다
- slug:
expired-claim-versus-expired-execution - readiness:
READY - source:
analysis/05§10.1, §10.4 - code:
.../postgresql/idempotency/PostgreSqlOwnerSafeIdempotencyStore.java의 claim 결정 트리 ·.../inbox/PostgreSqlSameStoreInboxAdapter.java - evidence: 없음 — 결정 트리와 그 javadoc
- classification: 만료된 lease를 일률적으로 takeover하면 이미 실행이 시작된 작업을 blind retry하게 된다. 이 저장소는 상태로 나눈다 — 만료된
CLAIMED는resetClaim으로 takeover하고, 만료된EXECUTING은abandonExpiredExecution으로ABANDONED에 넣고RecoveryRequired를 반환한다. inbox도 같은 축을 쓰되RECEIVED(takeover 가능)와PROCESSING(→ DEAD, recovery-required)로 나눈다. 즉 "claim만 했다"와 "실행에 들어갔다"가 만료 시 다른 결론을 낳는다. - missing-verification: 컨테이너 레인 미실행
- relations:
reference:expired-claim-and-expired-execution-differ·concept:fenced-lease·open-question:v2-state-machine-lanes-not-executed
CASE — native claim이 @Version을 올리지 않아 충돌을 보고하지 않는 낙관적 잠금이 됐다
- slug:
native-claim-did-not-bump-the-version - readiness:
READY - source:
final/document.md#4-1·analysis/05§11.2 - code:
.../notification/RecipientClaimSql.java의 claim statement - evidence: 없음 — statement와 그 주석
- classification: claim이 native
UPDATE인데 JPA@Version컬럼을 올리지 않으면, claim 전에 로드된 managed 엔티티가 여전히 옛 version을 들고 있고 그 flush가 성공하면서 lease와 state를 pre-claim 값으로 덮어쓴다. native statement가 충돌이 있었다고 말해주지 않았기 때문에 충돌을 보고하지 않는 낙관적 잠금이 된다. 수정은 statement에version = d.version + 1을 넣는 것이고, 그 한 줄에 주석이 붙어 있다. - missing-verification: 컨테이너 레인 미실행
- relations:
concept:cas-tuple-and-update-count·reference:cas-tuple-in-the-where-clause·reference:a-contract-test-must-run-the-adapters-statement
REFERENCE — CAS 튜플을 where 절에 전부 반복하고 update count를 답으로 쓴다
- slug:
cas-tuple-in-the-where-clause - readiness:
READY - source:
final/document.md#9규칙 17 ·analysis/05§10, §16 - classification: 읽고 나서 PK만으로 update하면 그 사이 takeover한 worker의 상태를 덮어쓴다. 소유권을 이루는 모든 값을 where 절에 반복하고 update count를 판정에 쓰면, 1은 "여전히 내 것"이고 0은 "누군가 움직였다"를 뜻한다.
- scope: lease·claim·상태 전이를 갖는 모든 관계형 어댑터. 튜플 구성은 보통 scope·owner token·attempt·claim operation id·state revision이고, 권한 경계가 있으면 authority
EXISTS를 더한다. - exceptions: 단일 writer가 보장되는 경로(마이그레이션·관리 작업)는 필요 없다. 다만 그 보장이 배포 형태(단일 인스턴스)에서 오면 다중 인스턴스로 가는 날 깨지므로, 보장의 출처를 적어야 한다.
- relations:
concept:cas-tuple-and-update-count·case:a-lease-without-an-owner·case:native-claim-did-not-bump-the-version
REFERENCE — 시간은 DB에서, 그리고 행을 잠근 다음에 읽는다
- slug:
read-the-clock-after-the-lock - readiness:
READY - source:
final/document.md#9규칙 17 ·analysis/05§10, §16 - classification: 애플리케이션 시계로 lease 만료를 판단하면 lease를 쓴 머신이 아닌 곳에서 판단하는 것이고, 락 전에 DB 시계를 읽으면 행이 바뀔 수 있는 시점 이전의 순간으로 판단하는 것이다. 순서는
for update→clock_timestamp()다. - scope: lease·claim·만료 판정을 갖는 모든 SQL.
now()가 아니라clock_timestamp()인 이유도 같은 축이다 — 전자는 트랜잭션 시작 시각으로 고정된다. - exceptions: 읽기 전용 조회에서 만료 여부를 보고만 하는 경우는 잠글 필요가 없다. 다만 그 결과로 행동하면 안 된다.
- relations:
reference:cas-tuple-in-the-where-clause·concept:cas-tuple-and-update-count
REFERENCE — 만료된 claim과 만료된 실행은 다르게 다뤄야 한다
- slug:
expired-claim-and-expired-execution-differ - readiness:
READY - source:
analysis/05§10.1, §10.4 - classification: lease 만료는 "누가 들고 있었는가"만 말하고 "무엇까지 했는가"를 말하지 않는다. claim만 한 상태의 만료는 안전하게 takeover할 수 있지만, 실행에 들어간 상태의 만료는 외부 부수효과가 이미 발생했을 수 있으므로 blind retry가 아니라 조정으로 보내야 한다.
- scope: 실행 전 예약과 실행 자체를 구별하는 모든 작업 큐·멱등성 저장소. 구현은 상태를 둘로 나누는 것이다(
CLAIMED/EXECUTING,RECEIVED/PROCESSING). - exceptions: 외부 부수효과가 없는 순수 계산 작업은 구별이 필요 없다. 다만 "부수효과 없음"이 유지되는지는 시간이 지나며 바뀌므로 그 전제를 적어야 한다.
- relations:
case:expired-claim-versus-expired-execution·concept:fenced-lease·reference:unknown-is-a-third-result
REFERENCE — digest는 길이 프레이밍하고 버전을 붙인다
- slug:
digest-must-be-length-framed-and-versioned - readiness:
READY - source:
analysis/05§10.1 - classification: 가변 폭 문자열을 delimiter로 이어 digest를 만들면 서로 다른 구성요소 목록이 같은 문자열로 렌더링될 수 있다. 길이 프레이밍(
len:value)이 그것을 막고, 버전 번호가 구성 변경 전후의 digest를 가로질러 비교하지 못하게 한다. 그리고 digest는 "누가·언제"뿐 아니라 **"무엇을"**까지 덮어야 한다. - scope: replay 판정·중복 탐지·전이 동일성 비교에 쓰는 모든 digest. 구성요소 중 하나라도 플랫폼이 제약하지 않는 값(외부 토큰)이면 프레이밍이 필수다.
- exceptions: 모든 구성요소가 고정 길이이거나 플랫폼이 문법을 강제하는 값이면 delimiter로 충분하다. 다만 그 강제가 어디 있는지 적어야 한다.
- relations:
case:a-digest-that-covered-who-but-not-what·reference:names-are-registry-keys-not-values
OPEN QUESTION — V2 상태 기계 넷의 컨테이너 레인이 실행되지 않았다
- slug:
v2-state-machine-lanes-not-executed - readiness:
OPEN - source:
final/document.md#6-2·analysis/05§15.5 - known: 네 상태 기계(idempotency·outbox-storage·outbox-polling·inbox)가
readiness-cards.yaml에서implemented-candidate상태다 · 각 card의evidence.scenarios가 exact selector를 고정한다 · 후보 검증은 R1에 머문다 - unknown: 네 레인이 이 리비전에서 통과하는지 · 동시 claim에서 CAS가 실제로 0행을 내는지 · trigger 기반 fencing(
trg_fence_legacy_outbox_writer)이 예상대로 발화하는지 - next-verification:
./gradlew :adapter:outbound:persistence-jpa:postgresqlIdempotencyIntegrationTest postgresqlOutboxStorageIntegrationTest postgresqlOutboxPollingIntegrationTest postgresqlInboxIntegrationTest— 각각 readiness card의 producer다 - decision-criterion: 넷 다 zero-skip으로 통과하면 네 Case의
missing-verification을 지운다. 하나라도 실패하면 그 실패가 새 Case가 되고 해당 card의 state를 재검토한다 - relations:
case:a-lease-without-an-owner·case:expired-claim-versus-expired-execution·case:native-claim-did-not-bump-the-version·open-question:container-lanes-not-executed
DECISION — capability는 스키마 적용과 사용 승인을 분리한다
- slug:
capability-separates-installation-from-activation - readiness:
READY· decision-status:ADOPTED - source:
final/document.md#4-1·analysis/05§8.4, §11.3 - decision-evidence:
db/migration/postgresql/V6__capability_schema_registry_adoption.sql· 각 capability 스트림 V1의 3단계 구조 ·.../NotificationSchemaActivation.java의 startup 확인과 javadoc - grounds:
concept:capability-schema-registry·concept:independent-flyway-streams - classification: 스키마가 적용된 것과 capability를 써도 되는 것을 분리하고, 후자를
lifecycle_state='ACTIVE'로 표시한다. activation 검증기의 javadoc이 이 결정이 없을 때의 두 실패 모양을 적는다 —ddl-auto=validate에서는 기능을 끈 배포가 테이블 부재로 boot 실패하고,ddl-auto=none이면 boot는 성공하고 첫 발송이 rawrelation does not exist를 처음 보낸 사람에게 반환한다. - relations:
concept:capability-schema-registry·decision:flyway-owns-the-schema
DECISION — 상태 기계 구현은 Spring stereotype을 갖지 않는다
- slug:
state-machines-carry-no-stereotype - readiness:
READY· decision-status:ADOPTED - source:
analysis/05§10 - decision-evidence: 네 V2 구현의 클래스 선언과 그 javadoc("두 composition root가
dev.caskeleton.adapter를 component-scan하므로@Repository를 붙이면 선택하지 않은 배포에서도 빈이 된다") - grounds:
reference:off-must-be-structural·reference:a-bean-is-not-composition-evidence - classification: 스테레오타입을 붙이면 두 합성 루트의 스캔이 그것을 잡아, capability를 선택하지 않은 배포에서도 빈이 된다. 그래서 네 구현이 전부 plain class이고 협력자를 주입이 아니라 생성자에서 조립한다 — "이건 이 store의 부품이지 애플리케이션이 조립하거나 교체하는 서비스가 아니고, 주입하면 public bean 표면이 1개에서 6개로 넓어진다."
- relations:
reference:off-must-be-structural·decision:one-root-owns-the-master-switch
TOPIC 9 — transaction-deadline-and-pool
CONCEPT — 호출 예산에서 DB 로컬 타임아웃까지의 데드라인 전파
- slug:
deadline-propagation - readiness:
READY - source:
final/document.md#3-2·analysis/05§3.3 - code:
.../transaction/TransactionDeadlineCalculator.java·.../postgresql/PostgreSqlLocalTimeoutConfigurer.java·.../h2/H2LocalTimeoutConfigurer.java - classification: 호출자의 남은 예산이 세 단계로 좁혀져 DB 세션 설정에 도달하는 구조의 설명이다. 획득 전에는
connectionTimeout + beginBudget + minimumActionWindow + completionMargin을 요구하고 Spring의 초 단위 타임아웃이 1초 미만이면 시작하지 않는다. begin 이후에는 statement/lock/idle 셋을 각각 유도하고 하나라도 1ms 미만이면 거부한다. DB에 미는 방법이SET이 아니라set_config(..., true)인 이유가 둘이다 —SET은 파라미터 바인딩 전에 파싱되어 syntax error가 나고, 함수 호출은 값이 statement text에서 빠진다. 세 번째 인자true가 transaction-local을 뜻한다. H2 구현이 두 가지에서 다른데(세션 스코프, idle 가드 없음) 그것이 H2의 성질이지 선택이 아니라는 점도 함께 적혀 있다. - missing-verification: 실제 세션에서
SHOW statement_timeout으로 적용을 확인하지 않았다 - relations:
reference:deadline-narrows-in-three-stages·reference:session-scoped-settings-outlive-the-transaction·case:a-five-second-string-that-broke-every-prod-deploy
CONCEPT — REQUIRES_NEW의 커넥션 비용과 풀 사이징 제약
- slug:
requires-new-connection-cost - readiness:
READY - source:
final/document.md#3-2·analysis/05§3.1, §13.2 - code:
.../transaction/SpringTransactionPort.java(inNew) ·.../experimental/multitenancy/TenantPoolBudget.java - classification:
REQUIRES_NEW는 바깥 트랜잭션의 커넥션을 핀한 채로 새 물리 JDBC 커넥션을 딴다. 그래서 풀 사이징 제약이 곱셈이 된다 —maximumPoolSize >= (concurrent_threads × (1 + max_inNew_depth)) + 1. 레코드마다inNew를 도는 루프가 금지인 이유(풀 고갈 + 데드락)와, 같은 곱셈 함정이 database-per-tenant에서 어떻게 반복되는지(각 tenant 풀은 개별적으로 합리적이고 그 합이 아니다 — 50 tenant × 10 = 서버max_connections100에 500 커넥션, 실패는 idle이던 것 포함 모든 tenant에 동시에 도착)를 함께 다룬다. - missing-verification: 풀 계약 레인 미실행
- relations:
case:requires-new-pins-the-outer-connection·case:tenant-pools-summed-past-the-server-ceiling·open-question:pool-contract-lane-not-executed
CASE — connection-timeout: 5s가 모든 prod 배포를 시작 실패시켰고 local만 통과했다
- slug:
a-five-second-string-that-broke-every-prod-deploy - readiness:
READY - source:
final/document.md#4-1·analysis/05§14.6 - code:
.../app-bootstrap/src/main/resources/application.yml의 hikari 블록 주석 ·.../HikariPoolConstraintValidator.java - evidence: 없음 — application.yml 주석의 사후 기록
- classification:
connection-timeout이HikariConfig#setConnectionTimeout(long)에 바인딩되므로 binder가"5s"를 즉시 거부한다."5s"로 출시됐고 모든 prod/dev 배포가 시작에 실패했는데 local은application-local.yml이 값을 override해서 통과했다 — prod 레인만이 그것을 찾을 수 있었다. 그리고HikariPoolConstraintValidator는 이 키를DurationStyle로 읽어서"5s"를 기꺼이 받아들인다 — 그 관대함이 잘못된 기본값을 지원되는 것처럼 보이게 만들었다. - missing-verification: 없음
- relations:
concept:deadline-propagation·reference:local-with-a-different-db-is-a-different-system·case:a-validator-checking-the-wrong-datasource
CASE — validator가 요청을 서비스하지 않는 datasource를 검증하고 있었다
- slug:
a-validator-checking-the-wrong-datasource - readiness:
READY - source:
analysis/05§14.5 - code:
.../app-bootstrap/.../persistencejpa/JpaDataSourceProfileValidator.java의 javadoc - evidence: 없음 — javadoc의 사후 기록
- classification: validator가
app.jpa-platform.datasource.*에 바인딩된 settings를 읽었는데 요청을 서비스하는 풀은spring.datasource.hikari.*에서 만들어진다 — 하나의 풀에 두 개의 기술, 그리고 validator는 사용되지 않는 기술에 대해 통과할 수 있다. 더 나쁜 건 그 평행 네임스페이스가 어떤 shipped YAML에도 env-key 레지스트리의 어떤 행에도 없어서 두 필드가 항상 null이었고requirePoolBounds가 모든 배포에서 던졌을 것이라는 점이다 — 아무것도 그것을 호출하지 않아서 아무것도 실패하지 않았다. 서로를 상쇄한 두 결함이고, 애플리케이션이 시작한 이유는 두 번째가 첫 번째를 숨겼기 때문이다. - missing-verification: 없음
- relations:
reference:a-bean-is-not-composition-evidence·case:a-five-second-string-that-broke-every-prod-deploy
CASE — REQUIRES_NEW가 바깥 커넥션을 핀한 채 새 커넥션을 딴다
- slug:
requires-new-pins-the-outer-connection - readiness:
READY - source:
final/document.md#3-2·analysis/05§3.1 - code:
.../transaction/SpringTransactionPort.java의inNew와 그 javadoc(application-port D12) - evidence: 없음 — 제약이 javadoc에 수식으로 적혀 있다
- classification:
inNew가 편해 보이지만 커넥션 비용이 곱셈이라는 것을 문서화한 제약이다. 풀 사이징 부등식이 명시돼 있고, 레코드마다inNew를 도는 루프가 금지이며(풀 고갈 + 데드락), 배치로 묶거나 루프를 트랜잭션 밖으로 빼야 한다. 같은 이유로inRootWrite가REQUIRES_NEW로 suspend해서 "root인 척"하지 않고 fail-fast한다. - missing-verification: 풀 계약 레인 미실행 — 포화 시 pending count 보고와 대기 동작을 관측하지 않았다
- relations:
concept:requires-new-connection-cost·decision:in-root-write-fails-fast·open-question:pool-contract-lane-not-executed
REFERENCE — 데드라인은 호출 예산에서 시작해 세 단계로 좁힌다
- slug:
deadline-narrows-in-three-stages - readiness:
READY - source:
analysis/05§3.3 - classification: 상류 호출자의 남은 시간이 트랜잭션·statement·lock 순으로 좁혀져야 하고, 각 단계에서 남은 시간이 다음 단계의 최소 요구를 못 채우면 시작하지 않는 것이 옳다. 시작해서 중간에 잘리면 completion-unknown을 만들지만 시작하지 않으면 확정 거부다.
- scope: 호출 예산을 전파하는 모든 계층. 각 단계에 여유(margin)를 두어 마지막에 결과를 기록할 시간을 남긴다.
- exceptions: 예산을 모르는 진입점(스케줄러·부팅 작업)은 자기 상한을 갖되 그것이 무한이 아니어야 한다.
- relations:
concept:deadline-propagation·reference:write-transactions-need-a-finite-timeout
REFERENCE — 쓰기 트랜잭션에는 유한 타임아웃이 필수다
- slug:
write-transactions-need-a-finite-timeout - readiness:
READY - source:
analysis/05§2.3 - classification: 무제한 write 트랜잭션은 statement 하나가 막히면 커넥션·락·row version을 무한정 잡는다. 기준은 "이 프로파일이 read-only가 아닌데 타임아웃이 없는가"이고, 있으면 생성자가 거부해야 한다.
- scope: 트랜잭션 프로파일·정책 값 타입.
TransactionProfile의 생성자가!readOnly && timeout이 null/0/음수를 거부하는 형태다. - exceptions: 읽기 전용 트랜잭션은 상한이 있으면 좋지만 필수는 아니다 — 락을 잡지 않기 때문이다. 다만 락을 잡는 read(
FOR UPDATE)는 쓰기와 같이 취급한다. - relations:
reference:deadline-narrows-in-three-stages·reference:make-the-unsafe-state-unrepresentable
REFERENCE — 세션 스코프 설정은 풀로 돌아간 커넥션에 남는다
- slug:
session-scoped-settings-outlive-the-transaction - readiness:
READY - source:
analysis/05§3.3, §13.2 - classification:
SET으로 건 설정은 커넥션이 풀로 돌아가도 살아남아 다음 borrower가 상속한다. 다음 borrower가 다른 tenant이거나 tenant 없는 백그라운드 job이면 그 설정이 격리 경계를 무너뜨린다. 기준은 "이 설정이 트랜잭션과 함께 되돌아가는가"이고, PostgreSQL에서는set_config(..., true)가 그것을 보장한다. - scope:
statement_timeout·lock_timeout·search_path·app.tenant_id등 세션 상태 전부. RLS의 tenant 바인딩이 transaction-local이어야 하는 이유가 같다. - exceptions: 대응물이 없는 벤더(H2의 idle 가드)에서는 세션 스코프를 인정하되 그 사실을 적고 caller-side deadline에 맡긴다 — "적용했다"고 거짓 보고하지 않는다.
- relations:
concept:deadline-propagation·concept:rls-three-preconditions·case:search-path-survived-the-return-to-the-pool
OPEN QUESTION — 풀 계약 레인이 실행되지 않아 포화 동작이 확인되지 않았다
- slug:
pool-contract-lane-not-executed - readiness:
OPEN - source:
final/document.md#6-2·analysis/05§15.3 - known:
jpaPlatformPoolContractTest가 threshold를 약속하지 않고 행동 계약만 검증하도록 개명됐다 · 검증 항목은REQUIRES_NEWdepth 1이 동시 스레드당 커넥션 2개를 요구하는 것, 포화된 풀이 pending count를 보고하는 것, caller가 커넥션 없이 진행하지 않고 기다리는 것 - unknown: 이 리비전에서 그 세 계약이 실제로 성립하는지 · 풀 사이징 부등식이 현재 기본값(
maximumPoolSize10,minimum-idle2)에서 어떤 동시성까지 견디는지 - next-verification:
./gradlew :adapter:outbound:persistence-jpa:jpaPlatformPoolContractTest --console=plain - decision-criterion: 세 계약이 통과하면
case:requires-new-pins-the-outer-connection의missing-verification을 지운다. 실패하면 그 실패와 현재 기본값의 관계를 새 Case로 기록한다 - relations:
case:requires-new-pins-the-outer-connection·concept:requires-new-connection-cost·open-question:container-lanes-not-executed
DECISION — 트랜잭션 템플릿은 모드별로 미리 만들어 둔다
- slug:
templates-are-built-once-per-mode - readiness:
READY· decision-status:ADOPTED - source:
final/document.md#3-2·analysis/05§3.1 - decision-evidence:
.../transaction/SpringTransactionPort.java의 생성자와 javadoc - grounds:
concept:requires-new-connection-cost·reference:write-transactions-need-a-finite-timeout - classification:
TransactionTemplate은 문서상 thread-safe지만 mutable이다. 호출마다 propagation/readOnly를 바꿔 쓰면 같은 빈을 공유하는 동시 요청 사이에 race window가 생긴다. 그래서 네 모드에 대해 템플릿 셋을 생성 시점에 만들어 두고(inWrite와inRootWrite는 실행 속성이 같아 공유하되 precondition만 다르다) 이후 변경하지 않는다. - relations:
case:requires-new-pins-the-outer-connection·decision:in-root-write-fails-fast
DECISION — inRootWrite는 suspend하지 않고 fail-fast한다
- slug:
in-root-write-fails-fast - readiness:
READY· decision-status:ADOPTED - source:
final/document.md#3-2·analysis/05§3.1 - decision-evidence:
.../transaction/SpringTransactionPort.java의inRootWrite구현과NestedRootTransactionRejectedException - grounds:
decision:templates-are-built-once-per-mode·concept:requires-new-connection-cost - classification: "root 트랜잭션이어야 한다"를
REQUIRES_NEW로 구현하면 호출자 트랜잭션과 독립 커밋되는 silent 의미 변경이 생긴다. 대신 활성 트랜잭션이 있으면 즉시 거부한다. 그리고TransactionTemplate.execute가 커밋 후에 값을 반환하므로inRootWrite의 결과는 post-commit에만 호출자에게 보이고 커밋 실패는 값 대신 예외로 전파된다. - relations:
decision:templates-are-built-once-per-mode·reference:unknown-is-a-third-result
TOPIC 10 — schema-ownership-and-capability-streams
CONCEPT — 독립 Flyway 스트림과 baseline version 0
- slug:
independent-flyway-streams - readiness:
READY - source:
final/document.md#4-1·analysis/05§8.3 - code:
.../persistence-jpa/.../notification/NotificationSchemaStream.java·.../postgresql/PostgreSqlPersistenceConfig.java:115·db/migration/** - classification: 이 저장소에 Flyway 스트림이 여덟 개이고 각각 자기 history 테이블을 갖는 이유의 설명이다. 뻔한 해법(디렉터리를 primary location 목록에 추가)이 틀린 이유는 두 트리가 다 V1부터 번호를 매기고 공유 history가 두 V1을 같은 버전으로 만들어 하나를 거부하거나 resolution 순서에 따라 건너뛰기 때문이다.
baselineVersion("0")+baselineOnMigrate(true)가 필요한 이유(core가 이미 채운 스키마 위에 자기 history를 만들어야 하므로 Flyway가 "history 없는 비어있지 않은 스키마"라며 거부한다)와, version 0이어야 하는 이유(더 높은 baseline은 그 스트림의 마이그레이션을 건너뛴다 — 이 설정이 보통 두려워하는 실패 모드)를 함께 다룬다. - missing-verification: 마이그레이션 레인 미실행
- relations:
case:two-trees-both-numbered-from-v1·concept:capability-schema-registry·decision:flyway-owns-the-schema
CASE — 두 트리가 다 V1부터 번호를 매겨 공유 history가 하나를 건너뛸 수 있었다
- slug:
two-trees-both-numbered-from-v1 - readiness:
READY - source:
final/document.md#4-1·analysis/05§8.3 - code:
.../notification/NotificationSchemaStream.java의 javadoc - evidence: 없음 — javadoc의 사후 기록
- classification: notification 마이그레이션이
db/migration/jpa/notification-platform에 있는데 primary location은db/migration/postgresql이라 아무것도 적용하지 않았다. 디렉터리를 목록에 추가하는 해법이 틀린 이유는V1__notification_platform_core와V1__initial_schema가 같은 버전이 되기 때문이다. 해법은 독립 스트림 + 자기 history 테이블이고, 그것이 capability를 진짜로 optional하게 만든다. - missing-verification: 없음
- relations:
concept:independent-flyway-streams·case:messaging-migrations-collide-at-v2
CASE — char(64)와 varchar(64) 불일치를 H2가 가리고 있었다
- slug:
h2-hid-a-column-type-mismatch - readiness:
READY - source:
final/document.md#4-1·analysis/05§8.5 - code:
db/migration/postgresql/V10__idempotency_request_hash_varchar.sql헤더 ·db/migration/jpa/idempotency/V2·.../IdempotencyRecordEntity.java - evidence: 없음 — 마이그레이션 헤더가 실패 메시지 전문을 담는다
- classification: V1이
request_hash를char(64)로 선언했고 엔티티는length = 64로 매핑해 Hibernate가varchar(64)로 읽는다. 실 PostgreSQL에ddl-auto=validate로 시작하면 실패했다 —found [bpchar (Types#CHAR)], but expecting [varchar(64)]. 로컬이 H2로 도는 한 보이지 않았다 — H2의 create-drop은 엔티티에서 스키마를 만들므로 절대 엔티티와 불일치할 수 없다. 수정이 엔티티가 아니라 마이그레이션 쪽인 이유와, V1을 in-place로 고치지 않은 이유("적용된 마이그레이션의 checksum은 이미 그것을 돌린 모든 배포에 대한 약속")가 함께 적혀 있다. 이 사건이application-local.yml을 PostgreSQL 기본으로 바꿨다. - missing-verification: 없음
- relations:
reference:local-with-a-different-db-is-a-different-system·reference:an-applied-checksum-is-a-promise
CASE — 레지스트리 컬럼이 38자 경로에서 짧아 "더 짧은 경로를 적는" 우회를 유혹했다
- slug:
registry-column-too-short-for-its-own-path - readiness:
READY - source:
analysis/05§8.4 - code:
db/migration/postgresql/V9__widen_capability_schema_stream.sql·db/migration/jpa/idempotency/V2 - evidence: 없음 — 마이그레이션 헤더의 사후 기록
- classification:
schema_stream이varchar(32)였고 작성 당시 모든 스트림에 맞았으며'db/migration/jpa/notification-platform'(38자)에서 안 맞기 시작했다. 실패 모드가 나쁜 종류다 — 모든 면에서 올바른 등록이value too long으로 마이그레이션 타임에 실패하고, 뻔한 우회책은 스트림의 실제 경로가 아닌 더 짧은 경로를 기록하는 것이며, 스키마가 어디서 왔는지에 대해 거짓말하는 레지스트리는 없는 것보다 나쁘다. 128로 넓힌 이유도 적혀 있다 —capability_id가 이미varchar(128)이고 하나의 bound가 두 개보다 추론하기 쉽다. - missing-verification: 없음
- relations:
concept:capability-schema-registry·reference:an-applied-checksum-is-a-promise
CASE — Flyway location customizer가 운영자가 바인딩한 값을 덮어썼다
- slug:
a-customizer-that-discarded-the-bound-property - readiness:
READY - source:
analysis/05§7.7 - code:
.../postgresql/PostgreSqlPersistenceConfig.java:94-115의 javadoc - evidence: 없음 — javadoc의 사후 기록
- classification: customizer가 무조건
locations(...)를 호출했는데 그것은 Spring이spring.flyway.locations에서 바인딩한 것을 대체한다. 그래서 운영자가SPRING_FLYWAY_LOCATIONS로 capability 스트림을 추가하고 Flyway가 성공적 마이그레이션을 보고하는 것을 보고도 벤더 스트림만 얻을 수 있었다 — 프로퍼티는 읽히고 바인딩되고 그 뒤에 도는 customizer가 버렸다.local-notification-ingest레인은 7개 location을 세팅하고 1개를 적용했다. 지금은 "아무도 고르지 않았을 때만 기여하고, 누군가 골랐으면 비켜선다". - missing-verification: 없음
- relations:
concept:independent-flyway-streams·reference:a-bean-is-not-composition-evidence
CASE — messaging 마이그레이션 두 leaf가 같은 디렉터리에서 V2를 둘 만들었다
- slug:
messaging-migrations-collide-at-v2 - readiness:
READY - source:
final/document.md#4-3·analysis/19§7.2 - code:
src/messaging/messaging-inbox-jdbc-postgresql/.../db/migration/messaging/V2__messaging_inbox.sql·src/messaging/messaging-outbox-jdbc-postgresql/.../db/migration/messaging/V2__messaging_outbox_lease_fencing.sql - evidence:
evidence/raw/259-messaging-flyway-migration-namespace.txt - classification: 두 leaf가 같은 classpath 리소스 디렉터리
db/migration/messaging/에 각자 번호를 매겨V2가 둘이다. 그 위치를 Flyway에 주는 순간 duplicate version으로 부팅이 실패한다. 지금 실패하지 않는 유일한 이유는 그 위치를 아무도 Flyway에 주지 않기 때문이고(main 코드에서db/migration/messaging을 부르는 곳 0건), 각 leaf의 IT는 자기 jar 리소스만 보므로 재현하지 못한다. 원 구현 계획서는 분리된 위치를 지정했었다. - missing-verification: 실제로 그 위치를 Flyway에 추가해 duplicate version 실패를 관측하지 않았다
- relations:
concept:independent-flyway-streams·case:two-trees-both-numbered-from-v1·case:outbox-chain-behind-an-unsatisfiable-condition
REFERENCE — 마이그레이션 스트림은 자기 history 테이블을 갖는다
- slug:
each-stream-owns-its-history-table - readiness:
READY - source:
analysis/05§8.3 - classification: 두 마이그레이션 트리가 하나의 history를 공유하면 버전 공간을 공유하게 되고, 각 트리가 독립적으로 번호를 매기는 한 충돌은 시간 문제다. 기준은 "이 트리의 버전 번호를 누가 정하는가"이고, 답이 둘 이상이면 스트림을 나누고 history를 분리한다.
- scope: capability별·모듈별로 나뉜 모든 마이그레이션. 분리 시
baselineVersion("0")+baselineOnMigrate(true)가 필요하고, 0이어야 그 스트림의 마이그레이션이 전부 돈다. - exceptions: 하나의 팀이 하나의 트리를 소유하고 그 안에서 번호를 조정할 수 있으면 분리가 불필요하다. sample composition이 두 location을 하나의 목록으로 합쳐 버전 공간을 공유하는 경우가 그 예이고, 그때는 번호 충돌을 사람이 피해야 한다.
- relations:
concept:independent-flyway-streams·case:two-trees-both-numbered-from-v1·case:messaging-migrations-collide-at-v2
REFERENCE — 로컬이 다른 DB면 로컬 테스트는 다른 시스템에 대한 진술이다
- slug:
local-with-a-different-db-is-a-different-system - readiness:
READY - source:
final/document.md#9규칙 18 ·analysis/05§8.5 - classification: 로컬이 in-memory DB로 돌고 스키마를 엔티티에서 생성하면 엔티티와 마이그레이션의 불일치가 구조적으로 발견 불가능하다. 기준은 "로컬과 prod가 같은 벤더·같은 마이그레이션 모드·같은 스키마 소유자를 쓰는가"이고, 아니면 "로컬에서는 됩니다"가 다른 시스템에 대한 진술이다.
- scope: 개발 환경 구성 전반. 이 저장소는 두 사건(
char(64),fs_cleanup_item) 이후 local을 PostgreSQL + Flyway +ddl-auto=validate로 바꾸고 H2를 별도 개발자 task로 밀었다. - exceptions: 빠른 단위 테스트용 in-memory는 유지해도 된다. 다만 그 레인이 스키마 계약을 증명한다고 주장하면 안 된다 — H2가
FOR UPDATE SKIP LOCKED를 실제로 지키는지 같은 벤더 차이는 측정하고 근거를 적어야 한다. - relations:
case:h2-hid-a-column-type-mismatch·reference:an-applied-checksum-is-a-promise
REFERENCE — 적용된 마이그레이션의 checksum은 그것을 돌린 모든 배포에 대한 약속이다
- slug:
an-applied-checksum-is-a-promise - readiness:
READY - source:
analysis/05§8.1, §8.5 - classification: 이미 적용된 마이그레이션을 in-place로 고치면 그것을 돌린 배포들의 history와 어긋난다. 기준은 "이 파일이 어딘가에 적용된 적이 있는가"이고, 있으면 새 버전을 추가하고 가드된 변환(
DO $$ ... IF EXISTS ... THEN ALTER)을 쓴다. 같은 이유로 Flywayrepair는 모드가 아니다 — history를 지금 디스크에 맞게 다시 써서 증거를 지워 증상을 해결한다. - scope: 모든 forward-only 마이그레이션. 스트림이 둘 이상이고 상대 순서가 고정되지 않았으면 같은 가드된 변환을 양쪽에 둔다.
- exceptions: 어디에도 적용된 적 없는 마이그레이션(방금 작성한 것)은 고쳐도 된다. 판정 근거는 개발자의 기억이 아니라 history 테이블이다.
- relations:
case:h2-hid-a-column-type-mismatch·case:registry-column-too-short-for-its-own-path·decision:repair-is-not-a-mode
DECISION — Flyway가 스키마를 소유하고 런타임 롤은 DDL 권한을 갖지 않는다
- slug:
flyway-owns-the-schema - readiness:
READY· decision-status:ADOPTED - source:
final/document.md#4-1,#10-3·analysis/05§8.1, §12.1 - decision-evidence:
docs/adr/ADR-JPA-004-flyway-schema-source-of-truth.md·.../security/PostgreSqlRuntimeRoleVerifier.java의 startup 질의 ·.../migration/FlywaySchemaPolicy.java의 프로필별 모드 - grounds:
reference:an-applied-checksum-is-a-promise·reference:local-with-a-different-db-is-a-different-system - classification: 스키마를 Flyway가 소유하고 ORM은 확인만 한다(
ddl-auto는none/validate만 허용). 그리고 그것을 강제 가능하게 만드는 것이 런타임 롤에서 DDL 권한을 뺀 것이다 — 서버에has_schema_privilege로 직접 물어 fail-closed로 거부한다. "애플리케이션 자신의 자격증명이 DDL을 실행할 수 없으면, 어떤 코드 경로도 어떤 라이브러리도 어떤 주입된 statement도 런타임에 스키마를 바꿀 수 없다." staging/prod는 배포 파이프라인이 마이그레이션을 소유하고 애플리케이션은VALIDATE_ONLY다. - relations:
decision:repair-is-not-a-mode·decision:capability-separates-installation-from-activation
DECISION — Repair는 모드가 아니라 운영자가 호출하는 작업이다
- slug:
repair-is-not-a-mode - readiness:
READY· decision-status:ADOPTED - source:
analysis/05§8.1 - decision-evidence:
.../migration/FlywaySchemaPolicy.java(모드 enum에 repair 없음) ·.../FlywayValidationGate.java의 javadoc - grounds:
reference:an-applied-checksum-is-a-promise·decision:flyway-owns-the-schema - classification: Flyway의
repair는 schema history를 지금 디스크에 있는 스크립트에 맞게 다시 쓴다 — 증거를 지워서 증상을 해결한다. checksum mismatch는 배포된 스크립트가 적용된 것과 다르다는 뜻이고 흥미로운 질문은 "어떤 변경이 이 DB에 빠졌는가"인데, repair는 그 질문을 물을 수 없게 만들어 답한다. 그래서 startup 동작이 아니라 운영자가 의도적으로 호출하는 operation descriptor로만 존재한다. - relations:
decision:flyway-owns-the-schema·reference:an-applied-checksum-is-a-promise
TOPIC 11 — redis-command-admission
CONCEPT — 명령 카탈로그와 admission 아홉 단계
- slug:
redis-admission-stages - readiness:
READY - source:
final/document.md#4-4·analysis/10§64 - code:
.../cache-redis/.../CommandPolicyGuard.java·.../RedisCommandCatalog.java·.../RedisCommandGateway.java - classification: 이 SDK가 명령 하나를 내보내기 전에 지나는 단계의 설명이다 — 카탈로그 분류(BLOCKED·R3·R4 거부) · capability/최소 버전 확인 · permit provenance 검증 · 네임스페이스 검사 · Cluster 동일 슬롯 검사 · 요청 예산 · 정책 기반 레인·타임아웃 유도 · 실패 번역 · 관측. 그리고 그 위에 얹힌 계약 — gateway는 "everything routed through it has already passed
CommandPolicyGuard"를 전제하므로 정책·permit·예산·타임아웃·관측을 자기 관심사로 두지 않는다. 카탈로그가 미분류 명령을 fail-closed로 거부하는 것이 이 구조의 바닥이다. - missing-verification: 토폴로지 레인 미실행 — 실제 서버에서 각 단계가 도는지 관측하지 않았다
- relations:
case:five-adapters-bypass-the-single-admission-point·reference:a-single-admission-point-must-count-its-bypasses·decision:unclassified-commands-are-refused
CASE — "build gate"라 불리는 catalog drift 검사가 어디에서도 실행되지 않는다
- slug:
a-build-gate-that-is-not-in-the-build - readiness:
READY - source:
final/document.md#4-4·analysis/10§47 - code:
.../cache-redis/.../RedisCommandMetadataDiff.java· 명령 정책 파일 머리 주석 - evidence:
evidence/raw/163-redis-*-probes.txt§8.1 - classification: 정책 파일이 "The catalog drift gate compares the two and fails the build when the server grows a command this file has not judged"라고 적고 비교 로직과 다섯 버킷 테스트가 완성돼 있다. 그런데 실제 서버 메타데이터를 넣는 곳이 없다 — main 참조 0, 토폴로지 레인 사용 0, 실행 태스크 없음. 빌드를 깨는 gate는 존재하지 않고 gate가 쓸 비교 함수와 그 단위 테스트만 있다. 위험은 fail-closed 카탈로그가 잡지 못하는 셋이다 — key spec 이동(네임스페이스·슬롯 검사가 잘못된 인자를 키로 봄) · ACL 카테고리 확장(계정 분리 가정이 약해짐) · deprecation(타입 있는 API가 사라질 명령을 계속 노출).
- missing-verification: 토폴로지 레인 미실행
- relations:
reference:a-gate-nobody-runs-reports-the-last-run·reference:server-metadata-defines-the-command·open-question:redis-topology-lane-not-executed
CASE — 의미 어댑터 다섯이 gateway를 직접 불러 admission 아홉 단계를 건너뛴다
- slug:
five-adapters-bypass-the-single-admission-point - readiness:
READY - source:
final/document.md#4-4·analysis/10§64 - code:
.../CommandPolicyGuard.java· cache·idempotency·lease·ratelimit·realtime 어댑터의lease.gateway()호출부 - evidence:
evidence/raw/165-redis-*-probes.txt§8.1 - classification: 두 javadoc이 "The single admission point every command passes through"와 "everything routed through it has already passed
CommandPolicyGuard"를 주장하는데, 다섯 어댑터가RedisRuntimeOwner→RedisLease→lease.gateway()를 직접 부른다(30곳).SyncRedisCommandExecutor·ReactiveRedisCommandExecutor·CommandPolicyGuard·CommandRequest를 참조하는 파일이 0이고 타입 있는 API 참조도 0이다. 아홉 단계 중 네임스페이스만CapabilityKeyspace가 같은RedisNamespace에서 키를 조립해 구성으로 유지된다. - missing-verification: 실제 Redis에 붙여 우회 경로의 명령이 정책 없이 나가는지 관측하지 않았다
- relations:
concept:redis-admission-stages·reference:a-single-admission-point-must-count-its-bypasses·reference:check-which-duplicate-is-wired
CASE — NOSCRIPT 복구가 다섯 벌이고 넷은 스크립트 레지스트리를 지나지 않는다
- slug:
five-copies-of-noscript-recovery - readiness:
READY - source:
final/document.md#4-4·analysis/10§56 - code:
.../cache-redis/...의 NOSCRIPT 복구 다섯 지점 - evidence: 없음 — 복구 경로 전수 확인
- classification: Redis가
NOSCRIPT를 반환했을 때의 복구(스크립트 재등록 후 재시도)가 다섯 벌로 구현돼 있고 그중 넷이 스크립트 레지스트리를 지나지 않는다. 레지스트리를 지나는 한 벌만이 스크립트 SHA와 소스의 대응을 유지하므로, 나머지 넷은 재등록하는 스크립트가 레지스트리가 아는 것과 같다는 보장이 없다. - missing-verification: 실제
SCRIPT FLUSH이후 다섯 경로의 동작 차이를 관측하지 않았다 - relations:
reference:check-which-duplicate-is-wired·concept:redis-admission-stages
CASE — startup probe가 production에서 한 번도 실행되지 않는다
- slug:
a-startup-probe-that-never-runs - readiness:
READY - source:
final/document.md#4-4·analysis/10§6 - code:
.../cache-redis/...의 startup probe와 그 조건 - evidence: 없음 — 도달성 확인
- classification: Redis 연결·버전·capability를 확인하는 startup probe가 존재하는데 production 경로에서 실행되지 않는다. 그래서 "이 배포의 Redis가 이 SDK가 요구하는 최소 버전을 만족하는가"가 부팅 시점에 답해지지 않고, 첫 명령에서 capability 예외로 나타난다.
- missing-verification: 부팅해서 probe 미실행을 직접 확인하지 않았다
- relations:
reference:the-startup-validator-follows-the-autoconfiguration-root·reference:a-bean-is-not-composition-evidence
REFERENCE — 서버 메타데이터가 명령의 정의이고 정책 파일은 허용 범위다
- slug:
server-metadata-defines-the-command - readiness:
READY - source:
analysis/10§47 - classification: 명령이 무엇인가는 서버의
COMMAND DOCS/COMMAND INFO/COMMAND GETKEYSANDFLAGS가 정하고, 이 SDK가 그것으로 무엇을 할 용의가 있는가는 정책 파일이 정한다. 둘을 대조하는 게이트가 없으면 서버가 명령을 늘리거나 key spec을 옮겨도 알 수 없다. - scope: 벤더 프로토콜을 감싸는 모든 SDK. 대조해야 할 다섯 버킷 — 미분류 신규 명령 · 사라진 명령 · key spec 이동 · ACL 카테고리 변경 · deprecation.
- exceptions: 미분류 명령은 fail-closed 카탈로그가 이미 막으므로 그 버킷만은 게이트 없이도 안전하다. 나머지 넷은 카탈로그가 잡지 못한다.
- relations:
case:a-build-gate-that-is-not-in-the-build·decision:unclassified-commands-are-refused
REFERENCE — 단일 admission point는 우회 경로를 세어야 성립한다
- slug:
a-single-admission-point-must-count-its-bypasses - readiness:
READY - source:
analysis/10§64 - classification: "모든 것이 여기를 지난다"는 주장은 그 지점의 코드가 아니라 그것을 지나지 않는 경로의 수로 검증된다. 기준은 하류 실행기를 직접 부르는 호출자를 세는 것이고, 0이 아니면 그 주장은 성립하지 않는다.
- scope: guard·interceptor·gateway처럼 "유일한 통로"를 주장하는 모든 컴포넌트. 판정 방법은 하류 타입(gateway·executor)의 참조를 전수로 세고 guard를 지나는 것과 아닌 것을 나누는 것이다.
- exceptions: 우회 경로가 있어도 그 경로가 같은 보장을 다른 방식으로 유지하면 부분적으로 정당하다 — 이 저장소에서 네임스페이스가 그런 경우다. 다만 그 사실을 주장 옆에 적어야 하고, 나머지 여덟 단계는 그렇지 않다.
- relations:
case:five-adapters-bypass-the-single-admission-point·concept:redis-admission-stages·reference:check-which-duplicate-is-wired
OPEN QUESTION — Redis 토폴로지 레인이 실행되지 않아 key spec 드리프트가 확인되지 않았다
- slug:
redis-topology-lane-not-executed - readiness:
OPEN - source:
final/document.md#6-2·analysis/10§0, §47 - known:
redisTopologyTest레인이 실제 서버에 붙는다 ·RedisCommandMetadataDiff의 비교 함수가 완성돼 있고 다섯 버킷 테스트가 있다 · 그 둘을 잇는 실행 경로가 없다 - unknown: 현재 대상 Redis 버전의 실제
COMMAND DOCS출력이 정책 파일과 어떤 차이를 보이는지 · 특히 key spec 이동과 ACL 카테고리 변경이 이미 있는지 - next-verification: 토폴로지 레인에서
COMMAND DOCS/COMMAND INFO를 읽어RedisCommandMetadataDiff.compare(...)를 돌리고 결과를 출력한다 — 레인은 이미 서버에 붙어 있고 비교 함수도 있으므로 없는 것은 연결 한 줄이다 - decision-criterion: diff가 비어 있으면 현재 정책이 서버와 일치함이 확인되고 그 연결을 게이트로 승격할 근거가 된다. 비어 있지 않으면 각 버킷이 새 Case 후보다
- relations:
case:a-build-gate-that-is-not-in-the-build·open-question:container-lanes-not-executed
DECISION — 분류되지 않은 명령은 fail-closed로 거부한다
- slug:
unclassified-commands-are-refused - readiness:
READY· decision-status:ADOPTED - source:
analysis/10§47 - decision-evidence:
.../RedisCommandCatalog.java의require구현과 테스트theCatalogFailsClosedForAnUnclassifiedCommand - grounds:
reference:server-metadata-defines-the-command·concept:redis-admission-stages - classification: 카탈로그에 분류가 없는 명령은 허용이 아니라 거부다. 그래서 서버가 새 명령을 추가해도 이 SDK를 통해 조용히 나가지 못한다. 이것이 catalog drift 게이트가 없는 상태에서도 한 버킷(미분류 신규 명령)만은 안전한 이유이고, 동시에 나머지 네 버킷은 이 fail-closed가 잡지 못한다는 사실의 근거이기도 하다.
- relations:
reference:server-metadata-defines-the-command·case:a-build-gate-that-is-not-in-the-build
TOPIC 12 — http-failure-classification
CONCEPT — 전송 실패의 단계와 범주 — AttemptStage와 FailureCategory
- slug:
transport-failure-stage-and-category - readiness:
READY - source:
final/document.md#8-1·analysis/11§51 - code:
.../httpclient/.../ApacheFailureClassifier.java·.../TransportFailure.java·.../DefaultRetryEligibilityEngine.java - classification: HTTP 전송 실패를 "어디까지 갔는가"(
AttemptStage: pool·DNS·CONNECT·TLS·전송·응답)와 "무엇이 실패했는가"(FailureCategory)의 두 축으로 모델링하는 구조의 설명이다. 두 축이 함께notSent/maybeSent판정을 만들고, 그것이 재시도 결정표의 입력이 된다 —TLS_PERMANENT는permanent()로 절대 차단되고CONNECT는 멱등성과 무관하게 재시도된다. 그래서 분류가 곧 재시도 안전성이고, 원인 사슬 순회 순서가 그 분류를 정한다. - missing-verification: 없음 — 분류기와 결정표를 코드로 확인했다
- relations:
case:permanent-tls-classified-as-retryable-connect·reference:walk-the-cause-chain-most-specific-wins·decision:retry-safety-is-decided-by-evidence
CASE — 영구 TLS 실패가 재시도 가능한 CONNECT로 분류된다
- slug:
permanent-tls-classified-as-retryable-connect - readiness:
READY - source:
final/document.md#8-1항목 11 ·analysis/11§51 - code:
.../httpclient/.../ApacheFailureClassifier.java의recognize분기 순서 - evidence: 없음 — 저장소 자신의 테스트 3건이 실패로 잡고 있다(
:adapter:outbound:httpclient:test283 중 3) - classification:
recognize의 분기 순서가 pool → DNS → CONNECT → TLS인데, Apache HttpClient 5는 TLS 핸드셰이크 실패를 연결 단계 실패로 감싼다(HttpHostConnectException→SSLHandshakeException). 바깥 것이 CONNECT 분기에 먼저 걸려 안쪽SSLHandshakeException은 검사되지 않는다. 그래서 신뢰할 수 없는 CA·만료된 인증서·호스트명 불일치 셋 다 영구 실패인데 일시적 연결 실패로 취급되어 재시도된다 — 매번 같은 이유로 실패하므로 예산을 전부 소모하고, 호출자와 관측 모두 인증서 문제가 아니라 연결 문제를 본다. 판정 근거 셋: 잘못된 방향(영구를 일시적으로 낮춤) · 보안 신호 손실 · 저장소 자신의 테스트가 이미 실패로 잡고 있음. - missing-verification: 없음 — 실패하는 세 테스트의 이름과 기대/실제 값을 확인했다
- relations:
concept:transport-failure-stage-and-category·reference:walk-the-cause-chain-most-specific-wins·decision:retry-safety-is-decided-by-evidence
CASE — 로컬 거부 경로에서 회로 브레이커 permission이 반환되지 않는다
- slug:
a-circuit-breaker-permit-that-leaks-on-local-rejection - readiness:
READY - source:
analysis/11§22 - code:
.../httpclient/...의 회로 브레이커 permit 획득/반환 경로 - evidence: 없음 — 경로 확인
- classification: 회로 브레이커 permit을 얻은 뒤 로컬 검증에서 요청이 거부되면 그 permit이 반환되지 않는다. 실패 한 번에 하나씩 줄어드는 형태이고, 같은 저장소의 messaging publish 경로가 명시적으로 막은 것("실패 경로에서 새는 permit은 실패 한 번에 하나씩 줄어들다 아무것도 받지 않게 되는 limiter다")과 같은 결함이다.
- missing-verification: 반복 호출로 permit 고갈을 재현하지 않았다
- relations:
concept:transport-failure-stage-and-category·reference:check-which-duplicate-is-wired
REFERENCE — 원인 사슬은 가장 구체적인 분류가 이기도록 순회한다
- slug:
walk-the-cause-chain-most-specific-wins - readiness:
READY - source:
analysis/11§51 - classification: 예외 사슬을 바깥에서 안쪽으로 훑으며 처음 인식되는 것을 돌려주면, 라이브러리가 구체적 원인을 일반적 예외로 감쌌을 때 잘못된 분류가 나온다. 기준은 "이 사슬에서 가장 구체적인 분류가 이기는가"이고, 구현은 (a) 구체적 분기를 앞으로 옮기거나 (b) 사슬 전체를 훑어 최선의 매치를 고르는 것이다.
- scope: 드라이버·클라이언트 예외를 자기 범주로 번역하는 모든 분류기. 특히 전송 계층은 감싸기가 흔하다.
IdentityHashMap+ 최대 깊이로 사이클 안전을 확보하고,SQLException.getNextException()같은 벤더별 곁가지도 따라간다. - exceptions: 바깥 예외가 실제로 더 구체적인 경우가 있다 — 그때는 순서가 아니라 우선순위 표가 필요하고, 그 표를 테스트로 고정해야 한다.
- relations:
case:permanent-tls-classified-as-retryable-connect·reference:translation-chain-order-is-a-contract
REFERENCE — 재시도 가능성은 멱등성과 실패 범주를 함께 봐야 정해진다
- slug:
retryability-needs-both-idempotency-and-category - readiness:
READY - source:
analysis/11§51 ·analysis/05§3.6 - classification: 실패 범주만으로 재시도를 정하면 비멱등 요청을 재시도하고, 멱등성만으로 정하면 영구 실패를 반복한다. 기준은 두 입력의 곱이다 — 범주가 재시도 가능하고 동시에 요청이 재시도 안전할 때만 재시도한다. 그리고 그 판정에 "아무것도 전송되지 않았다"는 증거가 있으면 멱등성 요구가 완화된다.
- scope: HTTP·gRPC·메시징 클라이언트의 재시도 결정. 이 저장소는
notSent증거를 별도 축으로 두어 그 완화를 표현한다. - exceptions: 분류기가 terminal로 표시한 실패는 정책의 화이트리스트로 되살릴 수 없다 — 화이트리스트는 어떤 범주가 재시도될 수 있는지를 넓히지 이 실패에 대한 판정을 뒤집지 않는다.
- relations:
concept:transport-failure-stage-and-category·reference:translation-chain-order-is-a-contract·decision:retry-safety-is-decided-by-evidence
DECISION — 재시도 안전성은 증거에 기반해 판정한다
- slug:
retry-safety-is-decided-by-evidence - readiness:
READY· decision-status:ADOPTED - source:
final/document.md#10-2·analysis/11§51 ·analysis/20§2.4 - decision-evidence:
.../httpclient/.../TransportFailure.java의notSent/maybeSent팩토리와 그 javadoc ·docs/adr/ADR-GRPC-003-three-axis-execution-evidence.md(같은 원칙의 gRPC판) - grounds:
concept:transport-failure-stage-and-category·reference:retryability-needs-both-idempotency-and-category - classification: 이 모듈의 전체 설계가 "증거에 기반해 재시도 안전성을 판정한다"에 서 있다 — 무엇이 실패했는지가 아니라 무엇이 관측됐는지가 판정 입력이다.
notSent는 클라이언트가 자기 전송 실패를 본 경우이고 그 외는 전부maybeSent다. §51의 결함이 무거운 이유가 여기 있다 — 그 증거가 틀리면 설계 전체가 틀린 입력 위에 선다. - relations:
case:permanent-tls-classified-as-retryable-connect·decision:three-axes-of-evidence·decision:completion-unknown-is-never-retried
TOPIC 13 — fileserver-state-and-fencing
CONCEPT — 파일 상태 기계와 READY가 뜻하는 것
- slug:
file-state-machine-and-ready - readiness:
READY - source:
final/document.md#4-4·analysis/08§V1 - code:
db/migration/jpa/fileserver/V1__create_fileserver_metadata.sql·.../persistence/fileserver/FileTransitionRepository.java - classification: 파일이 11개 상태를 지나고 공개 읽기 가능한 상태는
READY하나뿐이라는 설계의 설명이다. V1 헤더가 원칙을 적는다 — "파일시스템이 아니라 관계형 레코드가 파일이 공개적으로 읽을 수 있는지 결정한다." 그래서 모든 전이가state와version둘 다로 가드되고,READY의 완전성이 DB CHECK 제약으로 강제된다(content_key·actual_size·sha256·strong_etag·published_at이 전부 NOT NULL). 그리고 물리 경로·마운트·원본 물리 파일명을 저장하지 않는다 —content_key는 서버 생성 opaque key이고original_name은 신뢰할 수 없는 표시용 텍스트다. - missing-verification: 컨테이너 레인 미실행
- relations:
reference:a-publicly-readable-state-must-be-complete-by-constraint·decision:no-physical-paths-in-metadata
CASE — scriptable 콘텐츠 탐지가 BOM·NUL·주석으로 우회된다
- slug:
scriptable-detection-bypassed-by-a-bom - readiness:
READY - source:
final/document.md#4-4·analysis/08§40 - code:
.../fileserver/.../ScriptableContentPolicy.java - evidence:
evidence/raw/146-fileserver-*-probes.txt(EXECUTION PROBE 블록) - classification: javadoc이 목적을 "Detection is on content, not on the claimed type or the extension, because both are attacker controlled"로 적는데, 구현은 1,024바이트 접두사를 소문자화·
stripLeading()한 뒤 여섯 마커로 시작하는지만 본다. hermetic probe로 실제 판정을 측정하니 셋이 통과한다 — UTF-8 BOM +<html>· 선행 HTML 주석 후<script>· NUL 바이트 후<html>.String.stripLeading()은Character.isWhitespace만 제거하므로 BOM(U+FEFF)도 NUL도 지우지 않는다. 셋 다 브라우저는 HTML로 렌더링하고, BOM 접두 HTML은 여러 편집기의 기본 출력이다. 형제 검증기와의 대비가 판정을 굳힌다 —MediaTypeVerifier의 매직바이트 선두 매칭은 시그니처의 정의가 파일 선두이므로 옳지만, scriptable 마커는 시그니처가 아니라 브라우저가 스니핑하는 패턴이다. - missing-verification: 실제 브라우저가 세 입력을 HTML로 렌더링하는지 확인하지 않았다(문서화된 스니핑 동작에 근거)
- relations:
reference:prefix-matching-fits-signatures-not-sniffing·concept:file-state-machine-and-ready
CASE — cleanup이 읽은 lease와 삭제 사이에 writer가 그 lease를 얻을 수 있었다
- slug:
a-read-then-delete-race-on-the-upload-lease - readiness:
READY - source:
analysis/08§V4 - code:
db/migration/jpa/fileserver/V4__fileserver_upload_terminal_state.sql헤더 - evidence: 없음 — 마이그레이션 헤더의 사후 기록
- classification: cleanup이 writer lease를 읽고, 없는 것을 확인하고, staging 바이트를 삭제했다. 그 read와 delete 사이에 writer가 바로 그 lease를 획득할 수 있다 — 업로드가 끝났다고 말하는 게 DB에 아무것도 없었으니까 — 그리고 cleanup이 제거한 객체는 업로드가 활발히 append 중이던 것이었다. 해법은 cancel/failed finalize가 cleanup을 큐잉하는 같은 트랜잭션 안에서 세션을
TERMINAL로 옮기고, acquire/renew/offset commit이ACTIVE를 요구하며, cleanup이 읽은 값으로 판단하는 대신 조건부 update로 행을 claim하는 것이다. - missing-verification: 컨테이너 레인 미실행
- relations:
reference:claim-with-a-conditional-update-not-a-read·concept:fenced-lease
CASE — claim이 소유자·토큰·만료를 기록하지 않아 죽은 worker의 항목이 영영 남았다
- slug:
a-cleanup-claim-without-fencing - readiness:
READY - source:
analysis/08§V3 - code:
db/migration/jpa/fileserver/V3__fileserver_fenced_cleanup_lease.sql헤더 - evidence: 없음 — 마이그레이션 헤더의 사후 기록
- classification: claim이 항목을
IN_PROGRESS로 옮기고 다른 건 아무것도 기록하지 않았다 — owner도, token도, lease expiry도. 두 결과가 나왔다. (1) 물리 delete를 수행하고 DB를 정리하기 전에 죽은 worker가 행을 영원히IN_PROGRESS로 남겼고, 어떤 쿼리도 그것을 살아있는 worker가 활발히 삭제 중인 것과 구별할 수 없어 아무것도 회수하지 않았다. (2) 완료 update가cleanup_id만으로 매칭해, 합리적 lease를 한참 지나 멈춰 있던 worker가 다른 worker가 반쯤 진행한 항목 위에 DONE을 쓸 수 있었다.NULLlease를 reaper가 자동 회수하지 않고 운영자에게 넘기는 결정도 함께 기록돼 있다 — "자동 takeover는 아무도 상태를 기록하지 않은 작업에 대해 추측하는 것이다." - missing-verification: 컨테이너 레인 미실행
- relations:
concept:fenced-lease·case:a-lease-without-an-owner·reference:claim-with-a-conditional-update-not-a-read
CASE — README가 "노출된 setting도 bean도 없다"고 적은 능력에 production bean 여덟이 있다
- slug:
readme-said-no-beans-there-are-eight - readiness:
READY - source:
final/document.md#4-4·analysis/08§4, §39 - code:
.../fileserver/README.md· 해당 능력의 port 구현 여덟과 그 bean 정의 - evidence: 없음 — README 문장과 bean 목록 대조
- classification: README가 특정 능력들에 대해 "노출된 setting도 bean도 없다"고 적는데, 여덟 개의 port 구현과 여덟 개의 bean이 있다. 방향이 과소 진술이라 위험이 과대보다 낮지만, 이 문서를 읽고 "그 능력은 아직 없다"고 판단한 팀이 같은 것을 다시 만들 수 있다.
- missing-verification: 없음
- relations:
reference:fix-overstatement-before-understatement·reference:numbers-in-docs-should-be-derived
REFERENCE — 공개 읽기 가능한 상태는 완전한 identity를 DB 제약으로 요구한다
- slug:
a-publicly-readable-state-must-be-complete-by-constraint - readiness:
READY - source:
analysis/08§V1 - classification: "이 상태의 행은 반드시 이 필드들을 갖는다"를 애플리케이션 코드로만 두면 어떤 경로가 그것을 어겼는지 알 수 없다. 부분 CHECK 제약(
state <> 'READY' OR (… IS NOT NULL …))으로 두면 그 상태에 도달하는 모든 경로가 강제된다. 기준은 "이 상태가 외부에 무엇을 약속하는가"이고, 약속이 있으면 제약으로 적는다. - scope: 공개·발행·활성 같은 terminal 상태를 갖는 모든 상태 기계. 같은 형태가 messaging outbox delivery의
ck_..._state_shape/ck_..._terminal_shape에도 있다. - exceptions: 제약이 마이그레이션 시점에 기존 행을 재검증하므로, 이미 데이터가 있는 테이블에는
NOT VALID로 추가해 앞으로의 쓰기에만 적용하는 선택지가 있다 — 그때는 기존 행의 정합성을 별도로 확인해야 한다. - relations:
concept:file-state-machine-and-ready·reference:cas-tuple-in-the-where-clause
REFERENCE — 조건부 update로 행을 claim하고 읽은 값으로 판단하지 않는다
- slug:
claim-with-a-conditional-update-not-a-read - readiness:
READY - source:
analysis/08§V4 ·analysis/05§10 - classification: 읽어서 확인하고 행동하면 그 사이에 상태가 바뀔 수 있다. 조건부 update가 행을 claim하면 update count가 곧 "내가 얻었는가"의 답이 된다. 기준은 "이 판단과 그에 따른 행동 사이에 다른 참여자가 끼어들 수 있는가"다.
- scope: cleanup·relay·worker처럼 경쟁 참여자가 있는 모든 작업. 물리 자원(파일·객체)을 지우는 경로에서 특히 결정적이다 — 되돌릴 수 없기 때문이다.
- exceptions: 단일 참여자가 구조적으로 보장되면 불필요하다. 다만 그 보장이 배포 형태에서 오면 다중 인스턴스로 가는 날 깨진다.
- relations:
case:a-read-then-delete-race-on-the-upload-lease·case:a-cleanup-claim-without-fencing·reference:cas-tuple-in-the-where-clause
REFERENCE — 접두사 시작 매칭은 시그니처에는 맞고 스니핑 패턴에는 맞지 않는다
- slug:
prefix-matching-fits-signatures-not-sniffing - readiness:
READY - source:
analysis/08§40 - classification: 매직바이트는 정의상 파일 선두에 있으므로 접두사 시작 비교가 정확하다. 브라우저가 스니핑하는 패턴은 선두 고정이 아니므로 같은 비교가 우회 가능하다. 기준은 "이 패턴의 정의가 위치를 포함하는가"이고, 아니면 정규화(BOM·NUL·주석 제거) 후 탐색이거나 범위 스캔이어야 한다.
- scope: 콘텐츠 기반 탐지 전반 — 스크립트 탐지·MIME 스니핑 방어·서명 확인. 정규화 대상은 최소한 BOM(U+FEFF)·NUL·선행 주석이다.
- exceptions: 시그니처 검증(매직바이트)은 선두 고정이 옳고 정규화하면 오히려 틀린다.
- relations:
case:scriptable-detection-bypassed-by-a-bom·reference:reject-rather-than-sanitize
DECISION — 물리 경로와 원본 파일명을 저장하지 않는다
- slug:
no-physical-paths-in-metadata - readiness:
READY· decision-status:ADOPTED - source:
analysis/08§V1 - decision-evidence:
db/migration/jpa/fileserver/V1__create_fileserver_metadata.sql헤더가 결정과 근거를 적는다 - grounds:
concept:file-state-machine-and-ready·reference:a-publicly-readable-state-must-be-complete-by-constraint - classification: 물리 경로·마운트·원본 물리 파일명을 저장하지 않고
content_key(서버 생성 opaque)와original_name(신뢰할 수 없는 표시용 텍스트)만 둔다. 그래서 저장 위치를 바꿔도 메타데이터가 바뀌지 않고, 클라이언트가 준 이름이 경로로 해석될 여지가 없다. - relations:
concept:file-state-machine-and-ready
TOPIC 14 — objectstorage-staged-lifecycle
CONCEPT — staged lifecycle과 서명된 grant
- slug:
staged-lifecycle-and-signed-grants - readiness:
READY - source:
final/document.md#4-4·analysis/09·analysis/03§10 - code:
.../objectstorage/...의 staged 업로드 경로와 grant 서명·검증 - classification: 객체가 staging에서 관리 네임스페이스로 옮겨지는 단계와, 각 단계의 권한이 서명된 grant로 표현되는 구조의 설명이다. opaque identity(클라이언트가 물리 위치를 알 수 없음), privilege separation(업로드 자격과 발행 자격이 다름), 그리고 grant가 endpoint·유효창·operation을 함께 바인딩하는 이유를 다룬다.
application-core가 이 수명주기를 port로 소유하고 어댑터가 벤더별 구현만 갖는 경계도 함께 설명한다. - missing-verification: 컨테이너 레인 미실행
- relations:
case:apply-is-a-setting-approval-is-not·case:the-last-part-cannot-get-a-grant·reference:the-powerful-half-must-not-be-one-setting-away
CASE — APPLY를 켜는 설정은 있고 승인을 검증하는 bean은 없다
- slug:
apply-is-a-setting-approval-is-not - readiness:
READY - source:
final/document.md#4-4·analysis/09§49 - code:
.../objectstorage/.../ObjectStorageLegacyMigrationConfig.java:30·.../Ed25519LegacyAdoptionApprovalVerifier.java·.../LegacyObjectAdoptionService.requireApproval - evidence:
evidence/raw/155-objectstorage-*-probes.txt§8.1 - classification: legacy 채택은 이 leaf에서 가장 권한이 센 동작이다(원시 locator로 legacy를 읽어 관리 네임스페이스에 발행). 설계가 detached 2인 승인을 요구하고 검증기 구현은 촘촘하다 — 서로 다른 두 승인자의 Ed25519 서명, destination/epoch/operationId/manifest 다이제스트 일치, 유효창 7일 이내, 그리고 코덱이 decode 후 재인코딩해 원본 바이트와 같아야만 통과(비정규 인코딩 거부). 그런데 조립이 비대칭이다 — APPLY 실행 경로는 설정 두 줄로 켜지고 bean이 만들어지는데, 검증기를
main에서 생성하는 코드가 저장소 전체에 0이고 신뢰 승인자 키·키 id·유효기간에 해당하는 설정 항목이 하나도 없다. 어댑터 쪽requireApproval은 필드 동등성만 검사하고 서명은 보지 않는다. - missing-verification: APPLY 모드를 실제로 켜서 어떤 실패가 나는지 관측하지 않았다
- relations:
reference:the-powerful-half-must-not-be-one-setting-away·reference:a-binary-approval-codec-must-round-trip·reference:a-bean-is-not-composition-evidence
CASE — 직접 multipart의 마지막 part는 grant를 받을 수 없다
- slug:
the-last-part-cannot-get-a-grant - readiness:
READY - source:
analysis/09§38 - code:
.../objectstorage/...의 multipart grant 발급 경로 - evidence: 없음 — 경로 확인
- classification: 직접 multipart 업로드에서 마지막 part가 grant를 받을 수 없는 경로 결함이다. grant가 업로드 단계별로 발급되는 구조인데 마지막 part의 조건이 그 발급 경로를 벗어난다.
- missing-verification: 실제 multipart 업로드를 수행해 재현하지 않았다
- relations:
concept:staged-lifecycle-and-signed-grants·case:endpoint-check-only-on-upload
CASE — 서명된 grant의 endpoint 검증이 upload 경로에만 있다
- slug:
endpoint-check-only-on-upload - readiness:
READY - source:
analysis/09§39 - code:
.../objectstorage/...의 grant 검증 지점들 - evidence: 없음 — 검증 지점 전수 확인
- classification: grant가 endpoint를 바인딩하는데 그 검증이 upload 경로에만 있고 다른 경로에는 없다. grant를 발급받은 주체가 다른 endpoint로 그것을 쓸 수 있다는 뜻이고, 서명이 endpoint를 덮는 이유가 무력화된다.
- missing-verification: 다른 endpoint로 grant를 제시해 재현하지 않았다
- relations:
concept:staged-lifecycle-and-signed-grants·case:the-last-part-cannot-get-a-grant
REFERENCE — 권한이 센 절반이 설정 한 줄로 켜지면 안 된다
- slug:
the-powerful-half-must-not-be-one-setting-away - readiness:
READY - source:
analysis/09§49 - classification: 한 능력이 "실행하는 절반"과 "승인하는 절반"으로 나뉠 때, 실행 쪽이 설정으로 켜지고 승인 쪽이 손으로 배선해야 하면 비대칭이 위험 방향으로 기운다. 기준은 "이 설정을 켠 배포에서 승인 검증이 없으면 무엇이 일어나는가"이고, 답이 "실행된다"면 그 설정이 승인 bean의 부재를 startup 실패로 만들어야 한다.
- scope: 승인·서명·2인 통제를 요구하는 모든 관리 작업. 이 저장소의 admin plane이 반대 사례다 — 파괴적 작업의 bean을 아예 자동설정하지 않고 그 부재를 javadoc으로 문서화한다.
- exceptions: 승인 검증을 애플리케이션 계층이 소유하는 설계면 어댑터에 bean이 없는 것이 옳다. 다만 그때는 어댑터가 승인 없이 실행되는 경로를 갖지 않아야 하고, 이 저장소에서는 어댑터의 검사가 필드 동등성뿐이다.
- relations:
case:apply-is-a-setting-approval-is-not·reference:a-bean-is-not-composition-evidence
REFERENCE — 이진 승인 코덱은 decode 후 재인코딩이 원본과 같아야 한다
- slug:
a-binary-approval-codec-must-round-trip - readiness:
READY - source:
analysis/09§49 - classification: 서명 대상이 이진 인코딩이면 같은 논리 값에 대해 여러 바이트 표현이 가능할 때 서명 우회가 생긴다. decode한 뒤 재인코딩해 원본 바이트와 동일한지 확인하면 비정규 인코딩이 거부된다. 기준은 "이 인코딩이 canonical인가"이고, 보장이 없으면 round-trip 검사가 그 자리다.
- scope: 서명·MAC의 대상이 되는 모든 이진·텍스트 인코딩. 길이 프레이밍이 함께 필요한 이유도 같은 축이다.
- exceptions: 인코딩이 정의상 canonical이면(고정 길이 필드만) 불필요하다. 다만 필드가 하나라도 가변이면 성립하지 않는다.
- relations:
case:apply-is-a-setting-approval-is-not·reference:digest-must-be-length-framed-and-versioned·concept:signed-cursor-structure
DECISION — legacy 채택은 서로 다른 두 승인자의 서명을 요구한다
- slug:
legacy-adoption-requires-two-approvers - readiness:
READY· decision-status:ADOPTED - source:
analysis/09§49 - decision-evidence:
.../Ed25519LegacyAdoptionApprovalVerifier.java의 구현과 그 테스트(verifiesCanonicalExactBindingWithTwoDistinctTrustedApprovers,rejectsDuplicateApproverAndAnyBindingTamper) ·sample-portfolio의AdoptLegacyPosterImageUseCase.authorizeApply가 보여 주는 의도된 조립 - grounds:
concept:staged-lifecycle-and-signed-grants·reference:the-powerful-half-must-not-be-one-setting-away - classification: 원시 locator로 legacy 네임스페이스를 읽어 관리 네임스페이스에 발행하는 동작에 detached 2인 승인을 요구한다. 검증기가 서로 다른 두 승인자를 요구하고(중복 승인자 거부), destination·epoch·operationId·manifest·네임스페이스 다이제스트를 요청과 대조하며, 유효창을 최대 7일로 제한한다. 의도된 조립은 use case가 호출자가 준 approval을 버리고 검증 결과로 요청을 다시 만드는 것 — "application에서 검증하고 adapter에서 적용한다". 그 use case가 어디에도 배선돼 있지 않다는 점이
case:apply-is-a-setting-approval-is-not이다. - relations:
case:apply-is-a-setting-approval-is-not·reference:a-binary-approval-codec-must-round-trip
TOPIC 15 — self-disclosure-grading
CONCEPT — 네 단계 공시 등급 — modelled에서 production-verified까지
- slug:
four-grade-disclosure - readiness:
READY - source:
final/document.md#7-5·analysis/16§45.2, §45.3 - code:
src/adapter/inbound/graphql/CLAUDE.md의 등급 정의와 13행 표 - classification: graphql leaf가 자기 능력을 네 등급으로 공시하는 체계의 설명이다 —
modelled(타입은 있고 조립 없음) ·wired(조립됨) ·integration-verified(통합 테스트로 확인) ·production-verified(운영 관측). 그리고 규칙 하나 — "현재 등급보다 높게 표현하지 않는다". 이 체계가 왜 작동했는지가 핵심이다 — 13행 중 일곱을 스스로 강등했고, 이 분석이 파일 단위 배선 데이터와 전수 대조했을 때 12행이 일치했다. 타입 이름이 등급을 인코딩하는 관행(*Admission— "Spring transport handler는 없다, 그래서 타입 이름도*Admission이다")도 이 개념의 일부다. - missing-verification: 없음 — 13행 전수 대조
- relations:
case:seven-rows-downgraded-by-the-family-itself·reference:grades-may-understate-never-overstate·decision:capability-grade-is-declared-not-inferred
CASE — 등급표 13행 중 일곱을 스스로 강등하고 한 행만 관측과 어긋났다
- slug:
seven-rows-downgraded-by-the-family-itself - readiness:
READY - source:
final/document.md#7-5·analysis/16§20.1, §29.1, §45.2 - code:
src/adapter/inbound/graphql/CLAUDE.md등급표 ·.../graphql/http/...(해당 행의 대상 타입) - evidence:
evidence/raw/213-inbound-graphql-release-probes.txt - classification:
CLAUDE.md가 4등급을 정의하고 "현재 등급보다 높게 표현하지 않는다"를 규칙으로 선언한 뒤 13행 중 일곱을 스스로modelled로 강등했다. sub-scope 02~06의 파일 단위 배선 데이터와 13행을 전수 대조한 결과 12행 일치,요청 크기/Accept 협상 (http/)한 행만 불일치이고 어긋나는 방향이 표가 금지한 "높게 표현" 쪽이다 — 인용된 두 증거가 endpoint 테스트가 아닌 순수 단위 테스트이고 대상 타입은 미배선이다. 그리고 기계 매니페스트(GrpcStableCapabilityManifest에 해당하는 graphql 쪽)와 사람이 읽는 등급표가 커서에 대해 서로 다른 답을 준다. - missing-verification: 없음 — 13행 전수 대조
- relations:
concept:four-grade-disclosure·reference:grades-may-understate-never-overstate·case:a-startup-validator-that-requires-a-key-nothing-signs-with
CASE — 시작 검증기가 커서 서명 키를 요구하는데 그 키로 서명하는 코드가 없다
- slug:
a-startup-validator-that-requires-a-key-nothing-signs-with - readiness:
READY - source:
final/document.md#7-1·analysis/16§24.1 - code:
.../graphql/.../HmacGraphQlCursorCodec.java·.../GraphQlCursorKeyRing.java· 시작 검증기 - evidence:
evidence/raw/212-inbound-graphql-data-probes.txt - classification: 시작 검증기가 "unsigned cursors are client-editable"로 프로덕션을 거부하며 커서 서명 키를 요구하고, 설정이 그 키를 받고, 액추에이터가 그것을 확인해 준다 — 세 신호가 전부 긍정이다. 그런데 그 키로 서명하는 코드가 없다(
HmacGraphQlCursorCodec·GraphQlCursorKeyRing배선 0). 저장소 자신의 테스트가 이 상태를 알고 있다 —GraphQlPolicyRequestPathTest(GQL-INT-003)가 javadoc으로 기록하고 "this test fails the moment somebody wires one half without the other"로 고정한다. 현재 어떤 feature에도 페이지네이션이 붙지 않아 조작 대상 커서 자체가 없다는 것이 위험을 낮춘다. - missing-verification: 부팅해서 세 신호를 직접 관측하지 않았다
- relations:
concept:four-grade-disclosure·reference:a-bean-is-not-composition-evidence·concept:signed-cursor-structure
CASE — build-only 등급이 90개 파일의 미조립을 오늘의 사고에서 면제한다
- slug:
build-only-exempts-ninety-files-from-todays-incident - readiness:
READY - source:
final/document.md#8-3·analysis/17§4.1, §26.6 - code:
src/config/architecture/modules.json(adapter-inbound-websocket의runtime_memberships: []) ·.../ConditionalTransportCompositionContractTest.java의BUILD_ONLY_TRANSPORTS - evidence:
evidence/raw/228-app-bootstrap-activation-probes.txt - classification: websocket leaf의
backend.websocket플랫폼 약 90개 main 파일에 조립 지점이 없고 세 안전 장치(WebSocketPlatformStartupValidator125줄 ·WebSocketStackExclusivity78줄 · settings의 safe-default 규약)가 전부 호출자 0이다. 처음에 P1로 기록했다가runtime_memberships=[]가 기계로 강제되는 build-only 등급임을 확인하고 P2로 내렸다 — 그 실패 시나리오가 현재 출하되는 두 조합 어디에서도 발생할 수 없다. 등급이 심각도를 바꾸되 발견을 없애지는 않는다는 것이 이 Case의 요점이다. - missing-verification: 없음
- relations:
reference:runtime-membership-decides-severity·reference:grades-may-understate-never-overstate·decision:grpc-stays-build-only-until-the-bridge-is-decided
REFERENCE — 등급은 네 단계로 나누고 관측보다 높게 적지 않는다
- slug:
grades-may-understate-never-overstate - readiness:
READY - source:
final/document.md#7-5·analysis/16§45.3 ·analysis/20§2.6 - classification: 능력 등급표의 각 행은 "이 능력이 어디까지 검증됐는가"를 말한다. 낮게 적는 것은 보수적이고 안전하지만 높게 적으면 그 행을 읽은 팀이 검증되지 않은 것에 의존한다. 네 단계(
modelled/wired/integration-verified/production-verified)가 유용한 이유는 "구현됐다"와 "조립됐다"와 "검증됐다"가 다른 사실이기 때문이다. - scope: capability 등급표·지원 매트릭스·readiness card. 기계가 소비하는 매니페스트가 따로 있으면 둘 사이의 불일치도 같은 규칙으로 판정한다.
- exceptions: build-only 가족은 전부
modelled근처에 머무는 것이 정상이고 그 자체가 결함이 아니다 — 다만 그 사실을 운영 문서가 말해야 한다. - relations:
concept:four-grade-disclosure·case:seven-rows-downgraded-by-the-family-itself·reference:runtime-membership-decides-severity
REFERENCE — runtime_memberships를 먼저 읽고 심각도를 정한다
- slug:
runtime-membership-decides-severity - readiness:
READY - source:
final/document.md#9규칙 1 ·analysis/17§26.6 ·analysis/19§1.1 ·analysis/20§1.1 - classification: 같은 미조립도 그 leaf가 런타임에 오르는지에 따라 "오늘의 사고"와 "채택 시점의 부채"로 갈린다. 기준은 레지스트리의
runtime_memberships이고, 그것이 기계로 강제되면(verifyRuntimeModuleMembership, 전송별 contract test) 판정의 근거가 된다. - scope: 모듈 레지스트리를 갖는 모든 멀티모듈 프로젝트. 이 저장소는 62 leaf 중 27이 build-only(729 파일 / main 479)다.
- exceptions: build-only leaf라도 조립되는 소수의 bean이 있으면 그 부분은 오늘의 문제다 — gRPC의
GrpcAdmissionController가 9개 bean 중 하나인 경우가 그것이다. - relations:
case:build-only-exempts-ninety-files-from-todays-incident·decision:grpc-stays-build-only-until-the-bridge-is-decided·reference:grades-may-understate-never-overstate
DECISION — 능력 등급은 코드가 아니라 실행된 증거에서 파생한다
- slug:
grades-derive-from-executed-evidence - readiness:
READY· decision-status:ADOPTED - source:
final/document.md#10-5·analysis/19§6.6 ·analysis/16§45.3 - decision-evidence:
CompatibilityMatrix.Entry.hasLiveBrokerCertification()의 파생 구현과 javadoc · graphqlCLAUDE.md의 등급 규칙 ·docs/adr/ADR-GRPC-ADV-001-capability-promotion-is-per-capability.md - grounds:
concept:four-grade-disclosure·reference:agreement-between-documents-proves-nothing - classification: 등급을 선언된 boolean이 아니라 레인이 낸 증거에서 계산한다. messaging이 그 전환을 명시적으로 기록한다 — "As a field it was a boolean an author set next to the tier, and RabbitMQ carried
truewhile no fault scenario had ever been executed against it." graphql은 사람이 읽는 표에 같은 규칙을 적용해 일곱 행을 강등했고, gRPC는 capability별 promotion을 ADR로 고정했다. - relations:
decision:capability-grade-is-declared-not-inferred·concept:four-grade-disclosure
TOPIC 16 — multitenancy-isolation
CONCEPT — RLS가 성립하기 위한 세 전제
- slug:
rls-three-preconditions - readiness:
READY - source:
final/document.md#4-1·analysis/05§13.1 - code:
db/experimental-rls/V1__tenant_rls.sql·.../experimental/multitenancy/RlsPolicyVerifier.java·.../RlsTenantSessionBinder.java - classification: PostgreSQL RLS가 실제로 격리하려면 세 가지가 동시에 참이어야 한다는 설명이다. (1)
ENABLE ROW LEVEL SECURITY— 테이블에 policy를 켠다. (2)FORCE ROW LEVEL SECURITY— 테이블 OWNER에게도 적용한다. 없으면 owner는 자기 policy에서 면제되고 owner는 흔히 마이그레이션 롤이며 그것이 사람들이 테스트하는 롤이다. (3) 런타임 롤이BYPASSRLS를 갖지 않는다 — 이것은 테이블 속성이 아니라 롤 속성이라 startup에서 assert해야 한다. policy가current_setting('app.tenant_id', true)를 쓰는 이유(두 번째 인자true가 미설정 시 raise 대신 NULL을 반환하고, NULL은 절대tenant_id와 같지 않으므로 fail-closed)도 함께 다룬다. - missing-verification: 실제 RLS 환경을 세워 세 전제를 하나씩 깨보지 않았다
- relations:
case:three-ways-rls-does-nothing·reference:hibernate-filter-is-not-a-security-boundary·reference:isolation-settings-must-be-transaction-local
CONCEPT — 네 가지 멀티테넌시 전략과 각각의 격리 경계
- slug:
four-multitenancy-strategies - readiness:
READY - source:
analysis/05§13.2 - code:
.../experimental/multitenancy/의 column·rls·schema·database 구현 - classification: 이 저장소가 네 전략(discriminator column · RLS · schema-per-tenant · database-per-tenant)을 각각 플래그 뒤에 두고 구현한 구조의 설명이다. 각 전략의 격리 경계가 다르고 실패 모드도 다르다 — column은 predicate 누락이 곧 유출, RLS는 세 전제(§concept:rls-three-preconditions), schema는
search_path잔존, database는 커넥션 예산의 곱셈.TenantId의 정규식이 네 전략 전부의 공통 기반인 이유(스키마 이름·set_config값·라우팅 키에 들어가므로../public같은 값이 path-traversal이 된다)도 다룬다. - missing-verification: 네 전략 중 어느 것도 실제로 켜서 관측하지 않았다 — 전부 experimental 플래그 뒤에 있다
- relations:
concept:rls-three-preconditions·case:tenant-pools-summed-past-the-server-ceiling·decision:classpath-presence-is-not-consent
CASE — RLS가 아무것도 하지 않는 세 가지 방법
- slug:
three-ways-rls-does-nothing - readiness:
READY - source:
final/document.md#4-1·analysis/05§13.1, §17 P8 - code:
.../experimental/multitenancy/RlsPolicyVerifier.java·db/experimental-rls/V1__tenant_rls.sql - evidence: 없음 — verifier가 검사하는 세 조건과 마이그레이션 주석
- classification:
RlsPolicyVerifier가 RLS가 조용히 무력화되는 세 경로를 전부 확인한다 — policy 없음/RLS 미활성 · 런타임 롤이BYPASSRLS보유 · 런타임 롤이 테이블을 소유(FORCE 없으면 면제). 세 번째가 가장 놓치기 쉽다. 그리고 unique index 하나에도 같은 축의 주석이 있다 — tenant 격리가 컬럼에 의존하면 그 테이블의 모든 uniqueness 요구에 그 컬럼이 들어가야 하고,(value)만의 unique index는 다른 tenant가 그 값을 썼다는 이유로 insert를 실패시켜 버그이자 정보 유출이 된다. 다만analysis/05§97이 기록하듯 verifier 자신이 "반드시 보호돼야 하는 table"의 부재를 성공으로 인정하는 결함을 갖는다. - missing-verification: 세 조건을 하나씩 깨서 verifier가 실제로 잡는지 확인하지 않았다
- relations:
concept:rls-three-preconditions·reference:hibernate-filter-is-not-a-security-boundary·reference:tenant-column-belongs-in-every-unique-constraint
CASE — search_path가 풀로 돌아간 커넥션에 남아 다음 tenant가 상속한다
- slug:
search-path-survived-the-return-to-the-pool - readiness:
READY - source:
analysis/05§13.2 - code:
.../experimental/multitenancy/SchemaMultiTenantConnectionProvider.java의 reset 구현과 javadoc - evidence: 없음 — 구현과 javadoc
- classification:
search_path는 세션 설정이라 풀로 돌아간 커넥션이 여전히 마지막 tenant의 스키마를 들고 있다. 다음 borrower — 아마 다른 tenant, 또는 tenant 없는 백그라운드 job — 가 어떤 statement도 틀리지 않은 채로 거기서 읽고 쓴다. 해법은 반환 시NEUTRAL_SCHEMA = "pg_catalog"로 되돌리는 것이다. 같은 성질이 H2의 로컬 타임아웃 설정에도 있고(세션 스코프라 트랜잭션이 끝나도 남는다) 그쪽은 매 트랜잭션 전 재적용으로 실무상 가려진다. - missing-verification: 풀 재사용에서 잔존을 실제로 관측하지 않았다
- relations:
reference:isolation-settings-must-be-transaction-local·reference:session-scoped-settings-outlive-the-transaction·concept:four-multitenancy-strategies
CASE — tenant별 풀이 개별적으로 합리적이고 그 합이 서버 상한을 넘긴다
- slug:
tenant-pools-summed-past-the-server-ceiling - readiness:
READY - source:
analysis/05§13.2 - code:
.../experimental/multitenancy/TenantPoolBudget.java의 두 ceiling과 javadoc - evidence: 없음 — 구현과 javadoc
- classification: database-per-tenant는 특정한 방식으로 실패한다 — 각 tenant의 풀은 개별적으로 합리적이고 그 합이 아니다. 50 tenant × 10 커넥션 =
max_connections가 100인 서버에 500 커넥션이고, 실패는 idle이던 것 포함 모든 tenant에 동시에 connection refusal로 도착한다.TenantPoolBudget이 두 ceiling(풀 개수와 커넥션 총합)을 갖는 이유도 적혀 있다 — pool count만으로는 풀 크기 차이를 무시하고, connection total만으로는 각자 스레드와 모니터링을 가진 무한한 수의 작은 풀을 허용한다. 다만analysis/05§98이 기록하듯 예산이 새 pool 크기를 계산하지 않아 ceiling을 넘길 수 있다. - missing-verification: 다중 tenant 풀을 실제로 세워 상한 초과를 재현하지 않았다
- relations:
concept:requires-new-connection-cost·concept:four-multitenancy-strategies·reference:deadline-narrows-in-three-stages
REFERENCE — Hibernate filter는 보안 경계가 아니다
- slug:
hibernate-filter-is-not-a-security-boundary - readiness:
READY - source:
analysis/05§13.2 - classification: Hibernate filter는 엔티티 쿼리에 적용되고 native SQL·bulk DML·
getReference·L2 캐시를 통해 도달하는 것에는 적용되지 않는다. 그래서 filter만 믿는 설계는 다른 tenant의 행으로 가는 경로 여러 개를 열어 둔다. 기준은 "이 격리를 우회하는 경로가 몇 개인가"이고, ORM 기능은 그 답이 항상 0이 아니다. - scope: tenant·소유자·가시성 격리 전반. 실제 경계는 DB(RLS)나 별도 가드(
TenantAwareRepositoryGuard)여야 하고, filter는 편의로만 쓴다. - exceptions: 읽기 경로만 있고 native·bulk가 구조적으로 금지된 좁은 컨텍스트면 filter로 충분할 수 있다. 다만 그 금지를 ArchUnit 같은 것이 강제해야 하고, 강제가 없으면 전제가 유지되지 않는다.
- relations:
concept:rls-three-preconditions·case:three-ways-rls-does-nothing·reference:tenant-column-belongs-in-every-unique-constraint
REFERENCE — tenant 컬럼이 있는 테이블의 모든 unique 제약에 그 컬럼이 들어가야 한다
- slug:
tenant-column-belongs-in-every-unique-constraint - readiness:
READY - source:
analysis/05§13.1 - classification: tenant 격리가 컬럼에 의존하면 그 테이블의 모든 uniqueness 요구가 그 컬럼을 포함해야 한다.
(value)만의 unique index는 다른 tenant가 그 값을 이미 썼다는 이유로 한 tenant의 insert를 실패시키는데, 그것은 버그이자 정보 유출이다 — 존재하지 않아야 할 행의 존재를 알려준다. - scope: discriminator column 전략을 쓰는 모든 테이블. 같은 논리가 partial unique index와 exclusion constraint에도 적용된다.
- exceptions: 전역적으로 유일해야 하는 값(외부 시스템의 식별자)은 tenant를 포함하지 않는 것이 맞다. 다만 그때는 그 값이 tenant 간에 노출되는 것이 의도임을 적어야 한다.
- relations:
case:three-ways-rls-does-nothing·reference:hibernate-filter-is-not-a-security-boundary
REFERENCE — 격리 설정은 트랜잭션 로컬이어야 한다
- slug:
isolation-settings-must-be-transaction-local - readiness:
READY - source:
analysis/05§13.1, §13.2 - classification: tenant 바인딩·
search_path처럼 격리를 결정하는 세션 설정은 트랜잭션과 함께 되돌아가야 한다. 세션 스코프면 풀로 돌아간 커넥션이 그것을 들고 있고 다음 borrower가 상속한다. PostgreSQL에서는set_config(name, value, true)의 세 번째 인자가 그것을 보장한다. - scope: RLS tenant 바인딩·schema 라우팅·타임아웃 등 세션 상태로 표현되는 모든 격리. 대응물이 없는 벤더에서는 반환 시 명시적 reset이 대안이다.
- exceptions: 커넥션이 tenant에 고정 할당되는 database-per-tenant에서는 세션 스코프가 문제가 되지 않는다. 다만 그때는 풀 예산이 새 문제가 된다.
- relations:
case:search-path-survived-the-return-to-the-pool·reference:session-scoped-settings-outlive-the-transaction·concept:rls-three-preconditions
DECISION — 클래스패스에 있는 것은 실행 동의가 아니다
- slug:
classpath-presence-is-not-consent - readiness:
READY· decision-status:ADOPTED - source:
final/document.md#10-3·analysis/05§13 - decision-evidence:
.../experimental/ExperimentalFeatureGate.java의 javadoc · 세 클래스(RlsTenantSessionBinder·ConsistencyAwareDataSourceRouter·SchemaTenantMigrationOrchestrator)의 package-private 생성자와enabledBy정적 팩토리 - grounds:
concept:four-multitenancy-strategies·reference:hibernate-filter-is-not-a-security-boundary - classification: experimental 모듈은 transitive하게 도착할 수 있고, jar가 있다는 이유로 스스로 켜지는 tenant-isolation이나 replica-routing 기능은 가능한 최악의 기본값이다. 게이트가 결정의 부재를 활성화가 아니라 에러로 만든다. 그리고 그것이 실제로 강제되도록 세 클래스가 생성자를 package-private으로 바꾸고
enabledBy팩토리만 남겼다 — "게이트를 static이 아니라 파라미터로 받는 게 요구사항을 시그니처의 일부로 만든다: caller는 컴파일러가 요구하는 인자를 잊을 수 없다." - relations:
concept:four-multitenancy-strategies·reference:runtime-membership-decides-severity·decision:one-root-owns-the-master-switch
CASE — IdFactory.newId()의 “never-before-used” 문구 정밀화
- slug:
a01-f002-idfactory-newid - readiness:
READY - source:
analysis/01-domain-core.md#L200 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: Fact: interface는 저장소 collision check를 요구하지 않고 sample test도 전역 uniqueness를 증명하지 않는다.
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — response/LRO invariant enforcement boundary
- slug:
a02-f001-lro - readiness:
READY - source:
analysis/02-shared-contract.md#L139 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
Envelope,BulkEnvelope,Operation,PageMeta의 문서상 valid shape가 factory tests에는 고정되어 있지만 public canonical constructor에서 강제되지 않는다. raw constructor 사용이 실제로 허용된 extension surface인지, 아니면 constructor-level validation으로 invalid state를 막아야 하는지 결정이 필요하다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — DomainContextKey same-name different-type collision
- slug:
a02-f002-domaincontextkey - readiness:
READY - source:
analysis/02-shared-contract.md#L143 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: key identity가 name only인 반면 retrieval은 requested type cast를 수행한다. 동일 name의 다른
Class<T>key를 선언하는 것이 forbidden contract라면 creation-time collision 방지 또는 registry rule/test가 필요하고, 의도적으로 허용한다면 failure semantics를 문서화할 필요가 있다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — bounded operational record identifiers
- slug:
analysis-finding-a02-f003 - readiness:
READY - source:
analysis/02-shared-contract.md#L147 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
OperationalRecordjavadoc은 namespace/key를 bounded라고 설명하지만 constructor는 blank 여부만 확인한다. provider key size/character-set 제한을 shared contract가 소유해야 하는지 확인이 필요하다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — permission component grammar
- slug:
analysis-finding-a02-f004 - readiness:
READY - source:
analysis/02-shared-contract.md#L151 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: permission은 colon segment 수, blank, normalization은 강제하지만 segment character grammar는 제한하지 않는다. registry SSOT가 더 좁은 grammar를 요구한다면 shared value object와 parity test가 필요하다.
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — messaging schema qualification boundary
- slug:
analysis-finding-a02-f005 - readiness:
READY - source:
analysis/02-shared-contract.md#L155 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 현재 JDK-only test는 exact resource/digest/selected semantic vectors를 검증한다. Draft 2020-12 validator 호환성은 별도 qualification evidence가 필요하며 현재 module test 성공만으로 이를 주장해서는 안 된다.
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — notification admin atomic claim contract가 service에서 사용되지 않음
- slug:
analysis-finding-a03-f001 - readiness:
READY - source:
analysis/03-application-core.md#L328 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: - Fact:
AdminOperationStorePort.claim()과JpaAdminOperationStore.claim()은 존재하지만NotificationAdminApplicationService는 redrive/reconcile/suppress/provider-state에서findByOperationId -> side effect -> save를 사용한다. - Why it matters: 동일 operation id의 concurrent 요청이 둘 다 side effect를 실행할 수 있으며, 이는 claim javadoc이 명시한 과거 race와 동일하다. - Verification: 동일 operation id/command를 barrier로 동시에 호출하고 destructive action invocation count가 1인지 검증하는 concurrency regression test. - Candidate direction: service가 command fingerprint를 계산해 atomic claim을 먼저 수행하고 claimed/replay/conflict/in-progress를 분기. - Tech-Log: CASE + OPEN QUESTION/DECISION 후보. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — notification derived idempotency key가 32-bit hash
- slug:
analysis-finding-a03-f002 - readiness:
READY - source:
analysis/03-application-core.md#L336 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: - Fact: fallback key는
Integer.toHexString(Objects.hash(...))다. - Why it matters: 서로 다른 request의 collision을 배제할 수 없어 javadoc의 강한 uniqueness 표현과 실제 guarantee가 맞지 않는다. canonical fingerprint 비교가 있으므로 silent convergence보다는 false conflict 위험이 중심이다. - Verification: known Java hash collision fixture 또는 property search로 distinct canonical request가 같은 derived key를 만들 수 있음을 확인하고 downstream conflict behavior를 고정. - Candidate direction: canonical plan에 대한 SHA-256/HMAC 계열 digest. - Tech-Log: OPEN QUESTION/REFERENCE 후보. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — legacy storage/notification compatibility surface의 제거 조건 추적
- slug:
analysis-finding-a03-f003 - readiness:
READY - source:
analysis/03-application-core.md#L344 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: - Fact: deprecated/legacy 계약이 production wiring에 여전히 사용된다. - Why it matters: 이름만 보고 dead로 삭제할 수 없고 adapter/runtime migration이 먼저 끝나야 한다. - Verification: external production reference 0 + characterization replacement + config path removal을 migration gate로 사용. - Tech-Log: DECISION/REFERENCE 후보.
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — isolation vocabulary와 legacy routing capability의 시차
- slug:
analysis-finding-a03-f004 - readiness:
READY - source:
analysis/03-application-core.md#L351 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: - Fact:
Isolation에는 stricter levels가 있지만 legacyTransactionPorttemplate은 READ_COMMITTED로 고정되고 test도 stricter routing을 planned라고 명시한다. - Why it matters: public vocabulary만 보고 이미 지원되는 capability로 오해할 수 있다. - Verification: future routing이 추가될 때 use-case policy -> adapter transaction definition test를 함께 추가. - Tech-Log: OPEN QUESTION 후보. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — notification consumer는 diagnostic failure를 authoritative failure로 바꿀 수 있다
- slug:
analysis-finding-a04-f002 - readiness:
READY - source:
analysis/04-adapter-outbound-support.md#L216 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 이 finding은 support logger의 consumer semantics를 추적하면서 발견했다.
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — notification fail-open consumer가 logger failure를 격리하지 않는다
- slug:
analysis-finding-a04-f004 - readiness:
READY - source:
analysis/04-adapter-outbound-support.md#L569 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: - Observed fact:
FailOpenNotificationProvider는 send와 logSuccess를 동일 try에 두고 catch 안의 logFailure를 보호하지 않는다. - Runtime evidence: successful send 뒤 debug logger failure가 warn failure observation을 만들었고, provider failure 뒤 warn logger failure는 caller까지 전파됐다. - Comparison: messaging은 같은 shared logger를observeQuietly로 이미 격리하고 regression test를 갖는다. - Why it matters: diagnostics가 business/provider outcome을 바꿔서는 안 된다는 non-authoritative observation 원칙이 consumer마다 달라진다. - Verification:022afocused probe; notification에 throwing-logger regression 추가. - Candidate: notification에서 observation isolation 또는 shared logger no-throw contract. - Tech-Log: CASE + DECISION 후보. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — support README가 current architecture registry/history와 drift
- slug:
analysis-finding-a04-f005 - readiness:
READY - source:
analysis/04-adapter-outbound-support.md#L579 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: - Observed fact: SSOT 위치, CLAUDE.md 존재 여부, HTTP logger 존재 여부가 current source와 불일치. - Why it matters: support module의 dependency policy와 비교 설계를 읽는 사람이 현재 architecture를 잘못 이해한다. - Verification:
019raw search/history. - Candidate: README를modules.json/current consumer topology에 맞춰 갱신. - Tech-Log: 보통 refactor/doc maintenance; 독립 CASE 우선순위는 낮음. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — cache-redis/httpclient의 support project dependency 필요성 재검증
- slug:
analysis-finding-a04-f006 - readiness:
READY - source:
analysis/04-adapter-outbound-support.md#L587 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: - Observed fact: 두 leaf 모두 Gradle support dependency는 있지만 current production Java support reference는 0이고 support resource도 없다. - Why it matters: 불필요 edge는 classpath와 architecture narrative를 실제 필요보다 넓힌다. - Verification: dependency 제거 후 각 leaf focused test + app composition test. - Status: candidate only; downstream leaf exhaustive analysis 전에는 confirmed dead edge로 단정하지 않음. - Tech-Log: OPEN QUESTION / refactor candidate.
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — encode가 발급한 2046~2048-byte cursor를 decode가 거부한다
- slug:
analysis-finding-a05-f001 - readiness:
READY - source:
analysis/05-adapter-outbound-persistence-jpa.md#L366 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 문제는 decoded payload size를 decode 전에 추정하는 helper다.
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — CapabilitySupport.constraints의 bounded/report-safe 계약이 타입에서 강제되지 않음
- slug:
a05-f003-capabilitysupport-constraints - readiness:
READY - source:
analysis/05-adapter-outbound-persistence-jpa.md#L645 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: - Observed: 100,000-character constraint accepted. - Observed: capability list는
JpaPlatformReport를 통해 actuator endpoint model에 포함됨. - Observed: current shipped composition은 static short literals만 생성. - Impact: 현 composition incident가 아니라 public API invariant gap; fork/dynamic composition에서 bound가 caller discipline에 의존. - Candidate fix: type-level max length/vocabulary 또는 report projection에서 bounding. - Tech-Log: OPEN QUESTION/DECISION 후보; 실제 외부 dynamic source가 확인되면 CASE 승격 가능. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — RetryDecision.reason의 “bounded” 설명과 constructor contract 불일치
- slug:
a05-f004-retrydecision-reason - readiness:
READY - source:
analysis/05-adapter-outbound-persistence-jpa.md#L654 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: - Observed: 100,000-character reason accepted. - Observed: current retry metrics는 reason을 tag로 사용하지 않음. - Impact: 현재 cardinality defect로 확인되지 않음. - Candidate: length bound를 추가하거나 javadoc의 low-cardinality claim을 실제 사용 범위에 맞게 좁힘.
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — application-supplied JpaRetryPolicy가 valid execution에서 무시된다
- slug:
a05-f005-jparetrypolicy - readiness:
READY - source:
analysis/05-adapter-outbound-persistence-jpa.md#L975 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
JpaTransactionAutoConfiguration은 명시적으로 다음 overload를 제공한다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — Stable completion-evidence capability가 shipped composition에 설치되지 않는다
- slug:
a05-f006-stable - readiness:
READY - source:
analysis/05-adapter-outbound-persistence-jpa.md#L1106 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: ### 23.1 custom manager production construction = 0
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — TransactionProfileRegistry는 declarative retry 제거 후 legacy residue 후보
- slug:
a05-f007-transactionprofileregistry - readiness:
READY - source:
analysis/05-adapter-outbound-persistence-jpa.md#L1288 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 단 repository 밖 reflection/external direct construction은 source search로 알 수 없으므로 즉시 삭제 가능성까지 확정하지 않는다. module의 non-api package는 intended external이 아니라는 architecture policy와 함께 보면 cleanup 우선순위는 높아진다.
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — canonical transaction boundary documentation과 실제 dual stack 불일치
- slug:
analysis-finding-a05-f010 - readiness:
READY - source:
analysis/05-adapter-outbound-persistence-jpa.md#L1526 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: - docs/source comment는 coordinator가 PolicyTransactionPort를 구현한다고 설명. - 실제 구현체는 SpringTransactionPort. - coordinator/runtime bean은 별도로 계속 존재. - 우선순위: P2 architecture consistency / Decision 필요.
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — property-access IDENTITY entity가 batch guard를 우회한다
- slug:
a05-f012-identity - readiness:
READY - source:
analysis/05-adapter-outbound-persistence-jpa.md#L1715 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
HibernateBatchConfigurationGuard는 batching-required profile에서GenerationType.IDENTITY를 거부한다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — SpecificationPolicy는 Specification.unrestricted()를 bounded로 오인한다
- slug:
a05-f013-specificationpolicy-specification-unrestricted - readiness:
READY - source:
analysis/05-adapter-outbound-persistence-jpa.md#L2005 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: > a specification with no predicate is a full table scan wearing a builder's clothing
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — collection-fetch-pagination blocking release gate가 실제 위험을 증명하지 않는다
- slug:
a05-f014-collection-fetch-pagination - readiness:
READY - source:
analysis/05-adapter-outbound-persistence-jpa.md#L2241 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: support matrix는 다음 gate를 blocking release gate로 선언한다.
- missing-verification: ### 52.2 release registry가 가리키는 producer task는 그 test를 실행하지도 않는다
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — query SQL naming/observability composition 부재
- slug:
a05-f018-sql - readiness:
READY - source:
analysis/05-adapter-outbound-persistence-jpa.md#L2540 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 분석 문서가
query SQL naming/observability composition 부재를 P2 finding으로 분류했다. 원문 source anchor를 record 작성 시 다시 열어 코드·테스트·실행 증거를 그대로 승계한다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — export surface split SSOT
- slug:
a05-f019-ssot - readiness:
READY - source:
analysis/05-adapter-outbound-persistence-jpa.md#L2546 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: - leaf export list와 app-bootstrap consumer list가 중복 정의되고 이미 다름 - current tests pass하지만 두 목록 간 drift를 막는 single-source rule 없음 - architecture governance hardening
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — inspect()와 claim()이 만료된 COMPLETED row를 동시에 다른 상태로 해석한다
- slug:
a05-f020-inspect-claim - readiness:
READY - source:
analysis/05-adapter-outbound-persistence-jpa.md#L2659 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P1 — production idempotency lifecycle/reconciliation inconsistency.
- missing-verification:
inspect()는 row가COMPLETED이고 response payload가 있으면replayUntil이 이미 지난 값인지 확인하지 않고 무조건COMPLETED_REPLAY를 반환한다. 반면 claim path는 DB time과 expiry를 보고 만료된 row를 takeover 가능 상태로 처리한다. - relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — complete()의 replay 판정이 replayTtl 변경을 무시한다
- slug:
a05-f021-complete-replayttl - readiness:
READY - source:
analysis/05-adapter-outbound-persistence-jpa.md#L2688 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 첫 호출은 1시간, 두 번째 호출은 동일 operation/response에 9시간을 전달했다. 두 번째 호출은 semantic argument가 다른데도 same-result로 판정됐고 DB에는 최초 1시간 window가 그대로 남았다.
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — Stable runtime-role verification이 startup에서 실제 policy를 적용하지 않는다
- slug:
a05-f022-stable - readiness:
READY - source:
analysis/05-adapter-outbound-persistence-jpa.md#L2975 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P1 — production security/runtime-composition contract violation.
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — persistent byte quota가 실제 admission에서 집행되지 않는다
- slug:
analysis-finding-a05-f023 - readiness:
READY - source:
analysis/05-adapter-outbound-persistence-jpa.md#L3161 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P1 production cross-scope contract violation — persistent scope/tenant byte quota enforcement missing.
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — quota reclaim은 최대 64개 committed row만 처리하고 남은 byte를 조용히 버린다
- slug:
analysis-finding-a05-f024 - readiness:
READY - source:
analysis/05-adapter-outbound-persistence-jpa.md#L3230 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P2 production accounting correctness defect.
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — direct FileQuotaService.commit()은 만료 reservation을 commit한다
- slug:
a05-f025-filequotaservice-commit - readiness:
READY - source:
analysis/05-adapter-outbound-persistence-jpa.md#L3250 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P2 production API-contract defect.
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — recovery queue의 enqueue()는 concurrent upsert가 아니다
- slug:
a05-f026-enqueue - readiness:
READY - source:
analysis/05-adapter-outbound-persistence-jpa.md#L3271 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P2 production concurrency/idempotency defect.
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — cleanup crash-reclaim은 MAXIMUM_ATTEMPTS를 우회해 poison item을 무한 재시도할 수 있다
- slug:
a05-f027-maximum-attempts - readiness:
READY - source:
analysis/05-adapter-outbound-persistence-jpa.md#L3300 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P2 production liveness / bounded-retry defect.
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — provider 호출 뒤 recipient projection write가 lease fencing을 우회한다
- slug:
analysis-finding-a05-f028 - readiness:
READY - source:
analysis/05-adapter-outbound-persistence-jpa.md#L3444 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P1 production concurrency/correctness defect. provider side effect와 authoritative outcome write 사이의 lease handoff에서 stale writer가 살아남는다. 결과에 따라 중복 전송 위험 판단, retry/reconciliation state, attempt count가 새 holder의 흐름과 충돌할 수 있다.
- missing-verification: 실제 fenced helper도 완전하지 않다.
saveProjectionHeldBy()/transitionHeldBy()는id + lease_owner + lease_fence만 조건으로 두고lease_until > now는 확인하지 않는다. PostgreSQL에서 이미 만료되어stillHeld조건이 0건인 row에 동일 owner/fence write를 실행하면UPDATE 1이었다. - relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — reconciliation FOR UPDATE SKIP LOCKED는 worker 처리 구간을 claim하지 않는다
- slug:
a05-f029-for-update-skip-locked - readiness:
READY - source:
analysis/05-adapter-outbound-persistence-jpa.md#L3478 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P2 production multi-instance coordination defect. reconciliation은 send 자체가 아니라 provider 상태 조회/상태 projection이어서 recipient dispatch P1보다 영향도를 낮게 잡지만, 두 worker가 같은 job을 처리할 수 있다는 class contract는 깨진다.
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — V8 atomic admin claim은 production service에 연결되지 않았고 completion 모델도 미완성이다
- slug:
analysis-finding-a05-f030 - readiness:
READY - source:
analysis/05-adapter-outbound-persistence-jpa.md#L3509 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P2 production idempotency/wiring defect. V8에서 만든 fix가 dead path이며 completion state machine도 이어지지 않는다. DB transaction 안에서 수행되는 redrive/suppress 일부 경로는 마지막 unique conflict가 loser transaction을 rollback시켜 결과를 완화하지만, reconcile/provider runtime control처럼 action과 final audit insert가 하나의 동일 DB transaction으로 묶이지 않는 경로까지 전체적으로 exactly-once operation claim을 보장하지 못한다.
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — vendor selector의 fail-fast 계약이 shipped composition에 설치돼 있지 않다
- slug:
analysis-finding-a05-f031 - readiness:
READY - source:
analysis/05-adapter-outbound-persistence-jpa.md#L4073 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P2 confirmed. 오타 난 vendor 값은 startup을 실패시키기는 하지만, 그 실패는 property를 지목하지 않는다 —
PersistenceVendorSettings가 막겠다고 선언한 바로 그 증상이다. app-bootstrap의PersistenceVendorProdSafetyValidator도 도움이 되지 않는다. 그 validator는 prod profile에서 값이h2인지만 보고 알 수 없는 값은 통과시킨다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — nightly workflow가 광고하는 세 가지 중 하나를 lane이 실제로 관측하지 않는다
- slug:
analysis-finding-a05-f032 - readiness:
READY - source:
analysis/05-adapter-outbound-persistence-jpa.md#L4257 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
.github/workflows/jpa-nightly.yml:122-129는 이 lane이 검사하는 것을 세 가지로 적는다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — selected base card jpa-flyway-migration의 producer가 현재 revision에서 실패한다
- slug:
a05-f033-jpa-flyway-migration - readiness:
READY - source:
analysis/05-adapter-outbound-persistence-jpa.md#L4375 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P1 confirmed. 수정은 assertion을 stream의 현재 applied set으로 갱신하는 것이고, 재발 방지는
PostgreSqlOptionalStreamLifecycle이 이미 쓰는 방식(stream별 버전 목록을 한 곳에 고정)을 base stream에도 적용하는 것이다. 더 근본적으로는 이 lane이 tag lane과 완전히 분리돼 있다는 구조 자체가 재검토 대상이다 — 5개 tag lane이 green이라는 사실이 readiness lane의 상태에 대해 아무것도 말해주지 않는다. - missing-verification:
postgresqlSecurityBaselineIntegrationTest의 실패는 분석 환경 제약이지 결함이 아니다.verifyFullAcceptsTrustedHostAndRejectsHostnameMismatchAndUntrustedCertificate는PostgreSqlTlsMaterial이CN=localhost/SAN=DNS:localhost로 발급한 인증서를verify-full로 검증하므로 컨테이너의 매핑 포트가 테스트 JVM의 loopback에서 열려 있어야 한다. 이번 분석은 Docker 소켓을 공유하는 형제 컨테이너 안에서 실행돼 매핑 포트가 Docker 브리지(172.17.0.1)에만 열렸고, 실패는java.net.ConnectException이다. 이 lane은 skip이 아니라 실패하도록 설계돼 있으므로(no-skip) 동작 자체는 의도대로다. 다만 "no-skip"의 대가로 Docker 호스트와 테스트 JVM이 loopback을 공유하는 환경이 이 lane의 암묵적 전제가 된다는 사실은 기록해 둔다. - relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — selected base card 3개의 evidence tag가 production code 없는 fixture로 충족된다
- slug:
analysis-finding-a05-f034 - readiness:
READY - source:
analysis/05-adapter-outbound-persistence-jpa.md#L4446 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P2 confirmed. 대비되는 반례가 같은 card 집합 안에 있다는 점이 판단을 쉽게 해 준다 —
jpa-transaction-runtime의 7개 scenario는 productionSpringTransactionPort+PostgreSqlLocalTimeoutConfigurer+PersistenceExceptionTranslator를 실제 서버에서 돌리고, deadlock 40P01, serializable 재시도, lock/statement timeout 경계, pool admission 거부,pg_terminate_backend로 만든 commit 유실의INDETERMINATE판정까지 확인한다. 즉 이 결함은 체계적인 것이 아니라 세 card에 국한된다. 수정은 tag를 옮기는 문제다 — 이미 존재하는 강한 test를 scenario로 등재하거나, 약한 scenario의covers에서 과대 tag를 떼는 것. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 폐기된 namespace guard의 탐색 domain이 operator가 읽는 두 문서를 덮지 않는다
- slug:
analysis-finding-a06-f002 - readiness:
READY - source:
analysis/06-adapter-outbound-persistence-mongo.md#L116 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P3 confirmed. guard가 막겠다고 명시한 형태(문서가 operator에게 폐기 키를 쓰라고 말하는 것)가 guard의 사각지대에서 그대로 살아 있고, 그중 하나는 복사해 쓰라고 제시된 예제다. 런타임은 영향받지 않는다 — Compose lane은
SPRING_MONGODB_URI를 공급하고, 폐기는 제거가 아니다. 수정은 두 문서의 키를spring.mongodb.*로 바꾸고, guard의 domain에 leaf의*.md를 추가하는 것이다(추가하면 위 세 곳이 즉시 red가 되므로 함께 고쳐야 한다). - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — change-streams=true는 거부되지 않고 조용히 버려지며, 그 결과 startup validator의 한 분기가 production에서 도달 불가다
- slug:
a06-f003-change-streams-true - readiness:
READY - source:
analysis/06-adapter-outbound-persistence-mongo.md#L140 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P3 confirmed. 현재 잘못된 동작을 만들지는 않는다 — change stream 실행체는 애초에 shipped되지 않는다고 CLAUDE.md가 명시한다. 문제는 (a) 문서가 refuse라고 말하는 것이 discard이고, (b) 그 결과 capability 검사 한 갈래가 test에서만 살아 있다는 점이다. 수정은 두 방향 중 하나다 — 값을 정말로 거부하거나(
requiredSecondaries와 같은 형태), 아니면 flag를 record component에서 제거해 존재하지 않는 스위치로 만드는 것. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — schema version 실패는 두 경로 중 어느 쪽도 온전하지 않다
- slug:
analysis-finding-a06-f004 - readiness:
READY - source:
analysis/06-adapter-outbound-persistence-mongo.md#L316 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
MongoFailureCategory에는 이 실패를 위한 전용 값SCHEMA_VERSION_UNSUPPORTED("The stored document's schema version is outside the supported range")가 있고, 전용 예외MongoDataSchemaUnsupportedException이documentVersion/minimumSupported/currentVersion세 정수를 공개 accessor로 노출한다. production 생성 지점은 정확히 둘이고, 각각 반쪽만 맞다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 예외 계층의 "cause를 붙이지 않는다" 규칙에 문서화되지 않은 예외가 하나 있다
- slug:
analysis-finding-a06-f005 - readiness:
READY - source:
analysis/06-adapter-outbound-persistence-mongo.md#L331 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
MongoPersistenceException의 javadoc은 두 번째 규칙을 절대적으로 서술한다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — D3 gateway가 문서화한 검사 순서에 존재하지 않는 단계가 있다
- slug:
analysis-finding-a06-f007 - readiness:
READY - source:
analysis/06-adapter-outbound-persistence-mongo.md#L489 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P2. 수정은 문서를 실제 검사로 줄이거나(정직), 선언된
timeout/maxResults를 gateway가 실제로 적용하도록 만드는 것이다. 후자를 택하면hasBody()가 처음으로 호출자를 갖게 된다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 서버 측 deadline이 경로마다 다르게 적용되고, 문서가 지목한 메커니즘은 production 호출자가 0이다
- slug:
analysis-finding-a06-f008 - readiness:
READY - source:
analysis/06-adapter-outbound-persistence-mongo.md#L584 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P2. 데이터 손상은 아니지만 platform이 스스로 선언한 자원 경계가 자신의 세 실행 경로에서 서버에 도달하지 않는다. 수정은
MongoPlatformCollectionAccess가rawOperations()대신 deadline이 붙은 접근자를 내보내거나, 세 executor가 query를 만들 때context.timeout()을 붙이는 것이다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — timeout 초과 경로가 한 observation에 success와 failure를 모두 기록한다
- slug:
analysis-finding-a06-f009 - readiness:
READY - source:
analysis/06-adapter-outbound-persistence-mongo.md#L606 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
MongoOperationRejectedException은MongoPersistenceException의 하위 타입이고, 이 throw는 같은try블록 안에 있으므로 바로 다음catch (MongoPersistenceException alreadyTranslated)가 잡아observation.failure(...)를 호출한 뒤 다시 던진다. 결과적으로 하나의 observation에success와failure가 차례로 호출된다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — collection 이름 불변식이 aggregation executor의 서명에서 깨진다
- slug:
analysis-finding-a06-f010 - readiness:
READY - source:
analysis/06-adapter-outbound-persistence-mongo.md#L701 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 현재 노출은 없다(§41: 미배선). 그러나 fork가 이 executor를 배선하는 순간 두 가지가 동시에 생긴다 — registry가 보장한다고 적힌 불변식의 예외 하나, 그리고 관측·실패번역 없이 도는 실행 경로 하나. 판정: P2. 수정은 서명에서
String collection을 없애고context.collectionProfile()을 registry로 해석하는 것, 그리고 실행을DefaultMongoImperativeExecutor.executeInternal(...)안으로 옮기는 것이다. 후자는 §32에서 본 deadline 문제도 함께 해결한다(현재 aggregation은maxTime을 스스로 붙이므로 그 부분만은 이미 옳다). - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — MongoRegexPolicy.forbidden()은 금지하지 않는다
- slug:
a06-f011-mongoregexpolicy-forbidden - readiness:
READY - source:
analysis/06-adapter-outbound-persistence-mongo.md#L724 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: "금지"가 별도 상태가 아니라 최대 길이 1로 표현돼 있다.
validate(pattern, flags)의 네 검사를 길이 1짜리 패턴^에 대해 따라가면 — 길이 1 ≤ 1 통과, flags 없음 통과,requireAnchored && startsWith("^")통과,hasNestedQuantifier("^")는 그룹이 없으므로 false 통과 — 수용된다. 그리고^는 모든 문자열에 매치된다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — recordApplied는 문서화된 fence 계약을 구현하지 않고, 보호를 역전시킨다
- slug:
a06-f013-recordapplied - readiness:
READY - source:
analysis/06-adapter-outbound-persistence-mongo.md#L868 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P2. 수정은
saveCheckpoint와 같은 모양이다 —recordApplied도 저장된 fence를 조건으로 삼고, duplicate-key를 잡아 platform 예외로 번역하는 것. 지금은 test도 이 경계를 보지 않는다:MongoMigrationFencingTest.ledgerWritesCarryTheirFence는 fence 값이 전달되는지만 보고,MongoMigrationLaneTest의 superseded 테스트는 checkpoint만 다룬다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — index diff가 실제로 비교하는 것은 두 필드뿐이다
- slug:
analysis-finding-a06-f014 - readiness:
READY - source:
analysis/06-adapter-outbound-persistence-mongo.md#L894 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
MongoIndexDescriptorView의 javadoc이 "reduced to the fields a diff can compare"라고 스스로 한정하는 것은 사실이지만, 그 축소의 결과(무엇이 감지 불가가 되는지)는 어디에도 적혀 있지 않고,MongoIndexDiff.render()가 CI artifact로 쓰이도록 설계돼 있으므로 "빈 보고서 = 일치"로 읽힌다. 판정: P2. 최소 수정은MongoIndexDescriptorView에expireAfter와sparse를 추가하고compare에서 비교하는 것, 그리고actual.hidden() && !declared.hidden()에 대한unhide항목을 두는 것이다. 그것이 과하다면 최소한 비교 대상 필드 집합을 diff 출력에 함께 적어 "빈 보고서"가 무엇을 뜻하는지 읽는 사람이 알 수 있게 해야 한다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — TTL이 두 곳에 선언되고, 규칙을 가진 쪽은 아무도 쓰지 않는다
- slug:
analysis-finding-a06-f015 - readiness:
READY - source:
analysis/06-adapter-outbound-persistence-mongo.md#L911 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 1.
MongoIndexManifest.expireAfter(Duration)— 검증은 생성자의isNegative()하나. 2.MongoTtlPolicy/MongoTtlIndexDescriptor+MongoTtlPolicyValidator— 세 가지 실질 규칙: 최소 보존기간 1분(그 아래는 한 번의 sweep으로 전체 population을 지운다), expiry 필드의 BSON 타입이date인지(아니면 MongoDB가 조용히 무시한다), 그리고 읽기가expiresAt > applicationNow를 거는지(TTL monitor는 임의 간격으로 돌므로 만료된 문서는 그때까지 계속 읽힌다). - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — Flamingock lease로는 어떤 migration도 실행할 수 없고, javadoc은 다르게 적는다
- slug:
a06-f016-flamingock - readiness:
READY - source:
analysis/06-adapter-outbound-persistence-mongo.md#L926 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
FlamingockLockAdapter.fence()는UNFENCED(-1)를 반환하고, 그 이유를 정직하게 적는다 — 로컬 카운터로 fencing을 흉내내면 "look like fencing and protect nothing". 여기까지는 옳다. 문제는 그 다음 문장이다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — changeStreams flag는 false로 고정돼 있는데, 소비자 bean은 그것과 무관하게 조립된다
- slug:
a06-f018-changestreams-false - readiness:
READY - source:
analysis/06-adapter-outbound-persistence-mongo.md#L1053 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P2. 수정은 셋 중 하나다:
changeStreams를 실제 flag로 되살려 소비자 조립의 조건으로 쓰거나, 소비자 bean이 조립될 때 CHANGE_STREAM capability를 startup에서 검증하거나, 최소한MongoPlatformSettings의 주석을 현재 사실("source는 출하됐고 소비자도 조립된다")로 고치는 것. 지금 주석은 운영자가 읽으면 틀린 결론에 도달한다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — recovery package에 쓰이는 어휘와 쓰이지 않는 어휘가 나란히 있다
- slug:
analysis-finding-a06-f019 - readiness:
READY - source:
analysis/06-adapter-outbound-persistence-mongo.md#L1072 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
135-...§8.3의 검색 결과를 정리하면, 소비자가 실제로 쓰는 것과 아닌 것이 갈린다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 프로파일의 TLS·타임아웃·풀·Stable API가 driver에 도달하지 않는다
- slug:
a06-f020-tls-stable - readiness:
READY - source:
analysis/06-adapter-outbound-persistence-mongo.md#L1145 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P1. 수리는 이미 있는 형태를 따르면 된다 —
MongoClientSettingsBuilderCustomizerbean 하나가MongoClientSettingsFactory(또는 그build로직)를 Boot의 빌더에 적용하게 하는 것. 그때MongoCredentialResolver도 비로소 경로에 들어온다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — admin gateway의 두 audit 경로 중 하나만 fail-closed다
- slug:
analysis-finding-a06-f021 - readiness:
READY - source:
analysis/06-adapter-outbound-persistence-mongo.md#L1173 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
MongoAdminGateway.execute는 모든 audit 쓰기를audit(...)헬퍼로 보내고, 그 헬퍼는 sink 실패를MongoOperationRejectedException으로 바꾼다 — "an administrative operation that cannot be audited does not run".MongoAdminAuditStateMachineTest.anUnauditableCommandDoesNotRun이 그것을 고정한다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 태그 allowlist는 규약이지 강제가 아니다
- slug:
analysis-finding-a06-f022 - readiness:
READY - source:
analysis/06-adapter-outbound-persistence-mongo.md#L1179 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
MongoObservationConvention의 javadoc은 강제라고 말한다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — sharding admin gateway의 네 작업 중 셋은 어떤 입력으로도 완료될 수 없다
- slug:
analysis-finding-a06-f023 - readiness:
READY - source:
analysis/06-adapter-outbound-persistence-mongo.md#L1265 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P2. 데이터 위험은 없다 — 거부는 fail-closed이고, 오히려 안전한 방향으로 틀렸다. 위험은 능력이 문서상 존재하고 실제로는 없다는 것이며, 그 사실이 발견되는 시점은 운영자가 프로덕션 클러스터에서 reshard를 실행하려는 순간이다. 수정은 세 메서드가
MongoAdminCommand.over(...)+MongoAdminApproval.of(command, approver, expiry)를 만들어 2인자execute에 넘기고,ReshardApproval의 증거를 그 승인의 전제로 쓰는 것이다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — promotion 증거 어휘가 둘이고, gate는 하나만 검사한다
- slug:
analysis-finding-a06-f024 - readiness:
READY - source:
analysis/06-adapter-outbound-persistence-mongo.md#L1289 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
MongoAdvancedPromotionEvidence.REQUIRED는 여섯 범주다:stable-platform,actual-topology,security,migration,failure,runbook.MongoAdvancedPromotionGate.verify(...)가 그 여섯을 전부 검사한다 — 그리고 그 파일에는 고쳐진 결함이 주석으로 남아 있다: "migrationwas inMongoAdvancedPromotionEvidence.REQUIREDand not here, so the gate demanded five of the six categories it declares… which is the shape MNG-008 names: a gate that certifies more than it ran." - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 구현 없는 4개의 계약 중 셋은 그 사실을 적고, 하나는 적지 않는다
- slug:
analysis-finding-a06-f025 - readiness:
READY - source:
analysis/06-adapter-outbound-persistence-mongo.md#L1308 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
MongoSearchOperations·MongoTimeSeriesOperations·MongoVectorSearchOperations는 모두 동일한 문단을 담는다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 커버리지 gate 둘이 나란히 있고, 하나는 발화할 수 없다
- slug:
analysis-finding-a06-f026 - readiness:
READY - source:
analysis/06-adapter-outbound-persistence-mongo.md#L1367 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P2. 두 인증 lane(7.0/8.0)의 커버리지 주장이 무효다. 수정은 형제를 따르면 된다 —
run(...)이 실행할 contract 집합을 인자로 받거나, runner가 실제로 호출된 것만executed에 넣는 것. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — release gate가 실제로 차단하는 것은 hermetic test 3개이고, mongo용 CI workflow는 없다
- slug:
analysis-finding-a06-f027 - readiness:
READY - source:
analysis/06-adapter-outbound-persistence-mongo.md#L1394 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P2. 이것은 개별 코드 결함이 아니라 이 leaf의 검증 지형이다. 그리고 앞선 sub-scope들에서 찾은 것들 — §67의 change stream 소실, §75의 TLS 미적용, §85의 sharding 미완료, §56의 fence 계약 — 이 왜 살아남았는지를 설명한다: 그것들을 잡을 lane은 릴리스를 막지 않고 CI에서 돌지 않는다. 수정은 두 갈래다. (a) 컨테이너 lane 중 최소한
mongoReplicaSetTest·mongoMigrationTest·mongoSecurityIntegrationTest를 blocking contract로 승격하고, (b) JPA와 같은 형태의 workflow를 추가하는 것. - missing-verification: 차단 계약 셋 전부가
topology=none, 즉 컨테이너가 필요 없는 hermetic 클래스다. experimental 셋은 어느 build 파일에도 등록되지 않은 task를 가리킨다(grep mongoShardedTest build.gradle→ 매치 0; 스크립트가 그 사실을 스스로 적는다: "registered by no build file"). 그리고 컨테이너가 필요한 여섯 lane —mongoReplicaSetTest·mongoFailoverTest·mongoMigrationTest·mongoCompatibilityTest·mongoSecurityIntegrationTest·mongoPerformanceTest— 은 차단 목록에 하나도 없다. - relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 소비자가 없는 fixture 셋
- slug:
analysis-finding-a06-f028 - readiness:
READY - source:
analysis/06-adapter-outbound-persistence-mongo.md#L1417 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
138-...§8.1의 소비자 계수에서 test·testkit 양쪽 모두 0인 타입이 셋이다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 모듈의 존재 논거인 UuidCodec에 production 소비자가 없다
- slug:
a07-f001-uuidcodec - readiness:
READY - source:
analysis/07-adapter-outbound-identifier.md#L57 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**��� 있으면 함께 재개방 - classification: 판정: P2. 코드 자체에는 결함이 없다 — 30줄짜리 유틸이고 자기 스펙을 통과한다. 문제는 §1의 논거다. 모듈을
adapter-outbound밖에 두는 근거로 "UUID id/codec 능력"을 들고 있는데, 그 능력은 아무도 쓰지 않고 같은 일이 저장소 곳곳에서 각자 수행된다. 나머지 두 타입(가명화·업로드 식별자)만으로도 non-IO 능력 모듈의 논거는 성립하므로, 수정은 둘 중 하나다:UuidCodec을 실제 단일 경로로 만들거나(그러면 §4가 먼저 고쳐져야 한다), 모듈의 논거에서 빼는 것. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — normalize는 canonical이 아닌 입력을 받아 다른 UUID로 조용히 바꾼다
- slug:
a07-f002-normalize - readiness:
READY - source:
analysis/07-adapter-outbound-identifier.md#L73 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 도달성. 지금 이 메서드를 부르는 production 코드는 없다(§3). 그래서 현재 노출은 0이고,
UuidCodec을 단일 경로로 승격하는 순간 결함이 된다. 판정: P2. 수정은input.length() != 36이거나 대시 위치가 8-13-18-23이 아니면 먼저 거부하는 것 — 또는 계약 문구를 실제 동작(JDK 관대 파싱)에 맞추는 것이다. 전자가 문서가 말하는 바다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — CLAUDE.md의 의존성 서술이 세 항목 모두 틀렸다
- slug:
a07-f004-claude - readiness:
READY - source:
analysis/07-adapter-outbound-identifier.md#L115 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: >
:application-code,:domain-core,:shared-contract(Gradle matrix). Currently only:domain-core+com.github.f4b6a3:uuid-creatorare declared in build.gradle. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — README의 세 가지 사실 오류
- slug:
analysis-finding-a07-f005 - readiness:
READY - source:
analysis/07-adapter-outbound-identifier.md#L134 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 셋 다 메커니즘 자체는 실재하고 동작한다 — 틀린 것은 이름과 버전이다. P3.
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — CLAUDE.md가 대는 두 가드 중 하나는 저장소에 없다
- slug:
a07-f006-claude - readiness:
READY - source:
analysis/07-adapter-outbound-identifier.md#L144 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 1. ArchUnit
identifier_adapter_does_not_depend_on_other_adapters_or_bootstrap— 존재한다(§2, 상수명은 대문자). confirmed. 2..claude/hooks/ca_import_gate.pyG4가 쓰기 시점에 차단 —.claude/디렉터리에는settings.local.json하나뿐이고hooks/하위 디렉터리도ca_import_gate.py도 tracked 되어 있지 않다(140-...§8.4e). - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — R1과 R2의 설정 취급이 비대칭이고, 검증된 쪽은 하나뿐이다
- slug:
analysis-finding-a08-f002 - readiness:
READY - source:
analysis/08-adapter-outbound-fileserver.md#L92 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: R2에서는
strict-path-securty같은 오타가 컨텍스트를 실패시킨다. R1에서는app.file-export.maximum-rowz=10같은 오타가 조용히 무시되고, 설정했다고 믿는 상한이 적용되지 않은 채 기본값 1,000,000이 쓰인다. 두 selector가 같은 leaf의 같은 성격 설정인데 한쪽만 fail-closed다. P3 — R1은 문서상 "compatibility only"이므로 우선순위를 낮춘다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 문서가 지목한 기본값 위치와 test 목록이 실제와 다르다
- slug:
analysis-finding-a08-f003 - readiness:
READY - source:
analysis/08-adapter-outbound-fileserver.md#L106 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: - README는 R2 selector가 "
app-bootstrap/application.yml에서false로 기본값을 갖는다"고 적는다. 그 파일에app.fileserver.enabled도app.file-export.enabled도 없다(142-...§8.4e;app.fileserver로 걸리는 두 줄은 주석이다). 실효 기본값은 "속성 부재 →@ConditionalOnProperty미매치 → bean 없음"이고 동작은 옳지만, 문서가 가리킨 자리에는 그 키가 없다. - README의 Tests 목록 첫 항목FilePublicationContractTest는 이 leaf가 아니라application-core에 있다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 발행 rename만 경로 기반이고, 그것을 지키는 것은 이 모듈이 "근사에 불과하다"고 적은 사전검사다
- slug:
analysis-finding-a08-f004 - readiness:
READY - source:
analysis/08-adapter-outbound-fileserver.md#L360 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P3. 실제 악용에는 스토리지 루트 안쪽 쓰기 권한이 필요하고, 이 leaf가 그 루트의 소유자·권한을 증명하는 것은 R2 경로(
LocalPersistentRootAttestor)뿐이며 플랫폼 저장소 루트의 증명은app-bootstrap의 startup validator 몫이다. 그래서 도달성은 배포 형상에 달려 있다. 심각도를 P3로 두는 이유는 그것이고, 그럼에도 기록하는 이유는 모듈 자신의 문서가 이 패턴을 명시적으로 불충분하다고 선언했다는 점이다. 수정은 발행 rename을SecureDirectoryWalk.inParentOf로 옮겨 부모 서술자 상대move를 쓰고,sizeOf를channels.readAttributes로 바꾸는 것이다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — TransferBufferPool.maxBorrowedBytes()가 자기 회귀 test를 지목하는데 그 test가 읽지 않는다
- slug:
a08-f005-transferbufferpool-maxborrowedbytes - readiness:
READY - source:
analysis/08-adapter-outbound-fileserver.md#L394 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
TransferBufferPool은 대여 중 바이트의 최대치를 추적하고 javadoc에 이렇게 적는다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — production 판정이 두 개의 리터럴 프로파일 이름에 걸려 있다
- slug:
analysis-finding-a09-f001 - readiness:
READY - source:
analysis/09-adapter-outbound-objectstorage.md#L90 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 이 저장소가
application-prod.yml을 싣고 있으므로 현재 형상에서는 맞는다. 그리고150-...§8.2c에서 확인했듯 같은 방식으로 production을 판정하는 leaf는 이것 하나뿐이다 — 저장소 전체가 공유하는 production 판별 장치가 없다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 그러나 R0 경계가 문서에만 있고 compile 경로에서 닫히지 않는다
- slug:
analysis-finding-a09-f004 - readiness:
READY - source:
analysis/09-adapter-outbound-objectstorage.md#L469 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P2. 데이터 위험은 없다 — 없는 port는 호출될 수 없다. 위험은 (a) 운영자가 켰다고 믿는 기능이 없다는 것과 (b) 아무도 쓰지 않는 서명 자격증명 핸들이 프로세스 수명 동안 살아 있다는 것이다. 수정은 셋 중 하나다: coordinator를 조건부로 조립하거나, R0인 동안
DIRECT_UPLOAD/DIRECT_MULTIPART요구를 compile 단계에서 provider 종류와 무관하게 거부하거나, capability가 켜져도 presigner를 만들지 않도록 조립을 뒤로 미루거나. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — nonce replay 경계가 결과를 읽고 버린다
- slug:
analysis-finding-a09-f006 - readiness:
READY - source:
analysis/09-adapter-outbound-objectstorage.md#L573 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
ClaimResult는CLAIMED/EXACT_REPLAY/TERMINAL_REPLAY셋인데, 어느 값이든 발행은 그대로 진행된다. claim 결과가 바꾸는 것은 terminal 기록을 쓸지 여부뿐이다. 인터페이스 javadoc은 자신을 "Durable compare-and-set nonce replay boundary"라고 부르지만, 경계로서 무엇도 막지 않는다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — README readiness 표와 build.gradle 주석이 실제 소스와 어긋난다
- slug:
a10-f001-readme - readiness:
READY - source:
analysis/10-adapter-outbound-cache-redis.md#L105 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P2. 코드 결함이 아니라 문서 결함이지만 이 저장소 기준으로는 무겁다. 첫째, 이 leaf의 README는 "readiness는 서로 다른 세 가지 질문이며 하나로 합치면 안 된다"는 문장으로 시작하는, 정직한 readiness 보고를 자기 주제로 삼는 문서다. 둘째, 방향이 이례적이다 — 보통의 drift는 없는 것을 있다고 하는데 여기는 있는 것을 없다고 한다. fork가 이미 있는 4,900 LOC를 다시 구현하거나, 조립되지 않은 채 존재하는 코드의 존재 자체를 모르게 된다. 셋째,
RedisSdkAutoConfiguration의 javadoc이 "until this class existed the method had no production caller at all"이라고 적는 것으로 보아 이 클래스가 README 문장보다 나중이다 — 조립이 진행됐는데 서술이 따라가지 않았다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — SDK가 선언한 두 진입점에 구현이 없다
- slug:
analysis-finding-a10-f003 - readiness:
READY - source:
analysis/10-adapter-outbound-cache-redis.md#L235 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P2. 데이터 위험은 없다 — 없는 타입은 잘못된 답을 주지 않는다. 위험은 API 계약의 신뢰다: 이 leaf의 공개 표면 중 가장 먼저 읽히는 두 타입이 구현되지 않은 상태이고, 대칭 test가 그 사실을 가리지 못한다(인터페이스끼리만 비교하므로). sub-scope 01의 §5와 방향이 반대이면서 원인은 같다 — 조립이 절반이다. 수정은 이미 존재하는 26개 구현을 묶는
LettuceRedisOperations/LettuceReactiveRedisOperations두 클래스를 추가하고,ApiParityTest에 "두 facade는 구현을 가진다"는 검사를 더하는 것이다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — Pub/Sub 채널만 렌더 크기 검증을 받지 않는다
- slug:
a10-f004-pub-sub - readiness:
READY - source:
analysis/10-adapter-outbound-cache-redis.md#L247 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
RedisKeyRules.requireRenderedSize(...)는 조립된 키 문자열이 설정된 최대 바이트를 넘지 않는지 본다. 세 형제 중 하나만 그것을 부른다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 다중 키 fan-in 중 HyperLogLog merge만 budget이 없다
- slug:
a10-f005-hyperloglog-merge - readiness:
READY - source:
analysis/10-adapter-outbound-cache-redis.md#L261 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 같은 성격(여러 키를 읽어 하나에 쓰는, 비용이 입력 크기에 비례하는 연산)의 세 형제를 비교하면 요구가 다르다.
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — requireIdentifier의 다섯 검사 중 둘은 도달할 수 없다
- slug:
a10-f006-requireidentifier - readiness:
READY - source:
analysis/10-adapter-outbound-cache-redis.md#L359 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P3. 보안 효과는 그대로다 — 두 형태 모두 거부된다. 잃는 것은 진단 품질(운영자가 "must not contain a mail address" 대신 일반적인 문자 클래스 메시지를 본다)과, 두 분기가 실제로는 아무 일도 하지 않으면서 검증이 다섯 겹인 것처럼 보이게 만드는 점이다. 수정은 두 분기를 지우고 문자 클래스 메시지에 그 의도를 포함시키거나, 검사 순서를 뒤집어 구체적 형태를 먼저 판정하는 것이다.
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 패턴 구독의 R2 승인만 호출자가 아니라 배포에 대해 이루어진다
- slug:
analysis-finding-a10-f007 - readiness:
READY - source:
analysis/10-adapter-outbound-cache-redis.md#L469 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P3. 배포 수준 게이트는 실재하고 네임스페이스 봉쇄도 있으므로 열린 구멍은 아니다. 기록하는 이유는 같은 위험 등급에 두 가지 다른 승인 모델이 적용되고 그 차이가 문서화돼 있지 않기 때문이다. 수정은
patternSubscribe서명에AdvancedOperationPermit을 추가하거나, javadoc에 "배포 수준 승인"임을 명시하는 것이다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — permit 정책 이름이 세 곳에 문자열로 존재하고 교차 검사가 없다
- slug:
analysis-finding-a10-f008 - readiness:
READY - source:
analysis/10-adapter-outbound-cache-redis.md#L486 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P3. 확정은 sub-scope 05로 이월한다 —
RedisCommandPolicyLoaderTest가 정책 이름 집합을 검사하는지 그 sub-scope에서 확인한다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — close()가 실패하면 drain 스케줄러 스레드가 남는다
- slug:
a11-f001-close - readiness:
READY - source:
analysis/11-adapter-outbound-httpclient.md#L97 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P3. 스레드가 데몬이라 JVM 종료를 막지는 않고, 레지스트리당 하나이며, 닫기 실패라는 조건이 필요하다. 그러나 주석이 "a thread per rotation cycle"을 명시적 위험으로 적고 resource-bound suite가 그것을 잡으려 존재하는데, 정확히 그 누수가 실패 경로에 남아 있다. 수정은 스케줄러 종료를
finally로 옮기는 한 줄이다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — POOL_ROUTE_EXCEEDS_TOTAL 위반 코드는 발화할 수 없다
- slug:
a11-f002-pool-route-exceeds-total - readiness:
READY - source:
analysis/11-adapter-outbound-httpclient.md#L122 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: // ClientProfileValidator:192 if (profile.pool().maxConnectionsPerRoute() > profile.pool().maxTotalConnections()) { out.add(violation("POOL_ROUTE_EXCEEDS_TOTAL", profile, "pool.max-connections-per-route")); } ```
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 위반 코드 34종 중 22종이 어떤 test에서도 이름으로 확인되지 않는다
- slug:
analysis-finding-a11-f003 - readiness:
READY - source:
analysis/11-adapter-outbound-httpclient.md#L140 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
ClientProfileValidator가 내는 코드는 34종이다. 저장소 전체의test/testkitsource set에서 그 문자열을 참조하는 파일 수를 세면(168-...§8.4b): - missing-verification: test 두 개(
ClientProfileValidatorTest106줄)가 그룹으로 몇 개를 묶어 확인하지만(rejectsSimpleFactoryAndUnacknowledgedHttp3AndJdkRoutePool), 나머지 22종은 분기를 지워도 초록으로 남는다. 코드 자체는 현재 옳다 — 위험은 회귀다. P3. 수정은@ParameterizedTest로 코드별 최소 케이스를 한 벌 놓는 것이고, 34종이 모두 결정적으로 정렬된 목록을 내므로 그 형태가 자연스럽다. - relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — Number가 허용 목록에 있어 가변 숫자 타입이 REPLAYABLE로 인증된다
- slug:
a11-f004-number - readiness:
READY - source:
analysis/11-adapter-outbound-httpclient.md#L225 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P3. 도달성이 좁고(원자 카운터를 요청 DTO에 넣어야 한다), 검사 전체의 방향은 보수적이며,
aMutableValueIsOneShottest가 일반적인 가변 객체는 잡는다. 기록하는 이유는 이 검사가 "records, enums, strings, boxed primitives and immutable collection views replay; anything else is treated as one-shot"라고 선언하는데Number한 줄이 그 선언보다 넓기 때문이다. 수정은 boxed primitive 여덟 종과BigInteger/BigDecimal을 명시하거나,java.util.concurrent.atomic패키지를 제외하는 것이다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — BoundedDataBufferFlux의 두 연산자가 이름만 있고 아무것도 하지 않는다
- slug:
a11-f006-boundeddatabufferflux - readiness:
READY - source:
analysis/11-adapter-outbound-httpclient.md#L400 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P3. 수정은 두 연산자를 지우고 javadoc이
doOnDiscard와 드라이버의 역할을 정확히 적게 하거나, 취소 경로에서 실제로 해제해야 할 것이 있다면 그것을 구현하는 것이다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 동적 대상 DNS 핀 능력 검사가 블로킹 오버로드에만 있다
- slug:
a11-f007-dns - readiness:
READY - source:
analysis/11-adapter-outbound-httpclient.md#L559 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P3. 위험은 fork가 리액티브 전송을 추가하면서
dynamicTargetStable=true, validatedDnsPinning=false로 선언하는 경우 — 주석이 "cannot serve a dynamic target no matter what itsdynamicTargetStableflag says"라고 못박은 정확히 그 조합이 리액티브 쪽에서는 통과한다. 수정은 같은 세 줄을 리액티브 오버로드에 복사하는 것이다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — check에 붙은 verifyJsonSchemaRuntimeGraph가 실행되면 실패한다
- slug:
a12-f001-check-verifyjsonschemaruntimegraph - readiness:
READY - source:
analysis/12-adapter-outbound-messaging.md#L66 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P2. 금지 조건 쪽(YAML 계열·Jackson 2
core/databind부재)은 여전히 옳게 동작하지만, 필수 조건 쪽이 버전 드리프트로 고장 나 있어 게이트 전체가 통과할 수 없다. 결과는 이 저장소가 다른 곳에서 반복해 경계한 바로 그 상태다 — 붙어 있으나 초록일 수 없는 게이트는 사람들이 건너뛰는 법을 배우게 만든다. 수정은 필수 좌표에서 버전을 떼고group:name만 확인하거나(닫힘 조건은 "무엇이 없는가"이지 "어느 패치인가"가 아니다), 잠금 파일에서 버전을 읽어 비교하는 것이다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — README의 jackson-databind 부재 주장이 현재 상태와 어긋난다
- slug:
a12-f002-jackson-databind - readiness:
READY - source:
analysis/12-adapter-outbound-messaging.md#L102 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 판정: P3. 코드 결함은 아니다 —
OutboxEnvelopeJson의 손수 짠 직렬화는 그 자체로 문제가 없다. 기록하는 이유는 그 선택의 근거로 적힌 사실이 더 이상 성립하지 않는다는 점이고, fork가 그 문장을 읽고 "databind가 없다"를 전제로 다른 결정을 내릴 수 있기 때문이다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — AUTHENTICATION_FAILED를 지우지 않는다는 resumeHealthy의 보장이, 관리자 평면에 노출된 2단계 시퀀스로 우회된다
- slug:
a13-f002-authentication-failed-resumehealthy - readiness:
READY - source:
analysis/13-adapter-outbound-notification.md#L304 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
ProviderRuntime.resumeHealthy는 자신이 지키는 성질을 javadoc에 명시한다: - missing-verification: 테스트가 이것을 잡지 못하는 이유 — 해당 테스트는 세 전이를 각각 새 런타임에서 확인하고 합성을 확인하지 않는다:
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — "모든 reveal은 감사된다"고 선언한 AccessContext를 읽는 코드가 저장소에 하나도 없다
- slug:
a13-f003-accesscontext - readiness:
READY - source:
analysis/13-adapter-outbound-notification.md#L414 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: // ContactPointProtector.java:11 / Decrypt a value for an audited purpose. */ ContactPointValue reveal(ProtectedContactPoint protectedValue, AccessContext context); ```
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — Thymeleaf 예외 메시지 삭제 가드가 프로덕션이 타지 않는 오버로드에만 있다
- slug:
a13-f004-thymeleaf - readiness:
READY - source:
analysis/13-adapter-outbound-notification.md#L460 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
ThymeleafStringTemplateEngine은 같은 클래스에render두 개를 갖는다. 모드 없는 쪽은 예외를 잡아 메시지를 버리고, 그 이유를 명시한다: - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 음수 Retry-After 헤더가 throttle 결과 대신 IllegalArgumentException을 만든다
- slug:
a13-f005-retry-after-illegalargumentexception - readiness:
READY - source:
analysis/13-adapter-outbound-notification.md#L620 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 파서는
Long.parseLong이 받아들이는 값을 그대로Duration으로 만든다: - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — "상한을 두고 읽는다"는 본문 핸들러가 전부 읽은 뒤에 자른다
- slug:
analysis-finding-a13-f007 - readiness:
READY - source:
analysis/13-adapter-outbound-notification.md#L779 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
BodySubscribers.mapping(upstream, finisher)의 finisher는 upstream이 완료된 뒤 그 결과에 적용된다. upstream은ofByteArray()이고, 그것은 무제한으로 요청하여 본문 전체를 힙에 모은다. 잘라내기는 그 다음이다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — SigV4가 서명한 host에 포트가 없어, 기본 포트가 아닌 엔드포인트에서 서명이 어긋난다
- slug:
a13-f008-host - readiness:
READY - source:
analysis/13-adapter-outbound-notification.md#L815 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: SigV4의 정규 요청은 실제로 전송되는
Host헤더 값을 서명해야 하고, 기본이 아닌 포트는 그 값에 포함된다. 요청 자체는host헤더를 싣지 않으며(JdkNotificationHttpGateway.RESTRICTED가 거부하고 JDK가 URI에서 채운다), JDK는localhost:4566같은 값을 보낸다. 서명은localhost에 대해 이루어졌다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — SigV4 서명 키 파생이 비밀을 지울 수 없는 String으로 승격시킨다
- slug:
a13-f009-sigv4-string - readiness:
READY - source:
analysis/13-adapter-outbound-notification.md#L828 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
secretAccessKey는NotificationSecretMaterialHandle이 제공하는 지울 수 있는 가변 사본이다. 그 핸들의 존재 이유가 "operation-scoped mutable secret copy that wipes itself on close"이고,close()가Arrays.fill(bytes, (byte) 0)을 한다. 이 한 줄이 그 바이트를 불변String으로 복사하며, 그String은 GC가 가져갈 때까지 힙에 남고 어떤close()도 지울 수 없다. 힙 덤프 한 장이면 회수된다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — FCM만 "커밋 후 응답 손실 = ambiguous" 규칙 밖에 있고, 그 FCM이 두 계약 집합 어디에도 없다
- slug:
analysis-finding-a13-f010 - readiness:
READY - source:
analysis/13-adapter-outbound-notification.md#L941 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
ProviderResults의 존재 이유가 클래스 javadoc에 쓰여 있다: - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 공유 provider 계약이 8종 중 3종에서만 상속되고, 강제 장치가 없다
- slug:
analysis-finding-a13-f011 - readiness:
READY - source:
analysis/13-adapter-outbound-notification.md#L974 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: §28.1·§28.3.
ProviderAdapterContract의 javadoc이 약속하는 성질("a new provider cannot be added without answering the same three questions")을 지키는 장치가 없다. 이 저장소는 같은 실패 양식에 대해EndpointGuardCallSiteTest와verifyNotificationApiSurface라는 구조적 강제를 이미 두 번 만들었으므로, 형태는 이미 있다 — 적용되지 않았을 뿐이다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — WebProblemSanitizer.alreadySafe가 죽은 메서드이고 그 안의 조건도 죽어 있다
- slug:
a14-f002-webproblemsanitizer-alreadysafe - readiness:
READY - source:
analysis/14-adapter-inbound-web.md#L364 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 호출자 0(프로덕션·테스트 모두). 그리고
!input.isBlank()가 이미 통과했으므로input.trim()은 비어 있을 수 없고,toLowerCase는 공백 여부를 바꾸지 않는다 — 삼항의REDACTED가지는 도달 불가다. javadoc이 약속하는 용도("for asserting a message is already safe")를 수행하는 코드가 없다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 플랫폼 요청 컨텍스트가 서블릿에는 생산자가 없고, 리액티브에는 익명 액터로 고정되어 있다
- slug:
analysis-finding-a14-f003 - readiness:
READY - source:
analysis/14-adapter-inbound-web.md#L487 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 서블릿 절반.
WebMvcRequestContextHolder.store(...)의 호출자가 저장소 전체에서 0이다. 그런데 그것을 읽는 쪽은 자동설정이 등록한다: - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 프레임워크 자유 신원 모델과 교차 테넌트 가드가 프로덕션에서 한 번도 참조되지 않는다
- slug:
analysis-finding-a14-f004 - readiness:
READY - source:
analysis/14-adapter-inbound-web.md#L546 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: §11.1.
security패키지 11개 파일이 서로만 참조하고 바깥에서 들어오는 화살표가 없다.AuthenticationView를 만드는 프로덕션 코드가 없으므로WebSecurityContextBridge.resolve(...)가 호출될 수 있는 상태 자체가 없다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — publicPaths가 RestrictedPathRule보다 먼저 등록되어, 넓은 공개 경로 하나가 관리 평면 규칙을 조용히 덮는다
- slug:
a14-f005-publicpaths-restrictedpathrule - readiness:
READY - source:
analysis/14-adapter-inbound-web.md#L566 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: §11.3.
RestrictedPathRule의 javadoc은 이 규칙이 존재하는 이유를 "an application-level policy consulted later cannot recover from a transport that already let the request through"로 설명한다. 그런데permitAll(publicPaths)이 그 규칙보다 먼저 등록되어 정확히 그 일을 한다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 용량 보호 계층 전체(41 main files)가 자기 테스트 픽스처 안에서만 실행된다
- slug:
analysis-finding-a14-f006 - readiness:
READY - source:
analysis/14-adapter-inbound-web.md#L661 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 즉 이 플랫폼을 그대로 배포하면 요청 본문 크기 상한도, 응답 크기 상한도, 요청 데드라인도, 동시성 상한도, 큐 상한도 없다.
WebRequestBudget.standard()가 정의하고WebBudgetCatalog가 담고 있는 값들은 아무도 읽지 않는다(§15.3). - missing-verification: 실패 시나리오 — 배포된 API에 무제한 청크 본문이 도착한다.
WebMvcBudgetFilter가 필터 체인에 없으므로BoundedHttpServletRequest가 스트림을 감싸지 않고,WebBudgetMeter가 바이트를 세지 않는다. 컨테이너 기본값(TomcatmaxPostSize는multipart/form-data와 폼 인코딩에만 적용되고 임의 본문에는 적용되지 않는다) 외에 상한이 없다. 같은 요청에 대해 동시성 상한도 없으므로SemaphoreAdmissionController가 내기로 되어 있던 503도 나오지 않는다. - relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 리액티브 전송에는 속도 제한 경로가 하나도 없다
- slug:
analysis-finding-a14-f007 - readiness:
READY - source:
analysis/14-adapter-inbound-web.md#L685 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: §15.2. 배선된 유일한 속도 제한기
RateLimitInterceptor는WebMvcConfigurer.addInterceptors로 붙는 MVC 전용 장치다.WebFluxThrottleFilter가 리액티브 대응물이지만 등록되지 않는다(§16.1).webflux/autoconfigure/WebFluxPlatformAutoConfiguration의 11개 빈에도 없다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 멱등 실행 계층과 durable-operation 표면이 픽스처에서만 조립된다
- slug:
analysis-finding-a14-f008 - readiness:
READY - source:
analysis/14-adapter-inbound-web.md#L777 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: §19.1·§19.2. 이 sub-scope에서 프로덕션 컨텍스트에 들어가는 것은
WebExecutionEvidenceTracker(두 요청 필터가 만든다)와 빈InMemoryWebOperationCatalog둘뿐이다. 나머지 38개 main 파일 — 게이트, 두 invoker, 응답 writer, 지문 공장, 헤더 정책, 명령 인코더, 승인 판정, 응답 계획, 코덱, 그리고operationasync9종 전부 — 는 테스트와 testkit에서만 생성된다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 의미 지문이 길이 프레이밍 없이 구분자로 만들어진다
- slug:
analysis-finding-a14-f009 - readiness:
READY - source:
analysis/14-adapter-inbound-web.md#L791 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: §19.4. 경로 변수 값과 헤더 값이 이스케이프 없이 구분자로 이어붙는다. 값 자체에 그 구분자가 들어가면(퍼센트 인코딩
%1F를 Spring이 디코딩해@PathVariable로 전달한다) 서로 다른 두 요청이 같은 정규 문자열을 만들 수 있다 — 경로 변수가 둘 이상인 연산에서 하나를 통제하면 구성 가능하다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 배선된 캐시 필터의 no-store가 배선된 조건부 읽기 경로를 무력화하고, 둘을 조정하려고 만든 패키지는 참조 0이다
- slug:
a14-f010-no-store - readiness:
READY - source:
analysis/14-adapter-inbound-web.md#L883 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
CacheControlFilter가 모든 응답에Cache-Control: no-store를 붙인다. RFC 9111에서no-store는 "어떤 캐시에도 저장하지 말라"는 지시다. 규격을 지키는 클라이언트는 응답을 보관하지 않으므로, 나중에 그 리소스에 대해If-None-Match를 보낼 근거(저장된 표현과 그 ETag)를 갖지 못한다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — maxArrayElements가 선언만 되고 강제되지 않으며, 바이트 예산 백스톱도 없다
- slug:
a14-f011-maxarrayelements - readiness:
READY - source:
analysis/14-adapter-inbound-web.md#L990 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 읽는 코드가 저장소 전체에 0개다(§27.1). Jackson 3의
StreamReadConstraints에는 배열 원소 수 상한이 없으므로BoundedJsonFactory가 넘길 자리도 없고, 매퍼 쪽에서도 검사하지 않는다. - missing-verification: 백스톱이 없다. 이 위험을 막을 상위 장치가
WebMvcBudgetFilter의 요청 바이트 상한인데, SS4에서 확인했듯 그 필터는 등록되지 않는다(§16.1). 서블릿 컨테이너의 기본값도 임의 본문에는 적용되지 않는다. 따라서 지금 이 플랫폼에는 JSON 배열 원소 수에 대한 상한이 어느 계층에도 없다. - relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 선언된 Advanced 능력 11개 중 9개는 켜는 방법이 없다
- slug:
a14-f014-advanced - readiness:
READY - source:
analysis/14-adapter-inbound-web.md#L1251 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: §35.1.
webAdvancedTest레인이 이 능력들을 전부 돌리고(web-advanced-nightly.yml:46·web-advanced-release.yml:53),WebAdvancedRollbackIT가 능력마다 플래그를 켰다 껐다 하며 롤백을 검증한다. 그러나 그 검증은WebAdvancedFeatureFlags.of(feature)라는 테스트 전용 값에 대한 것이고, 배포가 실제로 조작할 수 있는 스위치는 두 개뿐이다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — VirtualThreadProfile.propertyName()이 아무것도 게이트하지 않는 이름을 반환한다
- slug:
a14-f015-virtualthreadprofile-propertyname - readiness:
READY - source:
analysis/14-adapter-inbound-web.md#L1263 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: §35.3.
VirtualThreadProfile:75는"backend.web.advanced.virtual-threads.enabled"를 하드코딩한다. 실제 게이트는mvc-virtual-threads이고, 같은 능력에 대해 enum이 계산하는 이름도mvc-virtual-threads다. 이 메서드는 호출자가 0이므로 지금 오작동을 만들지는 않지만, "The property that turns this on"이라는 javadoc과 함께 잘못된 이름을 발행한다 — 운영자가 이 문서를 보고 설정하면 켜지지 않는다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 이 leaf의 리액티브 절반 29개 파일은 어떤 출하 배포에서도 활성화될 수 없다
- slug:
analysis-finding-a14-f016 - readiness:
READY - source:
analysis/14-adapter-inbound-web.md#L1362 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
@ConditionalOnWebApplication(type = REACTIVE)는 Spring Boot의WebApplicationType이REACTIVE일 때만 참이다.deduceFromClasspath()는DispatcherServlet과ServletContainerInitializer가 있으면 WebFlux가 함께 있어도SERVLET을 고른다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — SpringMvcRouteInventoryCollector 138줄에 참조가 하나도 없다
- slug:
a14-f017-springmvcrouteinventorycollector - readiness:
READY - source:
analysis/14-adapter-inbound-web.md#L1478 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 저장소 전체에서 이 타입 이름이 등장하는 곳은 자기 파일의 클래스 선언과 생성자 두 줄뿐이다. 테스트도 없다.
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — WebPlatformStartupValidator가 시작 시 실행되지 않는다
- slug:
a14-f018-webplatformstartupvalidator - readiness:
READY - source:
analysis/14-adapter-inbound-web.md#L1484 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: §43.1·§43.2. 이름이 약속하는 시점에 아무도 부르지 않는다. 같은 leaf의 fileserver 하위 트리는 같은 종류의 시작 검증을 app-bootstrap의
@Bean으로 연결했고(SS10 §39.1), 그 근거를 "Better to refuse to start"로 적었다. 플랫폼 쪽 검증기에는 그 연결이 없다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 크로스 스택 게이트가 검증하는 조립은 픽스처의 조립이고, 플랫폼의 조립이 아니다
- slug:
analysis-finding-a14-f019 - readiness:
READY - source:
analysis/14-adapter-inbound-web.md#L1559 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 이 leaf는 이 저장소에서 가장 정교한 검증 장치를 갖고 있다 — 여섯 소스셋, 다섯 커스텀 레인, 세 런타임 패리티 비교, 실제 Nginx 컨테이너, 실제 소켓 고장 주입. 그리고 §47.1이 보여주듯 그 장치가 세우는 애플리케이션은 픽스처 애플리케이션이다.
- missing-verification: 이 leaf는 이 저장소에서 가장 정교한 검증 장치를 갖고 있다 — 여섯 소스셋, 다섯 커스텀 레인, 세 런타임 패리티 비교, 실제 Nginx 컨테이너, 실제 소켓 고장 주입. 그리고 §47.1이 보여주듯 그 장치가 세우는 애플리케이션은 픽스처 애플리케이션이다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 원인 사슬 순회가 2-순환에서 무한 루프에 빠지고, 저장소는 이미 그 사례를 이름으로 적어 두었다
- slug:
analysis-finding-a15-f001 - readiness:
READY - source:
analysis/15-adapter-inbound-grpc.md#L155 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 나머지 세 지점의 영향도 —
MvcDisconnectDetector와WebFluxDisconnectDetector는 요청 처리 중 클라이언트 연결 끊김을 판정하는 곳이고,TransactionRetryClassifier는 트랜잭션 재시도 여부를 판정하는 곳이다. 셋 다 요청 스레드 위에서 실행된다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 설정 바인딩이 마스터 스위치 밖에서 일어난다. 컴포지션 루트의 자기 규칙과 어긋난다
- slug:
analysis-finding-a15-f002 - readiness:
READY - source:
analysis/15-adapter-inbound-grpc.md#L171 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: §3.4.
GrpcServerProperties는 두 경로로 등록된다 —GrpcServerConfig의@EnableConfigurationProperties(게이트 안쪽)와CaSkeletonApplication의@ConfigurationPropertiesScan(게이트 바깥). 후자가 있으면ca-skeleton.grpc.enabled와 무관하게 바인딩이 일어난다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 스키마 조립·계약 정체성·해시 사슬이 통째로 미배선이고, 그것을 발행할 액추에이터 엔드포인트도 등록되지 않는다
- slug:
analysis-finding-a16-f001 - readiness:
READY - source:
analysis/16-adapter-inbound-graphql.md#L242 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 1. 결정적 병합 순서. SDL 조각의 정렬을 조립기가 강제하도록 설계돼 있고(§7.4), 실제로는 Spring GraphQL의 탐색 순서를 그대로 쓴다. 조각이 하나(
skeleton.graphqls)뿐인 지금은 무해하지만, adopter가 자기.graphqls를 추가하는 순간 — 그것이 이 leaf의 문서화된 확장 방식이다 — 충돌 선언의 승자와 스키마 해시가 패키징 방식에 따라 달라질 수 있다. 2. 네 부분 계약 정체성.GraphQlSchemaContract가 "해시만으로는 호환성을 판정할 수 없다"는 판단을 타입으로 만들었는데, 그 타입을 만드는 코드가 없다. 호환성 판정이 필요한 곳(릴리스 게이트)은compat의 비교기를 직접 쓴다. 3. 운영 가시성.GraphQlPlatformActuatorEndpoint.report()가 배포된 스키마 해시 · 실행 프로파일 · 배포 모드 · 활성 능력 · 등록된 연산/페치 프로파일 수를 하나의 보고서로 낸다. 등록되지 않으므로 운영자가 "이 배포가 무엇을 켜고 있는가"를 물을 표면이 없다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — @oneOf 게이트와 런타임 검증기가 미배선이고, "플랫폼이 강제한다"는 서술이 그것을 넘어선다
- slug:
a16-f002-oneof - readiness:
READY - source:
analysis/16-adapter-inbound-graphql.md#L265 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
GraphQlOneOfPolicy의 클래스 javadoc은 "The September 2025@oneOfinput rules the platform enforces"로 시작한다. 강제하는 두 코드 — 시작 게이트와 런타임 검증기 — 는 프로덕션 호출자가 0이다. - missing-verification: 기록하는 것은 두 가지다: (a) 플랫폼 계층의 강제가 서술과 달리 존재하지 않는다, (b)
GraphQlOneOfSchemaGate.verify(sdl)가 확인하는 것은 라이브러리가 확인하지 않는 부분(멤버가 전부 nullable이고 기본값이 없어야 한다는 선언 시점 규칙)이므로, adopter가 잘못된@oneOf입력 타입을 선언하면 시작 시점이 아니라 첫 요청에서 드러난다. - relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 5계층 예산 모델에서 요청 계층만 강제되고, 나머지 파생이 전부 미배선이다
- slug:
analysis-finding-a16-f003 - readiness:
READY - source:
analysis/16-adapter-inbound-graphql.md#L366 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
GraphQlDeadlinePropagator의 javadoc이 계층 분리의 이유를 정확히 적는다: - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 연산 이름 정책의 두 구현 중 하나만 배선되고, 미배선 쪽만 GraphQlOperationNamePolicy를 쓴다
- slug:
a16-f004-graphqloperationnamepolicy - readiness:
READY - source:
analysis/16-adapter-inbound-graphql.md#L387 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: §11.2. 익명 연산 거부는 배선된
GraphQlOperationSelectionHandler가 수행하므로 강제 자체는 존재한다. 기록하는 것은 정책 객체의 이원화다 —GraphQlOperationNamePolicy(85줄)의 참조자가 미배선 인터셉터와 자기 자신뿐이고, 배선된 핸들러는 별개의policy를 쓴다. 두 정책이 "이름 있는 연산을 요구하는가"에 대해 서로 다른 답을 낼 수 있는 구조이며, 지금은 한쪽만 답한다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 설정으로 정한 파서 한계가 graphql-java에 설치되지 않는다
- slug:
analysis-finding-a16-f005 - readiness:
READY - source:
analysis/16-adapter-inbound-graphql.md#L495 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 파서 계층은 GraphQL DoS 방어의 첫 번째 관문이다 — 복잡도 계산도 구조 분석도 문서를 파싱한 뒤에 일어나므로, 파싱 자체를 폭발시키는 문서는 그 앞에서 막아야 한다.
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 프로파일별 정책 매니페스트가 미배선이라, 자격에서 해석된 프로파일이 아무 예산도 선택하지 않는다
- slug:
analysis-finding-a16-f006 - readiness:
READY - source:
analysis/16-adapter-inbound-graphql.md#L509 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: §15.2. 클라이언트 프로파일은 검증된 principal에서 정확히 해석되고 요청 컨텍스트에 실린다. 그리고 그 값이 선택하는 것은 캐시 키와 지표 태그뿐이다 — 정책은 프로파일과 무관하게 단일 빈이다.
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 파싱·검증 실패에 플랫폼 매퍼가 없다
- slug:
analysis-finding-a16-f008 - readiness:
READY - source:
analysis/16-adapter-inbound-graphql.md#L664 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: §19.3.
GraphQlRequestErrorMapper(57)가 그 목적으로 존재하고 미배선이다. 배선된 두 매퍼(GraphQlExceptionResolver·GraphQlWireErrorMapper)는 각각 리졸버 예외와 플랫폼 거부를 덮는다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 기계가 읽는 능력 매니페스트와 사람이 읽는 등급표가 커서 서명에 대해 다르게 답한다
- slug:
analysis-finding-a16-f011 - readiness:
READY - source:
analysis/16-adapter-inbound-graphql.md#L873 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 즉 이 매니페스트는 "Stable 스타터에서 켜도 되는가"를 판정한다. cursor 서명은 켤 수 있는 것으로 판정되고, 켜는 코드는 없다.
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — "기본 비활성"은 존재하지 않는 스위치의 기본값을 서술한다
- slug:
analysis-finding-a16-f012 - readiness:
READY - source:
analysis/16-adapter-inbound-graphql.md#L986 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 권고 — 그 문단을 등급에 맞춘다: "Advanced capability 는 현재
modelled등급이며 활성화 경로가 없다.GraphQlAdvancedFeatureFlags·GraphQlAdvancedModuleGuard는 그 경로가 생길 때 쓸 판정 모델이다." - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 연결 티켓·origin 정책·메시지 권한·연결 예산이 요청 경로 밖이고, 그중 일부는 STOMP 어댑터가 다른 방식으로 대체한다
- slug:
a17-f002-stomp - readiness:
READY - source:
analysis/17-adapter-inbound-websocket.md#L257 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: §12.1·§12.2. 실제 배포에서 적용되는 보안은
stomp패키지의 두 인터셉터이고, 그것은 CLAUDE.md가 서술하는 범위다("HTTP-handshake principal enforcement and client-inbound STOMP destination authorization"). - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 능력 프로퍼티 이름을 만드는 코드와 실제 게이트가 다른 접두사를 쓴다
- slug:
analysis-finding-a17-f003 - readiness:
READY - source:
analysis/17-adapter-inbound-websocket.md#L410 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: §21.2.
WebSocketAdvancedCapability.propertyName()이 반환하는backend.websocket.advanced.*를 읽는@ConditionalOnProperty가 없다. 운영자가 그 메서드가 알려 주는 키를 설정하면 아무 일도 일어나지 않고, 실제로 능력을 켜는 키는app.websocket-platform.advanced.*다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 출하되는 web 어댑터의 스위치가 활성화 모델 밖에 있다
- slug:
analysis-finding-a18-f001 - readiness:
READY - source:
analysis/18-app-bootstrap.md#L198 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: §4.1.
adapter-inbound-web은 두 런타임 멤버이므로 build-only 예외에 해당하지 않는다. 그런데 그 네 개 스위치가 조건 안의 문자열 리터럴로만 존재해MasterSwitch의 javadoc이 경계하는 상태다 — "Spread across conditions as string literals, a rename becomes a silent activation change." - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 실패는 환경 원인이며, 그 테스트의 도구 가드가 불완전하다
- slug:
analysis-finding-a18-f002 - readiness:
READY - source:
analysis/18-app-bootstrap.md#L533 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 저장소 결함이 아니다. 분석 컨테이너에
jq가 없고, 위임된 스크립트가 그것을 요구하며 exit 78로 정직하게 실패한다. - missing-verification: 저장소 결함이 아니다. 분석 컨테이너에
jq가 없고, 위임된 스크립트가 그것을 요구하며 exit 78로 정직하게 실패한다. - relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — capability 12개 중 main 코드가 읽는 것은 3개, 거부하는 것은 1개
- slug:
analysis-finding-a19-f001 - readiness:
READY - source:
analysis/19-messaging-platform.md#L231 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
MessagingCapabilities의 클래스 javadoc이 이 record의 계약을 선언한다: - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 8개 profile validator 중 조립에서 실행되는 것은 3개
- slug:
analysis-finding-a19-f002 - readiness:
READY - source:
analysis/19-messaging-platform.md#L288 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
StartupProfileValidation의 javadoc이 이미 한 번 고쳐진 같은 결함을 서술한다: - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — messaging-reliability-api는 main 13파일 · 817 LOC에 테스트가 0개다
- slug:
a19-f003-messaging-reliability-api - readiness:
READY - source:
analysis/19-messaging-platform.md#L325 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: sub-scope 01의 네 leaf 중 유일하게
src/test가 없다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 스키마 호환성 검증기는 출하 leaf에 있고, main 코드에서 호출되지 않는다
- slug:
analysis-finding-a19-f004 - readiness:
READY - source:
analysis/19-messaging-platform.md#L390 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
SchemaCompatibilityValidator(messaging-schema-api, 출하)의 main 참조는 0건이다. 테스트 1개뿐. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 호환성 게이트를 가진 두 포맷은 build-only이고, 출하되는 유일한 코덱에는 게이트가 없다
- slug:
analysis-finding-a19-f005 - readiness:
READY - source:
analysis/19-messaging-platform.md#L415 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 위험 방향이 뒤집혀 있다. 스키마 진화 검사가 존재하는 두 포맷(Avro·Protobuf)은 어떤 런타임에도 오르지 않고, 실제로 wire에 바이트를 쓰는 유일한 코덱(JSON)에는 포맷 수준의 호환성 게이트가 없다. §4.3의 포맷 독립 검증기(
SchemaCompatibilityValidator)가 그 공백을 메울 자리인데 그것도 호출되지 않는다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — messaging-cloudevents는 출하 leaf이고 starter의 의존이며 소비자가 없다
- slug:
a19-f006-messaging-cloudevents - readiness:
READY - source:
analysis/19-messaging-platform.md#L431 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 그런데 main 코드에서
DefaultCloudEventMapper를 만드는 곳은 0곳이고,CloudEventMapper·CloudEventExtensions를 참조하는 main 파일은DefaultCloudEventMapper자신뿐이다. 자동설정 28개 클래스 어디에도 CloudEvents 이름이 없다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 브로커 ACL 매니페스트의 자기 점검이 존재하지 않는다
- slug:
a19-f008-acl - readiness:
READY - source:
analysis/19-messaging-platform.md#L495 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
BrokerAclManifest(messaging-security, 출하) — main 참조 0건, 테스트 1건. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 접근 검사가 두 갈래로 존재하고, 조립된 쪽이 진단이 약한 쪽이다 (§8.3)
- slug:
analysis-finding-a19-f009 - readiness:
READY - source:
analysis/19-messaging-platform.md#L511 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: (a) 조립된 쪽 —
DefaultMessagePublisher가DestinationAccessPolicy를 직접 호출: - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 지원 매트릭스가 "모든 messaging leaf는 build-only"라고 적고, 가족 권위 문서는 그 문장이 틀렸다고 이미 기록했다
- slug:
analysis-finding-a19-f013 - readiness:
READY - source:
analysis/19-messaging-platform.md#L682 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 이 드리프트의 실질적 무게는 이 문서 전체의 심각도 판정 축과 같다(§1.1). 지원 매트릭스만 읽은 운영자는 messaging이 아무것도 출하하지 않는다고 결론 내리는데, 실제로는
messaging-kafka·messaging-rabbit·messaging-spring-boot-starter·messaging-security·messaging-observability·messaging-cloudevents·outbox/inbox/claim-check·admin plane이 전부app-bootstrap아티팩트에 실려 있다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 한 아티팩트 안의 서로 모르는 Kafka 스택 두 개 (MSG-015, 가족 문서가 미해결로 표시)
- slug:
a19-f014-kafka-msg - readiness:
READY - source:
analysis/19-messaging-platform.md#L698 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
src/messaging/CLAUDE.md가 MSG-015를 P0 미해결로 들고 있다. 현재 상태를 코드로 확인했다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — CompatibilityMatrix에 EXTENSION 등급이 있고 항목이 없으며, bridge leaf가 표 밖에 있다
- slug:
a19-f015-compatibilitymatrix-extension - readiness:
READY - source:
analysis/19-messaging-platform.md#L757 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
Tierenum은 세 값을 갖는다 —STABLE,EXPERIMENTAL,EXTENSION("Adapter SPI only; outside the supported set").ENTRIES5개는 전부 STABLE 또는 EXPERIMENTAL이고EXTENSION을 쓰는 항목은 없다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — claim-check는 starter에 배선 코드가 한 줄도 없다
- slug:
analysis-finding-a19-f018 - readiness:
READY - source:
analysis/19-messaging-platform.md#L911 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
messaging-claim-check(6 main, 418 LOC, 출하)의ClaimCheckPublisher·ClaimCheckResolver는 main 참조 0건이고,MessagingReliabilityAutoConfiguration에ClaimCheck문자열이 등장하지 않는다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — admin 스위치가 가드를 켜고 서비스는 켜지 않는다
- slug:
analysis-finding-a19-f019 - readiness:
READY - source:
analysis/19-messaging-platform.md#L960 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
app.messaging.admin.enabled=true가 만드는 bean은 넷이다:DestructiveOperationGuard,AdminOperationJournal,MessagingAdminDurabilityValidator, (BrokerTopologyInspector가 있을 때)CompositeTopologyValidator. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — messaging-admin-api는 main 25파일 · 1,613 LOC에 테스트 파일이 1개다
- slug:
a19-f020-messaging-admin-api - readiness:
READY - source:
analysis/19-messaging-platform.md#L982 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
messaging-admin-api가 담고 있는 것은 승인·다이제스트·토폴로지 계약이다 —ApprovalVerifier,HmacApprovalVerifier,ApprovalGrant,VerifiedApproval,PlanDigest,ApprovedRedrivePlan,ApprovedReplayPlan,DestructiveOperation,TopologyManifest,TopologyValidationReport등 보안에 직결되는 타입들이다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — MessagingPublicSurfaceContractTest가 가족 밖(app-bootstrap)에 있다
- slug:
a19-f022-messagingpublicsurfacecontracttest - readiness:
READY - source:
analysis/19-messaging-platform.md#L1099 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: CLAUDE.md의 public surface 정책이 "이 규칙은 문서가 아니라
MessagingPublicSurfaceContractTest가 붙들고 있다"고 말한다. 그 테스트의 위치: - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 증거 등급 모델 전체가 자동 실행 경로 밖에 있고, CLAUDE.md는 현재 시제로 서술한다
- slug:
a20-f003-claude - readiness:
READY - source:
analysis/20-grpc-platform.md#L260 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: > "현재 in-process·Netty·fault lane은 실제로 실행되어 통과하지만, 실제 배포 환경에서의 soak·performance baseline은 없다."
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 조립 경계가 정책 객체 9개를 만들고 서버를 만들지 않는다
- slug:
analysis-finding-a20-f004 - readiness:
READY - source:
analysis/20-grpc-platform.md#L298 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
GrpcPlatformAutoConfiguration이 등록하는 9개는 전부 프로파일·정책·레지스트리다. 서버도, 인터셉터 체인도, 서비스 어댑터 등록도 없다. 그리고 그것을 담당하는 타입들이 main 참조 0이다: - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — 저장소 어디에도 참조가 없는 타입 3개
- slug:
analysis-finding-a20-f005 - readiness:
READY - source:
analysis/20-grpc-platform.md#L321 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
main = 0이면서test = 0인 것, 즉 선언 파일 외에 아무 곳에서도 이름이 등장하지 않는 타입: - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — GrpcAdmissionController.tryAdmit()의 동시성 경계가 동시성 아래에서 성립하지 않는다
- slug:
a20-f006-grpcadmissioncontroller-tryadmit - readiness:
READY - source:
analysis/20-grpc-platform.md#L518 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 이 클래스는 조립된다 —
GrpcPlatformAutoConfiguration의 9개 bean 중 하나(grpcAdmissionController)다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — GrpcStreamAdmission도 같은 형태이고, per-caller 맵이 줄지 않는다
- slug:
a20-f007-grpcstreamadmission - readiness:
READY - source:
analysis/20-grpc-platform.md#L572 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: §7.1과 동일한 TOCTOU이고, 이쪽은 javadoc이 서술하는 실패 시나리오가 곧 고동시성 상황이다:
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — GrpcSerializedStreamWriter의 DROP_OLDEST가 잘못된 메시지의 바이트를 뺀다
- slug:
a20-f008-grpcserializedstreamwriter-drop-oldest - readiness:
READY - source:
analysis/20-grpc-platform.md#L595 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: 버려지는 것은
dropped인데 빼는 값은 새로 들어오는 메시지의 크기nextBytes다.GrpcStreamEnvelope는 7개 성분(streamId·sequence·kind·snapshotVersion·resumeToken·terminationReason·payload) 중 크기를 담지 않으므로, 이 지점에서 버려지는 메시지의 크기를 알 방법이 애초에 없다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — GrpcOutcomeReplay가 제거 경로 없는 인메모리 저장소다
- slug:
a20-f010-grpcoutcomereplay - readiness:
READY - source:
analysis/20-grpc-platform.md#L664 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification: javadoc은 "a small inline store"라고 부르지만 작게 유지하는 장치가 없고, 크기를 넘는 응답은 거부하면서("store it behind an object reference instead") 개수는 거부하지 않는다.
- missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
CASE — GrpcCompletionReconciler가 요청 경로에서 동기화 없는 ArrayList를 변경한다
- slug:
a20-f011-grpccompletionreconciler-arraylist - readiness:
READY - source:
analysis/20-grpc-platform.md#L678 - code: 분석 문서의 해당 finding 절에 기록된 production path · symbol · test/probe를 record 생성 시 재개방
- evidence: 분석 문서의 해당 finding 절 및 연결된
evidence/raw/**가 있으면 함께 재개방 - classification:
synchronized·Concurrent*·volatile·Lock전부 0건이고, 단일 스레드 전용이라는 javadoc 표기도 없다. 이 leaf에서 스레드 안전성을 명시적으로 다루는 유일한 클래스는GrpcSerializedStreamWriter이며(그쪽은 9개 마커로 제대로 닫혀 있다), 그 사실이 이 leaf가 동시성을 인지하고 있음을 보여준다. - missing-verification: 분석 문서의 해당 finding 절에 별도 미실행 검증이 명시되지 않았다. record 생성 시 source anchor의 code/evidence를 다시 연다.
- relations: candidate-ledger의 topic/disposition provenance를 기준으로 record 생성 시 기존 Reference/Decision과 연결
Decomposition checklist
Source integrity
- 문서 해시(
10eabd7a…)와 리비전(21234e38…)을 헤더에 기록 - 모든 노드에 source 앵커 하나 이상
- source 앵커가 제목이 함의하는 내용을 실제로 담고 있음
- 런타임 의존 주장은 evidence를 명명하거나
missing-verification에 적었고, 실행이 필요한 것은 대응하는 Open Question을 갖는다
Topic quality
- 16개 Topic 전부 공학 문제 공간이며 디렉터리 이름이 아니다 — 각각 여러 leaf/모듈을 가로지른다
- 두 Topic이 같은 인과 사슬을 임의로 쪼개지 않음. 인접한 쌍의 구분 근거:
assembly-ownership(조립이 안 됨) ↔what-a-gate-does-not-prove(검증이 그것을 못 잡음) — 다른 질문이다owner-safe-state-machines(소유권을 SQL에 적기) ↔transaction-deadline-and-pool(시간과 커넥션 예산) — 같은 leaf지만 다른 실패 축이다bounding-by-type(값의 도메인) ↔drift-direction(문서의 진술) — 전자는 코드, 후자는 문서다duplicate-mechanisms(둘 중 약한 쪽이 조립됨) ↔redis-command-admission(우회 경로의 수) — 후자는 한 leaf의 구체적 admission 구조다
- 가장 큰 Topic(
assembly-ownership18노드)이 전부 같은 판정 기준을 공유
Concept
- 각 Concept이 사건이 아니라 메커니즘의 설명이다
- Concept이 Case의 요약이 아니다 — 각 Concept은 여러 Case가 참조하는 구조를 다룬다
- 본문에 넣을 코드·상태도·표의 출처가 source 앵커에 있다
- 계약 파일이 이 종류를 아직 기술하지 않는다는 사실을 트리 머리말에 적었다
Case
- 각 Case에 구체적 사건·실험·실패·진단·검증 시퀀스가 있다
- 제목이 역사를 지어내지 않아도 이해된다
- 결론이 실제 증거로 경계 지어졌고, 증거 없는 추론은
missing-verification에 적었다 - 저장소의 javadoc·마이그레이션 헤더가 사후 기록인 경우 그 사실을 evidence 항목에 적었다
Reference
- 각 규칙이 원 사건 밖에서 재사용 가능 — 프로젝트 이름을 지워도 규칙이 남는지 확인
- Case 요약을 선언형으로 바꾼 것이 아님 —
scope와exceptions가 사건 밖의 조건을 말한다 exceptions를 전부 source에서 진술. 특히 다음 넷은 반대 사례가 실재한다:a-bean-is-not-composition-evidence— build-only leaf의 공개 API 표면a-gate-nobody-runs-reports-the-last-run— 대체 경로가 있는 messaging 인증 레인check-which-duplicate-is-wired— 조립된 쪽이 더 나은 자격 증명 회전the-startup-validator-follows-the-autoconfiguration-root— 루트가 있는데도 부르지 않는 둘
Open Question
- 답이 분석 안에 이미 있지 않음 — 여덟 개 전부 실행 또는 부팅이 필요하다
- known / unknown / next-verification이 분리됨
- 후보 선택지를 지어내지 않음
container-lanes-not-executed가 나머지 네 개(commit-ambiguity · v2-state-machine · redis-topology · pool-contract)의 상위 집합임을 relations로 표시
Decision
- 프로젝트 선택이 명시적으로 기록됨 — ADR 5건(
ADR-JPA-002·ADR-JPA-003·ADR-JPA-004·ADR-GRPC-001·ADR-GRPC-003·ADR-GRPC-ADV-001) + 코드/build.gradle/마이그레이션 헤더의 근거 주석 - "기술이 있다"를 근거로 쓰지 않음
- 권고에 그치는 것은
NEEDS_DECISION—widen-doc-contract-assertions하나
Duplication
- 두 노드가 같은 일차 목적을 갖지 않음
- 내용 복사 대신 relations 사용
- Topic을 가로지르는 참조가 있는 노드는 양쪽에서 relations로 연결했다 — 예:
a-release-gate-with-no-evidence-producer는what-a-gate-does-not-prove에 속하고learning-transfer-between-families가 참조한다
Generation summary
| kind | 총계 | 비고 |
|---|---|---|
| CONCEPT | 21 | 기존 상세 분해 유지 |
| CASE | 221 | coverage sweep에서 155건 추가 |
| REFERENCE | 49 | 기존 상세 분해 유지 |
| OPEN QUESTION | 7 | 기존 상세 분해 유지 |
| DECISION | 26 | 기존 상세 분해 유지 |
| 합계 | 324 | candidate ledger 기반 |
Candidate coverage
- explicit P1/P2/P3 findings inventoried:
200 - ledger total candidates (baseline nodes 포함):
335 EMITTED:290MERGED:45BLOCKED:0REJECTED:0UNMAPPED:0- 기존 16 Topic + coverage sweep 추가 Topic:
9
MERGED는 관련성이 아니라 동일 사건/동일 검증 단위일 때만 사용했다. 기존 Topic이나 Reference가 비슷한 문제를 다룬다는 이유만으로 finding을 소거하지 않았다.
Coverage sweep 추가 Topic
contract-domain-and-bounds— 15 Caseruntime-reachability-and-composition— 33 Caseverification-path-coverage— 28 Casestate-ownership-and-concurrency— 19 Caseschema-and-data-contracts— 7 Casetransport-and-provider-semantics— 6 Casesecurity-policy-enforcement— 5 Casedeclaration-and-document-drift— 14 Caseruntime-contract-correctness— 28 Case
Coverage Summary
- candidate-ledger:
candidate-ledger.json(73c05399007e4dfd79921c309bc697f1b15352716f00ea7e869063f683d27759) - source-manifest:
root-tree-source-manifest.json(2cf5391ab044fc0fe0413225d0cda19bef58640d52eb3369712fb0e8e1ce3b21) - total candidates:
335 - emitted:
290 - merged:
45 - blocked:
0 - rejected:
0 - unmapped:
0
터미널 증거 자산
정본은 raw이고 SVG는 표현이다(도구 README의 Evidence rule). 각 meta/*.json이
command · cwd · executedAt · exitCode · sourceRevision을 담는다. 8종 전부 exit 0이고 출력에
secret이 없어 redaction은 발생하지 않았다.
| 자산 | 연결된 노드 | raw | 한 화면에 보이는 것 |
|---|---|---|---|
tl-kafka-dedup-drift |
case:support-matrix-said-the-opposite-of-the-code |
10줄 | 코드 배열 10번째 false · 문서 표 같은 행 O · 그 플래그를 강제하는 유일한 지점 |
tl-web-six-unowned-components |
case:scan-exclusion-without-an-owner · concept:three-assembly-paths |
29줄 | 여섯 컴포넌트 애노테이션과 mainRefs=0 · 두 자동설정이 실제로 도달함 · @Bean 13/10 · @Import 0 · 13개가 전부 협력자 |
tl-grpc-release-gate-no-producer |
case:a-release-gate-with-no-evidence-producer |
15줄 | 생성 지점 4곳이 전부 자기 테스트 · Gradle 태스크 0 · CI 0/28 · 대조군 messaging |
tl-outbox-unsatisfiable-condition |
case:outbox-chain-behind-an-unsatisfiable-condition |
26줄 | 조건 사슬 · 두 뿌리 port 구현 mainRefs=0 · 같은 starter의 기본 구현 대조 |
tl-messaging-observation-noop |
case:observation-downgraded-by-the-composition |
36줄 | 6인자 생성자 선택 · NO_OBSERVATION 위임 · 방출자 넷 0 대 협력자 둘 |
tl-rotation-defect-reproduced |
case:the-same-rotation-defect-closed-once-and-reproduced |
19줄 | messaging 계약 테스트 문장 · gRPC의 get()/set() 지점 · CAS 0건 · 경합을 아는 javadoc |
tl-platform-suite-results |
case:the-second-platform-carried-the-design-not-the-wiring · open-question:container-lanes-not-executed |
13줄 | 두 가족 스위트 집계(851/579, 0 실패 0 skip) · 증거 레인 3종 · 미실행 레인 목록 |
tl-grpc-evidence-lanes |
여러 Case 공통 | 63줄 | 증거 레인 3종 Gradle 실행 원문 — Netty가 실제 소켓을 열고 통과 |
기존 probe에서 재캡처가 필요한 자산
아래는 원문이 evidence/raw/에 있으나 4줄 헤더(# evidence NNN — slug 등)를 포함한 형식이다.
렌더러는 명령 출력 원문을 기대하므로 record 작성 시 헤더 없는 clean raw로 재캡처한다.
| 노드 | 기존 raw |
|---|---|
case:high-water-mark-swallowed-a-redelivered-change |
135-mongo-change-stream-probe.txt · 135a-…java |
case:pii-through-an-exception-message |
021-support-logger-pii-probe.txt · 021a-…java |
case:scriptable-detection-bypassed-by-a-bom |
146-fileserver-*-probes.txt의 EXECUTION PROBE 블록 |
case:the-readme-recipe-does-not-start |
129-mongo-empty-type-registry-write-probe.txt · 129a-…java |
case:a-build-gate-that-is-not-in-the-build |
163-redis-*-probes.txt §8.1 |
case:five-adapters-bypass-the-single-admission-point |
165-redis-*-probes.txt §8.1 |
case:apply-is-a-setting-approval-is-not |
155-objectstorage-*-probes.txt §8.1 |
case:seven-rows-downgraded-by-the-family-itself |
213-inbound-graphql-release-probes.txt |
터미널 증거가 맞지 않는 노드
근거가 명령 출력이 아닌 것들이다. record 작성 시 코드 인용·마이그레이션 헤더 인용·diff로 대체한다.
- 저장소의 javadoc·마이그레이션 헤더가 사후 기록인 Case —
two-owners-popped-the-evidence-frame,autoconfiguration-in-name-only,a-lease-without-an-owner,h2-hid-a-column-type-mismatch,a-customizer-that-discarded-the-bound-property,a-five-second-string-that-broke-every-prod-deploy등 - 코드 경로 비교가 근거인 Case —
a-weaker-private-copy-on-the-wired-path,a-policy-reversed-by-a-later-filter,endpoint-check-only-on-upload - 컨테이너가 필요해 아직 실행하지 못한 것 —
commit-ambiguity-is-not-only-sqlstate-08(대응 Open Question 있음)
evidence/svg/는 비워 뒀다. 다이어그램은 관계를 설명할 수 있으나 사건의 발생을 증명하지 못한다는
규칙(writing-tech-log-from-analysis §Evidence)에 따라, 필요한 Case에서 개별 판단한다.