Files
document-haness/docs/TechLog/tech-log-studio/seams-no-test-crosses/case/case-the-generator-dropped-four-contract-fields.md
T
DongHyeonkaandClaude Opus 5 53537e37e8 docs(TechLog): 주제 4·5 를 스킬대로 다시 쓴다
what-the-compiler-lets-through  중앙값 2,096 → 2,590 자
  seams-no-test-crosses                    → 3,091 자

bivariance 가 왜 느슨한 판정을 받는지, never 캐스트가 왜 아무것도 요구하지 않는지처럼
「이름을 댔으면 왜 있는지도 댄다」를 채웠다. 네 가지가 각각 무엇을 통과시키고 어디서
드러났는지를 표로 갈랐다. 검사 넷 중 무엇이 SQL 을 실제로 돌리는지도 표로 세웠다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 18:57:59 +09:00

5.5 KiB

kind, slug, title, topic, topicName, project, status, lastVerifiedOn, sourceRevision, source
kind slug title topic topicName project status lastVerifiedOn sourceRevision source
CASE the-generator-dropped-four-contract-fields 생성기가 계약 필드 넷을 조용히 빠뜨렸다 — 파생 스펙에 남은 YAML alias 34곳 seams-no-test-crosses 테스트가 지나지 않는 이음매 TechLog 게시 전 2026-09-04 tech-log@2026-09-02
final/document.md#§7.6

생성기가 계약 필드 넷을 조용히 빠뜨렸다 — 파생 스펙에 남은 YAML alias 34곳

계약에 있는 필드 넷이 생성된 모델에서 사라져 있었다. 파생 단계의 YAML alias 때문에 파서가 스키마 15개를 거절했고, 거절당한 스키마들은 전부 타입을 명시하고 있어 계약 결함처럼 보이지 않았다. 검증을 끄면 생성이 성공했고, 그렇게 만든 모델에 그 넷이 없었다. 컴파일은 통과한다 — 아직 아무도 그 필드를 안 쓰니까.

관계

  • 이음매마다 그 이음매를 실제로 지나는 검사를 하나씩 둔다 생성기가 그 목록의 한 줄이다.
  • 계약과 구현은 서버와 화면 양쪽에서 전수 대조한다 이 부류를 계약 쪽에서 막는 짝이 되는 기준이다.
  • 결정에는 상세 화면이 없어 목록 항목이 문서 전체를 실어야 했다 계약의 칸이 화면까지 오지 못한 다른 사건이다.

문제

이 건은 같은 갈래의 다른 것들과 결이 다르다. 테스트가 아니라 생성기가 값을 버렸다.

파생 스펙을 파서에 넣으면 스키마 15개를 「is not of type object」로 거절했다. 그 스키마들은 전부 type: object 를 명시하고 있어서 계약 결함처럼 보이지 않았다.

validateSpec 을 끄면 생성이 성공한다. 그렇게 만든 모델을 컴파일하면 통과한다.

결론

거절의 원인은 계약이 아니라 파생 단계였다. 변환들이 같은 Map 인스턴스를 여러 property 에 재사용했고 snakeyaml 이 그 지점을 anchor 와 alias 로 덤프했다. 파생 스펙에 alias 가 34곳 있었다.

검증을 끄고 만든 모델에서 사라진 필드 넷 : LatestEntry.publishedAt ProjectListItem.updatedAt SearchResultItem.matchedFields ReleaseListItem.changeTypes

컴파일이 통과한 이유는 아직 그 필드를 쓰는 코드가 없어서다.

덤프 직전 deep copy 로 노드 identity 를 끊어 alias 를 원천 차단하고, 남으면 빌드가 실패하도록 fail-closed 게이트를 뒀다. validateSpec 은 다시 켰다. 생성 모델 대조는 schema 이름에서 property 단위로 강화했다 — 이번 누락을 그 게이트가 통과시켰기 때문이다.

검증 환경

tech-log-backend : 365560e 파생 : snakeyaml 덤프 · swagger-parser 게이트 : verifyPublicGeneratedModels — schema 62개 · property 250개 확인 방식 : 파생 스펙에서 alias 를 세고, 생성된 모델의 property 를 계약과 대조

재현 조건

  1. 파생 스펙에서 anchor 와 alias 를 찾는다
  2. validateSpec 을 켜고 생성한다 — 그 스키마들이 거절된다
  3. 끄고 생성한 뒤 모델의 property 를 계약과 하나씩 맞춘다

본문

계약 결함처럼 보이지 않았다

파생 스펙을 파서에 넣으면 스키마 15개를 「is not of type object」로 거절했다. 그 스키마들은 전부 type: object 를 명시하고 있어서, 메시지가 가리키는 곳을 열어 봐도 고칠 것이 없었다.

원인은 그 스키마가 아니라 파생 스펙의 표현이었다. 변환들이 같은 Map 인스턴스를 여러 property 에 재사용했고, snakeyaml 은 같은 인스턴스가 두 번 나오면 두 번째를 anchor 와 alias 로 덤프한다. 파서가 alias 를 만나면 그 노드의 타입을 판정할 수 없으므로 거절한다.

파생 스펙에 alias 가 34곳 있었다.

검증을 끄면 생성이 성공한다

validateSpec 을 끄고 생성하면 모델이 만들어진다. 다만 거절되던 스키마의 일부 필드가 빠진 채로 만들어진다.

빠진 필드 어디 쓰는 칸인가
LatestEntry.publishedAt 홈 최근 기록의 게시일
ProjectListItem.updatedAt 프로젝트 목록의 갱신일
SearchResultItem.matchedFields 검색 결과에서 찾은 말 표시
ReleaseListItem.changeTypes 릴리스 목록의 변경 종류

컴파일은 통과한다. 아직 그 필드를 쓰는 코드가 없기 때문이다 — 넷 다 화면이 그리기로 되어 있지만 아직 안 그리고 있던 칸이라, 모델에 없어도 아무도 부르지 않는다.

두 가지로 막았다

덤프 직전에 deep copy 로 노드 identity 를 끊었다. 같은 인스턴스가 두 번 나오지 않으면 snakeyaml 이 alias 를 만들 이유가 없다.

그래도 남으면 빌드가 실패하도록 fail-closed 게이트를 뒀고, validateSpec 은 다시 켰다. 원인을 끊은 것과 남은 것을 잡는 것을 둘 다 둔 이유는, deep copy 를 지나지 않는 새 변환이 생기면 alias 가 다시 나올 수 있기 때문이다.

이름 대조에서 property 대조로

생성 모델 대조도 바꿨다. schema 이름만 세던 것을 property 단위로 강화했다.

이름 대조는 이번 누락을 통과시켰다. 스키마 자체는 만들어졌고 그 안의 필드만 빠졌기 때문이다. 지금은 schema 62개와 property 250개를 센다.

확인하지 못한 것

지금 세는 수는 사람이 갱신한다. 계약이 줄어드는 방향의 누락은 이 게이트가 잡지 않는다 — 수를 함께 줄이면 통과한다.