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 6feee5ba57 docs(TechLog): 자료에 남아 있던 사람의 흔적을 제자리에 놓는다
writing-as-the-person-who-did-it 을 서브에이전트 셋으로 나눠 56편에 적용했다.
56편 중 20편만 고쳤다 — 나머지 36편은 SSOT 를 절 단위로 대조했을 때 옮길 흔적이
이미 옮겨져 있었거나 없었다. 없는 목소리를 채우지 않는다.

옮긴 것은 전부 SSOT 의 어느 절에서 왔는지 댈 수 있다.

  §3.4  「눈으로 찾을 일이 아니었다」— 세 계약을 파싱해 뽑은 이유
  §4.3  구현하지 않기로 한 것과 빠뜨린 것은 다르다
  §8.5  표의 마지막 줄을 더할 때 이 목록을 또 빠뜨렸다
  §9.4  「왜 주제 링크가 탐색으로 가지?」— 우회를 남겨 두면 계속 나온 질문
  §11.3 「세 버튼」을 실제 이름으로 되돌리고 두 언어가 섞인 것을 그 자리에
  §12.2 막지 않은 대신 메모리에 남긴 것
  §13.1 여섯 벌 인용이 어느 커밋이 짚은 말인지
  §13.3 고쳐 쓴 첫 안이 거절당한 것과 사용자가 고른 말 두 쌍
  §14.3 「문서가 그대로 나온다」는 구조 차이가 아니라 내용 양의 차이라는 정정
  §16.1 삭제가 막힌 실제 기록 이름과 그것을 막은 프로젝트 링크
  §16.9 주제 논지와 축 결론이 AI 가 써서 DB 에 직접 넣은 미검토 초안이라는 것
  §17.5 바운딩 박스로 잘못 지목한 대상이 「판단 기준」이었다는 것

검증 환경의 커밋 해시 하나가 틀려 있었다(ca1cfa2 → ca1fc92). SSOT §13.1 과
부록 A 가 적은 값이고 저장소에 그 커밋이 있다.

검사 넷 전부 통과한다 — check_prose 56편 error 0 · check_body PASS ·
check_evidence --repo 문제 없음 · verify-tech-log-tree 프로젝트 5 error 0 warn 0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 16:31:48 +09:00

4.7 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개는 전부 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개를 센다.

확인하지 못한 것

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