--- kind: CASE slug: the-generator-dropped-four-contract-fields title: 생성기가 계약 필드 넷을 조용히 빠뜨렸다 — 파생 스펙에 남은 YAML alias 34곳 topic: seams-no-test-crosses topicName: 테스트가 지나지 않는 이음매 project: TechLog status: 게시 전 lastVerifiedOn: 2026-09-04 sourceRevision: tech-log@2026-09-02 source: - 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개는 전부 `type: object` 를 명시하고 있었다. 메시지는 「is not of type `object`」였다. 원인은 그 스키마가 아니라 파생 스펙의 표현이었다. 변환들이 같은 `Map` 인스턴스를 여러 property 에 재사용했고, snakeyaml 은 같은 인스턴스가 두 번 나오면 두 번째를 alias 로 덤프한다. 그 지점이 34곳이었다. ## 검증을 끄면 생성이 성공한다 `validateSpec` 을 끄고 생성하면 모델이 만들어진다. 다만 거절되던 스키마의 일부 필드가 빠진 채로 만들어진다. 빠진 것은 넷이었다 — `LatestEntry.publishedAt`, `ProjectListItem.updatedAt`, `SearchResultItem.matchedFields`, `ReleaseListItem.changeTypes`. 컴파일은 통과한다. 아직 아무도 그 필드를 안 쓰기 때문이다. ## 두 가지로 막았다 덤프 직전에 deep copy 로 노드 identity 를 끊었다. 같은 인스턴스가 두 번 나오지 않으면 alias 가 생기지 않는다. 그래도 남으면 빌드가 실패하도록 fail-closed 게이트를 뒀고, `validateSpec` 은 다시 켰다. 생성 모델 대조도 바꿨다. schema 이름만 세던 것을 property 단위로 강화했다. 이번 누락을 이름 대조가 통과시켰기 때문이다. 지금은 schema 62개와 property 250개를 센다. ## 확인하지 못한 것 지금 세는 수는 사람이 갱신한다. 계약이 줄어드는 방향의 누락은 이 게이트가 잡지 않는다.