- analysis/·source-index·state.json 을 final/document.md 제2부·제3부로 접었다. SSOT 는 하나다 - 파일럿 — commit-ambiguity-as-a-result 를 새 기준으로 재선별. 후보 14 → 글감 5 (PROMOTE 5 · MERGE_INTO 3 · KEEP_IN_SSOT 4 · 보류 2). 기록 5건을 다시 썼고 그림 1개를 techviz 로 만들었다 - 재선별이 잡은 것: 제1부 §6.2·§11.1 이 자기 §13.2 와 어긋나 있었다(레인을 안 돌렸다 vs 돌렸다) — 정정. 이미 답이 나와 있던 Question 을 HEAD 재실행 질문으로 다시 세웠다. Concept 이 인용한 코드가 SSOT 에 없어 뺐다 - candidateScope·sourceRepository 기록. 나머지 43개 주제는 재선별 대기(PENDING 905) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
14 KiB
kind, slug, title, topic, project, status, sourceRevision, rootTreeNode, body, evidenceCapturedOn, assets, evidence, source
| kind | slug | title | topic | project | status | sourceRevision | rootTreeNode | body | evidenceCapturedOn | assets | evidence | source | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| CASE | a12-f002-jackson-databind | 근거를 없앤 커밋이 README 를 열고 그 줄만 두었다 | messaging-and-outbox | clean-architecture-backend-template | 게시 전 | 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 | case:a12-f002-jackson-databind | case-a12-f002-jackson-databind.body.md | 2026-09-02 |
|
|
|
근거를 없앤 커밋이 README 를 열고 그 줄만 두었다
README 가 손수 짠 직렬화의 근거로 결속 라이브러리가 클래스패스에 없다는 사실을 들고, 그 클래스의 자바독이 같은 문장을 다시 적는다. 그 좌표를 배포되는 클래스패스로 올린 커밋이 같은 자리에서 README 에 절 셋을 붙이면서 이 줄만 두고 갔다.
관계
- 매 PR 을 막는 게이트가 초록일 수 없다 같은 리프의 같은 잠금 좌표에서 갈라진 사례다. 저쪽은 판본이 올라간 커밋, 이쪽은 구성이 바뀐 커밋이다.
- README가 "노출된 setting도 bean도 없다"고 적은 능력에 production bean 여덟이 있다 두 사례 모두 README 가 적은 것과 실제 코드가 다르다.
- 문서가 지목하는 조정 레코드를 쓰는 코드가 없다 문서가 가리킨 대상이 코드에 없다는 점이 같다.
문제
봉투 직렬화를 손으로 짠 이유가 README 에 적혀 있다.
이 모듈은 결속 라이브러리를 클래스패스에 두지 않아 뼈대를 가볍게 유지하며, 그래서 봉투 직렬화는 의존성 없는 손수 짠 JSON 이라는 것이다.
결론
손수 짠 클래스는 그 서술과 어긋나지 않는다. 바깥에서 끌어오는 타입이 없다.
다만 그 클래스의 자바독이 README 와 같은 문장을 다시 적는다. 모듈이 그 라이브러리를 클래스패스 밖에 둔다는 것이다. 고칠 자리가 하나가 아니라 둘이다.
잠금 파일만의 문제도 아니다. 이 리프의 빌드 파일이 스프링 JSON 스타터와 스키마 검증기를 직접 선언한다. 그 좌표가 컴파일과 실행 클래스패스에 올라 있는 것은 그 선언의 결과다.
같은 빌드 파일의 검증 태스크는 그 좌표가 런타임 그래프에 있어야 한다고 요구한다. 문서가 부재를 근거로 삼는 동안 게이트는 존재를 조건으로 건다.
문장이 쓰일 때는 맞았다. 초기 커밋 시점에는 그 좌표가 배포되는 클래스패스 어디에도 없었다.
바꾼 것은 2026-07-31 의 메시징 R2 커밋이다. 그 커밋이 빌드 파일에 JSON 스택 둘을 선언하면서 좌표를 컴파일과 실행으로 올렸다. 같은 커밋이 README 를 열어 절 셋을 새로 붙였고, 근거가 무너진 그 문장만 그대로 두었다. 그 뒤로 이 README 를 건드린 커밋은 없다.
실시간 팬아웃 봉투는 석 주 뒤에 들어왔다. 883 파일을 건드린 커밋이 이 리프에서 만진 것은 잠금 파일과 팬아웃 어댑터와 그 봉투 클래스 셋이고, 잠금 파일에서 바뀐 것은 판본뿐이다.
문장이 근거로 삼은 것은 모듈 전체의 부재이고, 그것이 깨진 뒤 옆 파일이 봉투 JSON 조립을 그 라이브러리로 처리한다.
다만 그 봉투를 부르는 어댑터를 조립하는 곳이 저장소 어디에도 없다. 선언은 그대로 있고 그것을 부르는 실행 경로만 없다.
구세대 이름공간의 결속과 코어를 세우는 것은 검증 태스크 안의 단언 한 줄이고, 구성 단위 제외 목록이 빼는 것은 형식 계열뿐이다. 애너테이션 쪽 2.21 은 아직 두 클래스패스에 다 남아 있다. 서술이 그 이름공간을 뜻했다면 맞지만, 문장에 이름공간이 없다.
판정은 P3 다. 코드 결함은 아니다.
README 를 읽고 이 리프의 의존성 정책을 판단하는 사람은 지금 없는 사실을 근거로 삼게 된다.
검증 환경
Gradle : 9.0.0 확인 방식 : README 와 자바독 대조, 빌드 선언과 잠금 파일 확인, 리프 main 의 참조 전수 확인, 커밋 이력 조회 소스 수정 : x
재현 조건
- README 의 직렬화 근거 문장과 손수 짠 클래스의 자바독을 나란히 읽는다.
- 빌드 파일에서 그 라이브러리를 끌어오는 선언을 찾는다.
- 같은 빌드 파일의 검증 태스크가 요구하는 좌표를 읽는다.
- 리프 main 에서 그 라이브러리를 쓰는 곳을 임포트와 인라인 표기까지 전수로 찾는다.
- 그중 봉투를 만드는 쪽의 본문을 읽고, 그 봉투와 어댑터를 자기 파일 밖에서 부르는 곳을 센다.
- 잠금 파일에서 관련 좌표를 확인한다.
- 그 좌표의 구성이 바뀐 이력을 커밋별로 뽑는다.
- README 를 건드린 커밋 전부를 나열하고, 구성을 바꾼 커밋이 README 에 무엇을 했는지 그 앞뒤 36 행으로 확인한다.
- 봉투 클래스가 들어온 커밋과 그 커밋이 이 리프에서 건드린 파일을 확인한다.
본문
봉투 직렬화를 손으로 짠 이유가 README 에 적혀 있고, 그 클래스의 자바독도 같은 말을 한다.
같은 주장을 하는 두 자리
:::evidence key="a12-f002-jackson-databind" alt="README 의 근거 문단과 손수 짠 클래스의 자바독이 같은 주장을 하는 것, 빌드 파일이 그 라이브러리를 끌어오는 선언과 같은 파일의 검증 태스크가 요구하는 좌표, 리프 main 에서 그 라이브러리를 쓰는 곳을 임포트와 인라인 표기까지 전수로 찾은 목록, 그중 봉투를 만드는 쪽의 본문, 그 봉투와 어댑터를 자기 파일 밖에서 부르는 곳의 수, 그리고 잠금 파일의 관련 좌표를 출력한 터미널 기록." caption="README 와 클래스 자바독이 같은 문장을 적는다 · 빌드 파일이 스프링 JSON 스타터와 스키마 검증기를 선언하고, 같은 파일의 검증 태스크는 그 좌표를 필수로 든다 · 리프 main 의 두 파일이 그 라이브러리를 쓰고 한쪽은 봉투를 만든다 · 그 봉투를 부르는 어댑터를 조립하는 곳은 0 — 44줄 · exit 0" zoom="true" :::
## OutboxEnvelopeJson — 손수 짠 JSON
이 모듈은 `jackson-databind` 를 classpath 에 두지 않아(스켈레톤을 가볍게 유지) outbox envelope
직렬화는 의존성 없는 손수 짠 JSON 이다.
/**
* Hand-rolled, dependency-free JSON serialiser for the outbox envelope (no Jackson — the module
* deliberately keeps {@code jackson-databind} off its classpath).
손수 짠 코드 자체는 서술대로다. 임포트가 도메인 타입 하나뿐이다. 고쳐야 할 것은 그 코드가 아니라 두 자리에 적힌 근거다.
빌드 파일이 그것을 끌어온다
8: implementation 'org.springframework.boot:spring-boot-starter-json'
9: implementation('com.networknt:json-schema-validator:3.0.2') {
잠금 파일이 스스로 그렇게 된 것이 아니다. 이 리프가 직접 선언한다. 그리고 같은 파일 아래쪽의 검증 태스크가 그 좌표를 요구한다.
'com.networknt:json-schema-validator:3.0.2',
'tools.jackson.core:jackson-core:3.0.2',
'tools.jackson.core:jackson-databind:3.0.2'
README 가 없다고 적은 모듈을 이 리프의 게이트가 필수로 든다.
문장이 무너진 자리
:::evidence key="a12-f002-jackson-databind-history" alt="이 좌표의 구성이 바뀐 이력을 커밋별로 뽑은 목록, README 를 건드린 커밋 전부, 구성을 바꾼 커밋이 README 에 절 셋을 붙였다는 것과 그 커밋 앞뒤의 36 행, 그리고 봉투 클래스가 들어온 커밋과 그 커밋이 이 리프에서 건드린 파일을 출력한 터미널 기록." caption="초기 커밋에서는 시험 클래스패스에만 있었고, 2026-07-31 커밋이 컴파일과 실행으로 올렸다 · 그 커밋이 README 에 절 셋을 붙였는데 36 행은 앞뒤가 같다 · 그 뒤 README 를 건드린 커밋은 없다 · 봉투 클래스는 석 주 뒤 커밋이 들여왔고 그 커밋의 잠금 변경은 판본뿐 — 29줄 · exit 0" zoom="true" :::
# 이 좌표의 구성이 바뀐 이력
a24ece9c 2026-08-28 feat: web, websocket 어댑터 추가 구현
-tools.jackson.core:jackson-databind:3.0.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
+tools.jackson.core:jackson-databind:3.1.5=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
5f10b791 2026-08-11 chore: record pre-existing uncommitted repository state
e5af2912 2026-07-31 feat: add messaging R2 polling producer
-tools.jackson.core:jackson-databind:3.0.2=testCompileClasspath,testRuntimeClasspath
+tools.jackson.core:jackson-databind:3.0.2=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
821fe00c 2026-07-24 init: 클린 아키텍처 백엔드
+tools.jackson.core:jackson-databind:3.0.2=testCompileClasspath,testRuntimeClasspath
구성을 바꾼 것은 2026-07-31 커밋이다. 8월 커밋이 한 일은 판본을 올린 것뿐이다.
문장의 classpath 를 배포되는 쪽으로 읽으면 초기 커밋 때는 성립했다. 이름공간을 두고는 같은 호의를 주지 않는다는 것이 아래의 문제다.
그 커밋이 README 에 한 일
# 구성을 바꾼 커밋이 README 에 한 일
README 를 건드렸는가 : 1
그 커밋이 README 에 새로 붙인 절 : 3 개
그 커밋 앞뒤의 36 행
before 이 모듈은 `jackson-databind` 를 classpath 에 두지 않아(스켈레톤을 가볍게 유지) outbox envelope
after 이 모듈은 `jackson-databind` 를 classpath 에 두지 않아(스켈레톤을 가볍게 유지) outbox envelope
같은 커밋이 이 README 를 열어 절 셋을 새로 붙였다. 방금 자기가 무너뜨린 그 한 줄만 그대로 두었다.
# README 를 건드린 커밋 전부
e5af2912 2026-07-31 feat: add messaging R2 polling producer
b3add016 2026-07-28 feat: redis, fileserver, httpclient 런타임 시점 구현 추가
821fe00c 2026-07-24 init: 클린 아키텍처 백엔드
그 뒤로 이 파일을 연 커밋은 없다.
같은 리프에서 그 라이브러리를 쓰는 곳
realtime/RealtimeFanoutEnvelopeJson.java:5:import tools.jackson.databind.ObjectMapper;
realtime/RealtimeFanoutEnvelopeJson.java:6:import tools.jackson.databind.json.JsonMapper;
realtime/RealtimeFanoutEnvelopeJson.java:7:import tools.jackson.databind.node.ObjectNode;
realtime/RealtimeFanoutEnvelopeJson.java:71: private static String text(tools.jackson.databind.JsonNode root, String field) {
envelope/LocalJsonSchemaRegistry.java:33:import tools.jackson.databind.DeserializationFeature;
envelope/LocalJsonSchemaRegistry.java:34:import tools.jackson.databind.JsonNode;
envelope/LocalJsonSchemaRegistry.java:35:import tools.jackson.databind.ObjectMapper;
envelope/LocalJsonSchemaRegistry.java:36:import tools.jackson.databind.json.JsonMapper;
README 가 근거로 든 것은 모듈 차원의 부재다. 그 부재가 깨진 자리에서 같은 리프의 옆 파일이 같은 종류의 일을 그 라이브러리로 한다.
24: private static final String VERSION = "1";
25: private static final ObjectMapper MAPPER = JsonMapper.builder().build();
30: public static String toJson(DurableFanoutRecord record) {
31: Objects.requireNonNull(record, "record must not be null");
32: ObjectNode root = MAPPER.createObjectNode();
봉투 JSON 을 조립하는 일이다.
다만 그 경로는 아직 돌지 않는다
RealtimeFanoutEnvelopeJson 을 자기 파일 밖에서 부르는 곳 : 1
MessagingDurableFanoutAdapter 를 자기 파일 밖에서 부르는 곳 : 0
부르는 하나는 팬아웃 어댑터이고, 그 어댑터를 조립하는 곳은 없다. 클래스패스 사실은 그대로이고, 도는 경로만 아직 없다.
이름공간을 한정하지 않는다
구세대 이름공간의 결속과 코어를 막는 것은 빌드 파일 검증 태스크의 단언이다. 구성 단위의 제외 목록은 형식 계열만 뺀다.
8:com.fasterxml.jackson.core:jackson-annotations:2.21=compileClasspath,runtimeClasspath,testCompileClasspath,testRuntimeClasspath
같은 이름공간의 애너테이션은 지금도 컴파일과 실행에 있다. 서술이 결속과 코어만 뜻했다면 맞지만, 문장에 이름공간이 없다.
확인하지 못한 것
실시간 팬아웃 봉투가 손수 짠 쪽과 같은 규율을 따라야 하는지는 판단하지 않았다. 여기서 확인한 것은 문장이 서술하는 사실 관계까지다.