262 lines
20 KiB
Markdown
262 lines
20 KiB
Markdown
---
|
|
title: branch / feature-api-compatibility-deprecation-contract
|
|
source_type: branch-note
|
|
status: raw
|
|
branch: feature-api-compatibility-deprecation-contract
|
|
related_projects: [ca-skeleton]
|
|
tags: [branch, ca-skeleton, api-compatibility, deprecation]
|
|
created: 2026-05-22
|
|
target_merge:
|
|
status_label: in-progress
|
|
id: BR-CA-SKELETON-OPERATIONAL-CONTRACT-026
|
|
kind: project-work-item
|
|
project: ca-skeleton-operational-contract
|
|
work_item: WI-CA-SKELETON-OPERATIONAL-CONTRACT-026
|
|
inherits: [DEC-CA-SKELETON-OPERATIONAL-CONTRACT-API-VERSIONING-001@1]
|
|
refines: []
|
|
overrides: []
|
|
depends_on: []
|
|
contract_packet: 1
|
|
parent_branch:
|
|
contract_packet_sha256: b0999dd64e9b6815a5e42c1c75e34bdccd2eb746e6572aedb12a4ac6b1d21fd8
|
|
---
|
|
|
|
# branch: feature-api-compatibility-deprecation-contract
|
|
|
|
> Layer: `raw/branch-notes/` — API compatibility와 deprecation 기준을 정의합니다.
|
|
|
|
<!-- section-id: branch-parent -->
|
|
## 부모 (필수)
|
|
|
|
- **Parent project (canonical SSOT)**: [[raw/project-notes/ca-skeleton-operational-contract]]
|
|
|
|
> ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note 의 운영 계약 중 해당 영역 (§<관련 섹션>) 의 결정/근거/금지 사항을 정제한다.
|
|
|
|
## 묶음
|
|
|
|
<!-- GENERATED: sources:start -->
|
|
- [[raw/company-tech-blogs/api-versioning-github-rest-date-header]]
|
|
- [[raw/company-tech-blogs/api-versioning-stripe-date-based]]
|
|
- [[raw/official-docs/api-versioning-google-aip-180]]
|
|
- [[raw/official-docs/ci-openapi-snapshot-diff-tooling]]
|
|
- [[raw/official-docs/compat-rfc-8594-sunset-header]]
|
|
- [[raw/official-docs/google-aip-185-resource-versioning]]
|
|
- [[raw/official-docs/openapi-spec-3-1-0]]
|
|
- [[raw/official-docs/protobuf-reserved-vs-json-openapi-extension]]
|
|
- [[raw/official-docs/schema-protobuf-vs-json-evolution]]
|
|
- [[raw/official-docs/sunset-deprecation-headers-paired-usage]]
|
|
<!-- GENERATED: sources:end -->
|
|
|
|
<!-- GENERATED: blog-topics:start -->
|
|
- [[raw/blog-topics/api-deprecation-sunset-header-migration-window-2026-07-02]]
|
|
<!-- GENERATED: blog-topics:end -->
|
|
|
|
> 본 feature branch 는 leaf — 자식 자료 없음. Phase C2 실 코드 작성 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.
|
|
|
|
### 오류 기록 (본 feature 작업 중 발생)
|
|
|
|
- (없음 — 현재 documented-only 단계)
|
|
|
|
### 면접 준비 (이 작업에서 나올 수 있는 면접 질문)
|
|
|
|
- (없음 — Phase C2 실 구현 단계에 누적)
|
|
|
|
<!-- GENERATED: branch-contract:start -->
|
|
<!-- section-id: branch-contract-packet -->
|
|
## 브랜치 계약 패킷
|
|
|
|
- **생성 시 프로젝트 개정**: `1`
|
|
- **패킷 스키마**: `contract_packet: 1`
|
|
- **완료 조건**: /v1 compatibility·deprecation contract test가 통과한다
|
|
|
|
<!-- section-id: inherited-project-decisions -->
|
|
### 상속한 프로젝트 결정
|
|
|
|
| Decision Ref | Project Summary | Branch Application | Source |
|
|
|---|---|---|---|
|
|
| `DEC-CA-SKELETON-OPERATIONAL-CONTRACT-API-VERSIONING-001@1` | URI prefix /v1이 default이며 X-Api-Version은 compatibility 실험용 보조 header다 | Work Item 완료 조건에 적용 | [[raw/project-notes/ca-skeleton-operational-contract]] |
|
|
|
|
<!-- section-id: branch-local-decisions -->
|
|
### 브랜치 지역 결정
|
|
|
|
> 기존 branch-local 결정은 아래 `## Decision Evidence Map / 결정-근거 매핑`의 D-row가 소유하며 이 packet에서 복제하지 않는다.
|
|
|
|
| Decision ID | Decision | Relation | Supporting Claims | Status |
|
|
|---|---|---|---|---|
|
|
|
|
<!-- section-id: declared-overrides -->
|
|
### 선언한 예외
|
|
|
|
| Override ID | Overrides | Reason | Approval | Status |
|
|
|---|---|---|---|---|
|
|
<!-- GENERATED: branch-contract:end -->
|
|
|
|
<!-- section-id: branch-goal -->
|
|
## 목표
|
|
|
|
API versioning만으로는 장기 유지보수가 부족합니다. breaking change, response field removal, deprecated field, migration window 기준을 skeleton에 포함해야 합니다.
|
|
|
|
- 이슈:
|
|
- PR:
|
|
|
|
<!-- section-id: branch-scope -->
|
|
## 범위
|
|
|
|
### 포함 범위
|
|
|
|
- breaking change 정의.
|
|
- response field removal 금지 기준.
|
|
- deprecated field 정책.
|
|
- migration window 기준.
|
|
- backward compatibility test 기준.
|
|
- OpenAPI diff 기준.
|
|
|
|
### 제외 범위
|
|
|
|
- public API product lifecycle.
|
|
- external developer portal.
|
|
- multi-version runtime router 구현.
|
|
|
|
## TODO
|
|
|
|
> TODO drained 2026-05-22 — 결정은 아래 "결정 사항" / "Breaking Change Catalog" / "Decisionized Work Items" 참조. breaking change 정의/response field removal/deprecation marker/migration window/backward compat/OpenAPI diff 모두 catalog 또는 표 row로 반영됨. 잔존 TODO 없음.
|
|
|
|
## Work Item Contract
|
|
|
|
각 TODO는 아래 판정 단위로 재작성되어야 canonical 승급 가능합니다. TODO가 단순히 `기준 작성`으로 남아 있으면 이 branch는 완료로 보지 않습니다.
|
|
|
|
| field | required | rule |
|
|
| --- | --- | --- |
|
|
| Decision | yes | 구현자가 선택해야 하는 기본값 |
|
|
| Allowed | yes | 허용되는 예외와 조건 |
|
|
| Forbidden | yes | 절대 금지되는 구현/문서 상태 |
|
|
| Required registry update | conditional | error/env/header/log/metric/capability 변경 시 필수 |
|
|
| Required contract test | yes | 계약 위반 시 실패해야 하는 테스트 |
|
|
| Failure condition | yes | review/build에서 실패로 판정할 상태 |
|
|
| Canonical extraction target | yes | `wiki/projects` 승급 위치 |
|
|
|
|
## 진행 중 메모
|
|
|
|
- 이 branch는 API contract baseline과 schema serialization contract를 보완합니다.
|
|
|
|
## 결정 사항 (decisions)
|
|
|
|
- 2026-05-22: compatibility/deprecation은 API versioning과 별도 기준으로 관리.
|
|
- 2026-05-22: breaking change catalog는 이 branch가 소유하고 OpenAPI diff 집행은 `feature-contract-verification-test-suite`가 수행.
|
|
- 2026-05-22: migration window 기본값은 90일. internal-only API는 30일로 줄일 수 있으나 branch note에 근거와 소비자 목록이 필요.
|
|
- 2026-05-22: published response field removal은 deprecated marker + migration window + compatibility fixture 없이는 금지.
|
|
- 2026-05-22: API deprecation 응답은 `Sunset: <date>` + `Deprecation: <date>` 헤더 **함께** 전송. 단독 Sunset 금지. 추가로 `Link: <url>; rel="sunset"` 권장.
|
|
|
|
## 근거 (필수, 최소 1개+)
|
|
|
|
> 본 branch의 결정 근거. 상세 비교는 §외부 근거 / 대안 조사 (있다면) 참조.
|
|
|
|
| Source | 정당화하는 결정 |
|
|
|---|---|
|
|
| [[raw/official-docs/compat-rfc-8594-sunset-header]] | IETF RFC 8594 Sunset header 표준 (ca-tmpl 채택 근거 |
|
|
| [[raw/company-tech-blogs/api-versioning-stripe-date-based]] | account pin + freeze; 외부 컨슈머 규모 큰 경우 우위 |
|
|
| [[raw/company-tech-blogs/api-versioning-github-rest-date-header]] | long EOL window + explicit 410 응답 |
|
|
| [[raw/official-docs/api-versioning-google-aip-180]] | enum value 제거도 금지; ca-tmpl `narrow enum = breaking` 결정과 부분 정합 |
|
|
| [[raw/official-docs/sunset-deprecation-headers-paired-usage]] | 참조 |
|
|
| [[raw/official-docs/openapi-spec-3-1-0]] | OpenAPI Specification v3.1 (JSON Schema 2020-12 alignment) — OAS 의 normative scope/structure 근거. ⚠️ Operation Object 의 `deprecated: boolean` 필드 자체는 OPENAPI31-C1~C7 발췌에 포함되지 않음 (raw 자체 Usage Boundary 명시) — D8 deprecation marker 의 OpenAPI spec normative 인용은 별도 raw 발췌 필요 |
|
|
|
|
## 외부 근거 / 대안 조사 (2026-05-22 — Group G-F: API Compatibility / Deprecation)
|
|
|
|
본 branch의 90d public + 30d internal migration window + breaking change catalog 7행 + Sunset header + OpenAPI `deprecated:true` 결정에 대한 외부 source.
|
|
|
|
- **채택 결정 (window + Sunset header + OpenAPI deprecation)**:
|
|
- [[raw/official-docs/compat-rfc-8594-sunset-header]] — IETF RFC 8594 Sunset header 표준 (ca-tmpl 채택 근거)
|
|
- **검토한 대안**:
|
|
- **대안 1: Stripe date-based versioning (no removal, freeze forever)** — [[raw/company-tech-blogs/api-versioning-stripe-date-based]] (account pin + freeze; 외부 컨슈머 규모 큰 경우 우위)
|
|
- **대안 2: GitHub X-GitHub-Api-Version header + 24mo EOL + 410 Gone** — [[raw/company-tech-blogs/api-versioning-github-rest-date-header]] (long EOL window + explicit 410 응답)
|
|
- **대안 3: Google AIP-180 backward compat 분류** — [[raw/official-docs/api-versioning-google-aip-180]] (enum value 제거도 금지; ca-tmpl `narrow enum = breaking` 결정과 부분 정합)
|
|
- **비교 핵심**: Stripe(freeze forever) vs ca-tmpl(90d/30d window) vs GitHub(24mo EOL + 410): 외부 컨슈머 규모와 운영 비용 trade-off. ca-tmpl internal-first면 90d 합리. **보강 후보 2가지**: (a) EOL 응답 코드(410 Gone)가 ca-tmpl catalog에 누락 — GitHub 사례 차용 검토, (b) Sunset(RFC 8594) + Deprecation 헤더는 **함께** 보내야 정합 — ca-tmpl 결정은 marker만 명시.
|
|
|
|
**후속 보강 (2026-05-22)**: Sunset 헤더는 Deprecation 헤더와 paired로 보내야 함. [[raw/official-docs/sunset-deprecation-headers-paired-usage]] 참조.
|
|
|
|
## Breaking Change Catalog
|
|
|
|
| change | classification | default action |
|
|
| --- | --- | --- |
|
|
| remove response field | breaking | deprecate first, remove after migration window |
|
|
| rename response field | breaking | add new field, keep old deprecated field through window |
|
|
| change field type/format | breaking | new version or additive field |
|
|
| narrow enum values | breaking | new version |
|
|
| add required request field | breaking | new version or default server-side |
|
|
| add optional response field | additive | allowed with schema update |
|
|
| change error code/category | breaking for clients | foundation registry change + migration note |
|
|
|
|
## Decisionized Work Items
|
|
|
|
| item | Decision | Allowed | Forbidden | Required test | Failure condition |
|
|
| --- | --- | --- | --- | --- | --- |
|
|
| migration window | 90 days public/default, 30 days internal-only | shorter only with owner approval | immediate field removal | compatibility fixture | deprecated field removed early |
|
|
| deprecation marker | OpenAPI `deprecated: true` + branch note | response header optional | undocumented deprecation | OpenAPI diff | deprecated field lacks marker |
|
|
| breaking diff | verification suite release-blocking | warning-only only for additive diff | breaking diff warning-only | openapi-diff gate | breaking diff passes CI |
|
|
|
|
## 테스트 계약
|
|
|
|
- published response field가 사전 deprecation 없이 제거되면 실패.
|
|
- OpenAPI diff에서 breaking change가 감지되면 실패.
|
|
- deprecated field가 migration window 없이 제거되면 실패.
|
|
- backward compatibility fixture가 깨지면 실패.
|
|
|
|
## 결정-근거 매핑
|
|
|
|
> 각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시. company-tech-blog 출처는 `company-case-study` 로 라벨링하며 공식 best practice 로 격상하지 않는다.
|
|
|
|
| Decision ID | Decision | Supporting Claims | Evidence Strength | Open Risk |
|
|
|---|---|---|---|---|
|
|
| D1 | compatibility / deprecation 은 API versioning 과 별도 기준으로 관리 (2026-05-22) | UNSUPPORTED_DECISION (project-internal scoping decision; 외부 표준이 두 영역의 분리를 normative 로 강제하지 않음) | N/A | scoping 결정의 정합성은 sibling branch (`feature-api-contract-baseline`) 와 cross-review 필요 |
|
|
| D2 | breaking change catalog 7행 분류 — `remove response field`, `rename`, `change type/format`, `narrow enum values`, `add required request field`, `add optional response field`, `change error code` | `raw/official-docs/api-versioning-google-aip-180.md#AIP180-C1` (component 제거 금지), `#AIP180-C2` (rename = remove+add), `#AIP180-C3` (default behavior preservation 으로 additive 분류), `#AIP180-C4` (required field 추가 금지), `#AIP180-C5` (minor/patch client breaking 금지), `raw/company-tech-blogs/api-versioning-github-rest-date-header.md#GH-APIV-C4` (GitHub 의 동일 7행 breaking 분류 사례) | `official-vendor-doc + company-case-study` | AIP-180 은 Google internal API design guideline — IETF/W3C 표준 아님 (외부 인용 시 "Google AIP" 명시 필수). GitHub 사례는 company-case-study — 7행 분류가 모든 API 의 표준이라는 일반화 금지 |
|
|
| D3 | OpenAPI diff release-blocking 집행은 `feature-contract-verification-test-suite` 가 수행 (이 branch 는 catalog 소유, 집행 위임) | UNSUPPORTED_DECISION (project-internal SSOT 분할 결정; 외부 표준 근거 없음) | N/A | catalog owner 와 enforcement owner 분리 시 drift 위험 — verification suite 의 입력 catalog 정합성 추적 필요 |
|
|
| D4 | migration window 기본값 90일 (public) / 30일 (internal-only) | UNSUPPORTED_DECISION (cited `AIP180-C1` 은 same major version 안에서 "must not be removed" — ca-tmpl 의 window 후 제거 정책과 다름. cited `GH-APIV-C7` 의 24개월 EOL 도 90/30일과 직접 일치하지 않음. cited `STRIPE-APIV-C4` 는 "as long as possible" 철학으로 window 자체를 권고하지 않음) | N/A | window 길이의 정당성은 internal-first skeleton 의 운영 부담 trade-off — 외부 표준 인용 불가. canonical 승급 시 design rationale 별도 문서화 필요 |
|
|
| D5 | published response field removal 은 deprecated marker + migration window + compatibility fixture 없이 금지 | `raw/official-docs/api-versioning-google-aip-180.md#AIP180-C1` (component 제거 금지), `#AIP180-C2` (rename = remove+add), `#AIP180-C5` (minor/patch client breaking 금지), `raw/company-tech-blogs/api-versioning-github-rest-date-header.md#GH-APIV-C4` (response field 제거가 breaking) | `official-vendor-doc + company-case-study` | AIP-180 은 same major version 안에서 사실상 영구 금지 — ca-tmpl 의 "migration window 후 제거 허용" 정책은 AIP 보다 약함 (외부 인용 시 정합성 caveat 필요) |
|
|
| D6 | API deprecation 응답은 `Sunset: <date>` + `Deprecation: <date>` 헤더 **함께** 전송; 단독 Sunset 금지; 추가로 `Link: <url>; rel="sunset"` 권장 | `raw/official-docs/sunset-deprecation-headers-paired-usage.md#SD-PAIR-C1` (Sunset = decommissioning 시점), `#SD-PAIR-C3` (Deprecation = 상태 신호), `#SD-PAIR-C5` (Sunset MUST NOT be earlier than Deprecation), `#SD-PAIR-C6` (sunset / deprecation link relation 용도), `raw/official-docs/compat-rfc-8594-sunset-header.md#RFC8594-C1` (Sunset 정의), `#RFC8594-C4` (sunset link relation IANA 등록) | `official-standard` | IETF httpapi WG 의 권고 — client tooling 의 실제 paired 감지 여부는 vendor 별 (예: Spring HATEOAS, Apigee). 단독 송신을 안 하면 client 가 deprecation 감지 못 한다는 절대 사실은 spec 에 없음 (해석) |
|
|
| D7 | breaking diff CI gate 가 release-blocking; additive diff 만 warning-only 허용 | `raw/official-docs/api-versioning-google-aip-180.md#AIP180-C3` (additive 의 default behavior 보존 시 호환), `#AIP180-C5` (minor/patch breaking 금지), `raw/company-tech-blogs/api-versioning-github-rest-date-header.md#GH-APIV-C5` (breaking 은 새 버전 release + 사전 공지) | `official-vendor-doc + company-case-study` | "release-blocking" 자동 enforcement 메커니즘 자체는 AIP-180 / GitHub 모두 정책만 명시 — CI gate 강제는 ca-tmpl 의 운영적 보강 |
|
|
| D8 | deprecation marker 는 OpenAPI `deprecated: true` + branch note; response header optional | `raw/official-docs/sunset-deprecation-headers-paired-usage.md#SD-PAIR-C3` (Deprecation 헤더 정의), `#SD-PAIR-C6` (link relation), `raw/official-docs/compat-rfc-8594-sunset-header.md#RFC8594-C1` (Sunset 정의), `raw/official-docs/openapi-spec-3-1-0.md#OPENAPI31-C1` (OAS normative keyword 해석 BCP 14), `#OPENAPI31-C2` (OAS = HTTP API contract scope), `#OPENAPI31-C7` (Schema Object = JSON Schema 2020-12 superset — `deprecated` 가 OAS-specific extension 으로 언급되나 본 raw 발췌에 직접 인용 없음) — marker (OpenAPI) 와 응답 헤더의 paired 송신은 D6 에서 강제 | `official-standard + official-vendor-doc` (partial — OpenAPI scope/normative-keyword 까지만) | ⚠️ **OpenAPI Operation Object 의 `deprecated: boolean` 필드 자체의 normative 정의는 openapi-spec-3-1-0 raw 의 OPENAPI31-C1~C7 발췌에 포함되지 않음** (raw 자체 §"Usage Boundaries 이 자료가 증명하지 않는 것" 명시: "`deprecated: true` 의 정확한 의미론 — 본 발췌에 직접 인용 없음"). §4.8.10 Operation Object 의 `deprecated` 필드 별도 발췌 또는 §4.8.24 Schema Object 의 `deprecated` keyword 별도 발췌가 필요한 follow-up. 현재 OPENAPI31-* 는 OAS 의 scope/normative-keyword/JSON-Schema-alignment 만 corroborate — deprecation marker 의미론은 여전히 직접 표준 인용 부재 |
|
|
|
|
## 검증해야 할 주장
|
|
|
|
> 공식 문서/사례는 근거지만 내 프로젝트에서의 동작을 자동 보장하지 않는다. 구현 전/중/후 실제로 검증해야 하는 주장.
|
|
|
|
| Claim | Why uncertain | How to verify | Status |
|
|
|---|---|---|---|
|
|
| Sunset + Deprecation 헤더가 paired 로 송신되며 `Sunset >= Deprecation` invariant 가 강제되는지 (`SD-PAIR-C5` 준수) | header middleware 구현 위치 (Spring filter / interceptor / `@ControllerAdvice`) 에 따라 invariant 누락 가능 | header invariant CI gate 추가 + integration test (deprecated endpoint 응답에 두 헤더 존재 + Sunset >= Deprecation 검증) | `planned` |
|
|
| OpenAPI `deprecated: true` 마커와 응답 헤더의 동기화가 보장되는지 | marker 추가만 하고 헤더 누락 또는 그 반대 가능성 | OpenAPI snapshot grep + 실제 응답 contract test cross-check | `planned` |
|
|
| 90d (public) / 30d (internal) migration window 가 release process 에 실제로 강제되는지 | window 정책이 process 문서에만 있고 CI / release gate 에 강제 메커니즘 없을 위험 | release calendar / CI gate 가 deprecation marker 추가 시각 + sunset date 차이를 검증하는지 dry-run | `needs-confirmation` |
|
|
| breaking diff CI gate 가 `release-blocking` 으로 실제 동작하는지 (`AIP180-C5` invariant 강제) | gate 가 warning-only 로 misconfigured 가능 | breaking diff 의도적 도입 후 CI build fail 검증 | `planned` |
|
|
| 7행 catalog 의 모든 row 가 OpenAPI diff tool 의 분류와 1:1 mapping 되는지 | tool (openapi-diff / oasdiff) 의 자체 분류와 catalog 의 분류가 다를 위험 | tool dry-run 결과 + catalog mapping 표 작성 | `planned` |
|
|
| EOL 응답 코드 (`410 Gone`, GH-APIV-C6) 가 ca-tmpl catalog 에 누락된 점 — sunset 이후 응답 정책 결정 필요 | GitHub 사례 차용 검토 필요 항목으로 본문 명시 — 결정 미정 | catalog 보강 결정 + sunset 시점 이후 응답 contract test 작성 | `needs-confirmation` |
|
|
| OpenAPI `deprecated: true` (Operation Object / Schema Object) 의 normative 정의를 표준 인용으로 확보 | `openapi-spec-3-1-0` raw 의 OPENAPI31-C1~C7 발췌에 `deprecated` boolean 필드 인용 누락 — D8 의 marker 정책이 외부 표준 직접 인용 없이 운영. raw 자체 Usage Boundary 가 "본 발췌에 직접 인용 없음" 명시 | OpenAPI 3.1 §4.8.10 Operation Object + §4.8.24 Schema Object 의 `deprecated` 필드 발췌를 별도 raw 또는 기존 raw 보강으로 확보 → DEM D8 의 Evidence Strength 를 partial → official-standard 로 승급 | `needs-confirmation` |
|
|
|
|
## 마주친 문제
|
|
|
|
- 아직 없음.
|
|
|
|
## 구현 가이드
|
|
|
|
- version·deprecation·sunset 값은 API registry가 소유하고 controller는 registry를 참조한다.
|
|
- additive fixture와 breaking fixture를 분리하며, 제거는 deprecation window와 소비자 확인 뒤에만 허용한다.
|
|
- OpenAPI diff가 breaking change를 검출하면 CI가 실패하고 승인 기록 없이는 우회하지 않는다.
|
|
|
|
## 엣지·실패·의존
|
|
|
|
- 필드 삭제·타입 변경·enum 축소는 기존 소비자를 깨뜨리므로 명시적 migration 경로가 필요하다.
|
|
- 본 계약은 API versioning·OpenAPI registry·contract verification Work Item에 의존한다.
|
|
|
|
## 관련 일일 노트
|
|
|
|
- 별도 일일 노트 없음.
|
|
|
|
## 완료 후 정리
|
|
|
|
- PR 링크:
|
|
- 리뷰 메모:
|
|
- 머지 결과 / 배포 환경:
|
|
- **wiki 추출 대상** (verified만, `wiki/projects/`로만 추출):
|
|
- `actually-implemented` 항목:
|
|
- `locally-verified` 항목:
|
|
- `prod-verified` 항목:
|
|
- **추출하지 않을 항목** (planned / documented-only / abandoned):
|