Files
llm-wiki/harness/state/semantic-certificates/028d796257593f29fa5962255a0bde4c75db268ac9085bf4739e432ab8b2e50f/d22275b5740a519599d3e92d67a9a460cdf35b7fcb4801e15dd1a65edb594b56.json
T

2 lines
147 KiB
JSON

{"audit_request":{"assertions":[{"assertion_id":"A01","condition":"패킷 생성 시점","line_end":43,"line_start":43,"modality":"observed","object":"생성 시 프로젝트 개정 = 1","predicate":"other","quote":"- **생성 시 프로젝트 개정**: `1`","scope":"브랜치 계약 패킷","source_surface":"SURF-5EBA6C6F9A4E27E1CD71","subject":"브랜치 계약 패킷"},{"assertion_id":"A02","condition":"unconditional","line_end":44,"line_start":44,"modality":"observed","object":"contract_packet 스키마 버전 1","predicate":"has_schema","quote":"- **패킷 스키마**: `contract_packet: 1`","scope":"브랜치 계약 패킷","source_surface":"SURF-5EBA6C6F9A4E27E1CD71","subject":"브랜치 계약 패킷"},{"assertion_id":"A03","condition":"완료 조건","line_end":45,"line_start":45,"modality":"must","object":"/v1 API와 envelope/OpenAPI contract test 통과","predicate":"requires","quote":"- **완료 조건**: /v1 API와 envelope/OpenAPI contract test가 통과한다","scope":"브랜치 계약 패킷","source_surface":"SURF-5EBA6C6F9A4E27E1CD71","subject":"브랜치 완료"},{"assertion_id":"A04","condition":"상속한 프로젝트 결정 API-VERSIONING-001@1","line_end":52,"line_start":52,"modality":"must","object":"URI prefix /v1 default, X-Api-Version 은 compatibility 보조 header","predicate":"uses","quote":"| `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]] |","scope":"API versioning","source_surface":"SURF-5EBA6C6F9A4E27E1CD71","subject":"API versioning"},{"assertion_id":"A05","condition":"상속한 프로젝트 결정 OPENAPI-001@1","line_end":53,"line_start":53,"modality":"must","object":"OpenAPI drift release-blocking 판정권","predicate":"owns","quote":"| `DEC-CA-SKELETON-OPERATIONAL-CONTRACT-OPENAPI-001@1` | verification suite가 OpenAPI drift의 release-blocking 판정권을 소유한다 | Work Item 완료 조건에 적용 | [[raw/project-notes/ca-skeleton-operational-contract]] |","scope":"OpenAPI drift release gate","source_surface":"SURF-5EBA6C6F9A4E27E1CD71","subject":"verification suite"},{"assertion_id":"A06","condition":"branch-local decisions","line_end":58,"line_start":58,"modality":"must","object":"branch-local 결정 (packet 에서 복제 금지)","predicate":"owns","quote":"> 기존 branch-local 결정은 아래 `## Decision Evidence Map / 결정-근거 매핑`의 D-row가 소유하며 이 packet에서 복제하지 않는다.","scope":"branch-local decisions","source_surface":"SURF-5EBA6C6F9A4E27E1CD71","subject":"Decision Evidence Map D-row"},{"assertion_id":"A07","condition":"검증 대상, status planned","line_end":452,"line_start":452,"modality":"unknown","object":"Idempotency-Key 헤더 노출 (OpenAPI snapshot 기준)","predicate":"requires","quote":"| `Idempotency-Key` 헤더가 모든 POST endpoint 에 실제로 노출되는지 (OpenAPI snapshot 기준) | header 채택 결정은 design 단계, OpenAPI snapshot 에 반영됐는지 별도 확인 필요 | `openapi.yaml` snapshot grep 또는 contract test 로 모든 POST operation 에 `Idempotency-Key` parameter 존재 검증 | `planned` |","scope":"OpenAPI snapshot","source_surface":"SURF-5979C98452D45B17D6A9","subject":"POST endpoint"},{"assertion_id":"A08","condition":"검증 대상, status planned","line_end":453,"line_start":453,"modality":"unknown","object":"/v1 URI prefix 적용","predicate":"requires","quote":"| `/v1` URI prefix 가 모든 public endpoint 에 적용되는지 | versioning 결정과 실제 controller mapping 의 drift 가능성 | ArchUnit / Spring controller mapping inspector 로 `RequestMapping` prefix 검증 | `planned` |","scope":"public endpoint versioning","source_surface":"SURF-5979C98452D45B17D6A9","subject":"public endpoint"},{"assertion_id":"A09","condition":"검증 대상 D15, status planned","line_end":464,"line_start":464,"modality":"unknown","object":"412 Precondition Failed","predicate":"returns","quote":"| `If-Match` mismatch 가 412 Precondition Failed 로 응답하는지 (D15) | optimistic lock 충돌을 409 Conflict 또는 500 으로 매핑할 위험 | sample-portfolio UPDATE 에 `If-Match: W/\"0\"` (stale version) 전송 → 412 + envelope `error.code=CONFLICT` 또는 별도 `PRECONDITION_FAILED` | `planned` |","scope":"conditional request","source_surface":"SURF-5979C98452D45B17D6A9","subject":"If-Match mismatch"},{"assertion_id":"A10","condition":"검증 대상 D18, status planned","line_end":470,"line_start":470,"modality":"unknown","object":"size cap 강제 여부","predicate":"has_threshold","quote":"| pagination `size` cap 이 강제되는지 (D18, 최대 footgun) | Spring `Pageable` default max = `DEFAULT_MAX_PAGE_SIZE = 2000` (SPRING-PAGE-C4 정정 — 이전 표현 `Integer.MAX_VALUE` 는 부정확). 2000 도 DoS 위험은 충분 — `?size=2000` × 무거운 응답 = 메모리 폭발 | `?size=10000000` → 400 VALIDATION_FAILED + envelope `error.details.field=size` + `error.details.code=SIZE_EXCEEDS_MAX` · `?size=500` (Spring default 2000 이하지만 project cap 100 초과) → 400 VALIDATION_FAILED | `planned` |","scope":"pagination","source_surface":"SURF-5979C98452D45B17D6A9","subject":"pagination size 파라미터"},{"assertion_id":"A11","condition":"검증 대상 D11, status needs-confirmation","line_end":473,"line_start":473,"modality":"unknown","object":"controller 응답 HTTP status","predicate":"maps_to","quote":"| `error-codes.yaml` 의 모든 row 가 실제 controller 응답의 HTTP status 와 일치하는지 (D11, registry drift detection) | registry row 와 controller drift 가 untracked 위험 | 모든 `error.code` row 에 대해 contract test 가 trigger (오류 발생 fixture) 후 실제 응답 status code 가 `http_status` column 과 일치 검증 | `needs-confirmation` (registry-governance branch 와 정합성 확인) |","scope":"status mapping","source_surface":"SURF-5979C98452D45B17D6A9","subject":"error-codes.yaml 의 모든 row"},{"assertion_id":"A12","condition":"검증 대상 D22, status planned","line_end":477,"line_start":477,"modality":"unknown","object":"opaque + signed + 24h TTL 3-invariant","predicate":"has_schema","quote":"| cursor token 이 opaque (client parse 불가) + signed (tamper 감지) + TTL (24h 후 만료) 3개 invariant 모두 만족하는지 (D22) | typed cursor 노출 / unsigned token / no TTL 중 하나라도 깨지면 contract 위반 | cursor token roundtrip test (next page 정상) + base64 decode 후 client 가 의미 있는 정보 추출 못함 검증 + tamper test (token 1byte 변조 → 400) + TTL test (24h 1초 후 token → 400) | `planned` |","scope":"cursor pagination","source_surface":"SURF-5979C98452D45B17D6A9","subject":"cursor token"},{"assertion_id":"A13","condition":"검증 대상 D23, status needs-confirmation","line_end":478,"line_start":478,"modality":"unknown","object":"atomic (async bulk = LRO polling)","predicate":"has_failure_behavior","quote":"| sync bulk endpoint 가 atomic 인지 + async bulk endpoint 가 LRO polling pattern 인지 (D23 sync/async 분기) | sync 에서 partial failure 허용은 AIP233-C7 위반. controller 작성자가 sync/async 구분 없이 partial failure 응답하거나 flat array body 채택 가능성 | sync batch contract test: `POST /v1/worklogs:batchCreate` 의 한 항목 fail 시 전체 rollback + HTTP 4xx + envelope.success=false. async batch contract test: `POST /v1/operations:batchCreate` 의 202 + Location + polling endpoint 의 `data.result.results[]` 가 항목별 success/error 매핑. BATCH_PARTIAL_FAILURE 가 sync 응답에 나타나면 실패. 1001 항목 size cap 위반 → 400 | `needs-confirmation` (boundary branch B14 BulkEnvelope.partial + D17 LRO endpoint pattern cross-link) |","scope":"bulk operation","source_surface":"SURF-5979C98452D45B17D6A9","subject":"sync bulk endpoint"},{"assertion_id":"A14","condition":"검증 대상 D17, status needs-confirmation","line_end":468,"line_start":468,"modality":"unknown","object":"202 + Location + envelope data.{operationId,statusUrl}","predicate":"returns","quote":"| LRO endpoint 가 202 + `Location` + envelope `data.{operationId,statusUrl}` 형식인지 (D17) | 현재 ca-skeleton 에 LRO endpoint 자체가 없음 — sample-portfolio fixture 신설 필요 | sample-portfolio 에 `POST /v1/worklogs:export` 같은 LRO fixture 추가 후 응답 검증 | `needs-confirmation` (sample-portfolio fixture 확장 필요) |","scope":"long-running operation","source_surface":"SURF-5979C98452D45B17D6A9","subject":"LRO endpoint"},{"assertion_id":"A15","condition":"D2","line_end":264,"line_start":264,"modality":"must","object":"/v1 URI prefix 기본값 + X-Api-Version supplemental","predicate":"uses","quote":"| D2 | API versioning 기본값 `/v1` URI prefix + `X-Api-Version` 은 supplemental | `raw/official-docs/google-aip-185-resource-versioning.md#AIP185-C1` (major version 노출 의무), `#AIP185-C2` (minor/patch 노출 금지 — `/v1.0` 금지 → `/v1`), `#AIP185-C3` (새 major 는 이전 major 에 의존 금지) | `official-reference` (Google AIP — Google 사내 community guideline; IETF/W3C 표준 아님) | AIP-185 본문은 protobuf 컨텍스트 — REST URI path vs header 선택 자체에 대한 normative 진술은 본 인용 범위 밖. `X-Api-Version` 을 supplemental 로 두는 결정의 직접 근거는 별도 (예: ca-tmpl internal design) — AIP-185 는 path 형식 (`/v1`) 만 corroborate. **UNSUPPORTED_IMPL_DECISION**: path version 과 `X-Api-Version` 충돌 시 *path 우선* 규칙은 project-internal convention — AIP-185 에 path/header 우선순위 normative 진술 없음. trade-off: URI 가 1급 계약 표면(캐시·라우팅·로그에서 가시)이므로 path 를 권위 source 로, header 는 실험/전환 보조로 둠 |","scope":"API versioning","source_surface":"SURF-65CDA45EC8402D6A3130","subject":"API versioning"},{"assertion_id":"A16","condition":"D3 (POST endpoint 표준 surface)","line_end":265,"line_start":265,"modality":"must","object":"idempotency header 이름 Idempotency-Key","predicate":"owns","quote":"| D3 | idempotency header 이름 `Idempotency-Key` 채택 (POST endpoint 표준 surface) | `raw/official-docs/idempotency-stripe-api-ref.md#STRIPE-IDEMP-C1`, `raw/official-docs/idempotency-ietf-draft.md#IETF-IDEMP-C1`, `raw/company-tech-blogs/idempotency-toss-payments-techblog.md#TOSS-IDEMP-C1` | `official-vendor-doc + official-reference + company-case-study` | IETF-IDEMP 는 draft 상태 (정식 RFC 아님); TOSS 는 company-case-study — 표준 lock-in 아님. PayPal 은 다른 header (`PayPal-Request-Id`) 사용 — 호환성은 별도 |","scope":"idempotency header name","source_surface":"SURF-65CDA45EC8402D6A3130","subject":"이 branch"},{"assertion_id":"A17","condition":"D4","line_end":266,"line_start":266,"modality":"must","object":"key scope / replay semantics SSOT 를 feature-rate-limit-idempotency-contract 로 위임","predicate":"delegates","quote":"| D4 | key scope / replay semantics SSOT 는 `feature-rate-limit-idempotency-contract` 로 위임 (이 branch 는 header 이름만 결정) | UNSUPPORTED_DECISION (project-internal SSOT 분할 결정; 외부 표준 근거 없음) | N/A | sibling branch 와의 결정 정합성은 cross-branch review 로 확보 필요 |","scope":"idempotency key semantics","source_surface":"SURF-65CDA45EC8402D6A3130","subject":"이 branch"},{"assertion_id":"A18","condition":"D5","line_end":267,"line_start":267,"modality":"must","object":"OpenAPI drift release-blocking 집행 (이 branch 는 producer)","predicate":"owns","quote":"| D5 | OpenAPI drift release-blocking 집행은 `feature-contract-verification-test-suite` 가 owner; 이 branch 는 producer | UNSUPPORTED_DECISION (project-internal owner 분할 결정) | N/A | producer-owner contract 가 깨지면 drift 가 untracked — verification suite 의 입력 spec 정합성 추적 필요 |","scope":"OpenAPI drift release gate","source_surface":"SURF-65CDA45EC8402D6A3130","subject":"feature-contract-verification-test-suite"},{"assertion_id":"A19","condition":"D11","line_end":273,"line_start":273,"modality":"must","object":"HTTP status code ↔ envelope error.code 매핑 SSOT (이 branch 는 consistency test producer)","predicate":"owns","quote":"| D11 | HTTP status code ↔ envelope `error.category`/`error.code` 매핑 SSOT 는 `error-codes.yaml` (foundation branch 소유 registry §21 row 49 + `http_status` column). 본 branch 는 mapping consistency contract test 의 producer | UNSUPPORTED_DECISION (project-internal SSOT 위치 결정; 외부 표준 직접 근거 없음 — RFC 9110 §15.x 가 *개별 status code* 의미를 정의할 뿐 *registry 형식의 SSOT* 자체는 표준 영역 밖); 보강: 개별 row 의 매핑 (예: 404 ↔ `RESOURCE_NOT_FOUND`) 의 normative 근거는 RFC 9110 §15.x 각 status section — 본 branch 의 contract test 가 *registry 와 응답의 drift* 만 검증, *registry 자체의 row 정합성* 은 foundation branch 책임 | N/A | mapping SSOT 가 registry 인 것 자체는 project-internal 결정. RFC 9110 §15.5.6 (405), §15.5.13 (412), §15.5.15 (414) 같은 다른 status code section 의 raw 발췌가 *다음 세션* 작업 — 이후 각 row 의 Supporting Claim ID 보강 가능. cross-branch: registry row 의 *추가/수정* 은 §21 변경 절차 (registry-governance branch) 적용 |","scope":"status mapping SSOT","source_surface":"SURF-65CDA45EC8402D6A3130","subject":"error-codes.yaml (foundation branch)"},{"assertion_id":"A20","condition":"D14","line_end":276,"line_start":276,"modality":"must","object":"default media type application/json (merge-patch+json 미채택)","predicate":"uses","quote":"| D14 | PATCH default media type = `application/json` (RFC 7396 `application/merge-patch+json` *미채택*). request shape = `JsonNullable<T>` / `Optional<T>` wrapper 로 **absent / null / value 3-상태** 구분 · ArchUnit rule SSOT 는 [[raw/branch-notes/feature-boundary-validation-mapping-contract]] B2 | `raw/official-docs/patch-json-merge-rfc7396.md#RFC7396-C3` (merge patch 는 explicit null 사용 모델에 부적합 — *미채택의 직접 근거*), `#RFC7396-C2` (대안: null=deletion normative — 본 branch *대안으로* 인용); [[raw/branch-notes/feature-boundary-validation-mapping-contract]] B2 (PATCH mapper SSOT, ArchUnit `no_merge_patch_json_media_type_string` enforced) | `official-standard` (RFC 7396 — 미채택 근거) + `cross-branch-SSOT` (boundary branch B2) | content type 정책은 본 branch 가 producer, mapper 구현은 boundary branch 책임. 클라이언트가 merge-patch semantics 가정하지 않도록 OpenAPI spec 에 명시. |","scope":"PATCH media type","source_surface":"SURF-65CDA45EC8402D6A3130","subject":"PATCH"},{"assertion_id":"A21","condition":"D18","line_end":286,"line_start":286,"modality":"must","object":"size default 20 / max 100 / min 1; 위반 시 400 VALIDATION_FAILED","predicate":"has_threshold","quote":"| D18 | Pagination — `page` 0-indexed (Spring `Pageable` 정합), `size` default 20 / max 100 / min 1 · `size > 100` 또는 `size < 1` 또는 `page < 0` 은 400 VALIDATION_FAILED · 빈 list 는 `data: []` (절대 `null` 아님) + `meta.page.total = 0` · 깊은 offset (`page > 10000`) 은 `Deprecation` 헤더 + cursor 권고 | `raw/official-docs/spring-data-pageable-defaults.md#SPRING-PAGE-C1` (`page` 파라미터 0-indexed, default 0), `#SPRING-PAGE-C2` (`size` 파라미터 default 20), `#SPRING-PAGE-C3` (Spring Data 레포지토리 infrastructure 의 `Pageable` 은 zero-indexed), `#SPRING-PAGE-C4` (`PageableHandlerMethodArgumentResolverSupport.DEFAULT_MAX_PAGE_SIZE = 2000` — Spring 기본 max 가 2000 이므로 project 의 100 cap 은 별도 opt-in override 임을 정당화), `#SPRING-PAGE-C5` (annotation 없는 fallback = `PageRequest.of(0, 20)`), `#SPRING-PAGE-C6` (`setOneIndexedParameters` default false → page 0 = first page). 추가 normative 근거: `raw/official-docs/google-aip-158-pagination.md#AIP158-C1` (collection pagination 처음부터 제공 필수, 나중 추가 = backwards-incompatible), `#AIP158-C2` (page_size server-side cap SHOULD coerce down; max 숫자는 server-defined — 100 은 project-internal), `#AIP158-C3` (page_token MUST NOT required; subsequent page_size 변경 MUST honor), `#AIP158-C4` (next_page_token empty = EoC MUST, 유일한 EoC 시그널), `#AIP158-C5` (page token opaque + URL-safe MUST; base64 단독 불충분). JSON:API JSONAPI-PAGE-C1~C6 모두 size cap 또는 index base 의 normative 진술 없음 — JSONAPI-PAGE-C6 가 pagination strategy 에 agnostic 임을 명시. `size` max 100 cap 및 min 1 / `page < 0` 400 처리는 project-internal DoS prevention trade-off (UNSUPPORTED_IMPL_DECISION 잔존 — Spring 기본값 이하 추가 제한) | `official-vendor-doc` (Spring Data Commons — SPRING-PAGE-C1~C6) + `official-reference` (AIP-158 — AIP158-C1~C5) + UNSUPPORTED_IMPL_DECISION (`size` 100 cap / min 1 / 깊은 offset threshold 10000 은 project-internal 숫자; size>100 을 AIP-158 권고 coerce 대신 400-reject 강화도 project-internal) | 가장 critical footgun 결정 — `size=10000000` DoS 방지 + 0/1-indexed Spring 정합. Spring default max 는 2000 이지 Integer.MAX_VALUE 가 아님 (C4 보정). 깊은 offset 의 cursor 권고는 본 branch 가 박지만 cursor endpoint 의 shape (opaque token encoding/TTL) 결정은 future B16 — sibling 또는 후속 작업 |","scope":"pagination","source_surface":"SURF-65CDA45EC8402D6A3130","subject":"pagination size/page 파라미터"},{"assertion_id":"A22","condition":"D22","line_end":284,"line_start":284,"modality":"must","object":"opaque base64 JSON + HMAC signature + 24h TTL","predicate":"has_schema","quote":"| D22 | Cursor pagination shape = opaque base64-encoded JSON token + HMAC signature + 24h TTL · cursor endpoint 는 별도 query param 또는 별도 path · client 의 token parse 금지 (opacity 강제) · cursor + `?page=N` 동시 사용 금지 | `raw/official-docs/google-aip-158-pagination.md#AIP158-C3` (page_token MUST NOT required + subsequent page_size 변경 MUST honor), `#AIP158-C5` (page token opaque + URL-safe MUST; base64 단독 불충분 → HMAC signature 추가 정당화) | `official-reference` (Google AIP-158 — opacity/URL-safe MUST normative) + UNSUPPORTED_IMPL_DECISION (24h TTL + HMAC algorithm 선택 + JSON shape 자체는 project-internal trade-off) | TTL 24h 의 정확한 숫자는 AIP-158 에 없음 (project-internal — 짧으면 long-running export 깨짐, 길면 DB layout 변경 시 stale cursor 문제). HMAC key rotation 정책은 별도 결정 (security branch 와 cross-link 필요). cursor endpoint URL 패턴 (`/v1/worklogs:listByCursor` colon-verb vs `?cursor=` query param) 미정 — 후속 D23 colon-verb 채택과 정합성 위해 colon-verb 권고 가능 (future revision) |","scope":"cursor pagination","source_surface":"SURF-65CDA45EC8402D6A3130","subject":"cursor pagination token"},{"assertion_id":"A23","condition":"실패·엣지 경로 D8","line_end":423,"line_start":423,"modality":"must_not","object":"raw 500 (envelope 따름)","predicate":"forbids","quote":" - **oversized request (413) / URI 길이 초과 (414)** — raw 500 금지, envelope 따름. gateway pre-reject 시에만 envelope 우회 + log correlation 필수. (D8 / D8 형제 — RFC9110-C5/C6/C19)","scope":"request size / URI length 실패 분류","source_surface":"SURF-748DD1139201E3F044E6","subject":"413/414 응답"},{"assertion_id":"A24","condition":"실패·엣지 경로 D15","line_end":427,"line_start":427,"modality":"must","object":"412 (409/500 매핑 금지)","predicate":"returns","quote":" - **conditional request** — `If-Match` mismatch 를 409/500 으로 매핑하면 실패(→ 412), `If-None-Match` match 304 에 body 동봉하면 실패. (D15)","scope":"conditional request","source_surface":"SURF-748DD1139201E3F044E6","subject":"If-Match mismatch"},{"assertion_id":"A25","condition":"실패·엣지 경로 D18 SPRING-PAGE-C4","line_end":428,"line_start":428,"modality":"must","object":"project cap 100 (Spring default max 2000 별도 opt-in override)","predicate":"has_threshold","quote":" - **pagination footgun** — `size > 100` / `size < 1` / `page < 0` 통과하거나 빈 list 가 `data: null` 이면 실패(`data: []` + `meta.page.total=0`). Spring default max 가 2000(≠Integer.MAX_VALUE)이라 project cap 100 은 별도 opt-in override. (D18 — SPRING-PAGE-C4)","scope":"pagination","source_surface":"SURF-748DD1139201E3F044E6","subject":"pagination size cap"},{"assertion_id":"A26","condition":"실패·엣지 경로 D23 AIP233-C7","line_end":432,"line_start":432,"modality":"must","object":"atomic (partial failure / BATCH_PARTIAL_FAILURE 가 sync 응답에 쓰이면 실패)","predicate":"has_failure_behavior","quote":" - **bulk** — sync batch 가 atomic 아니거나 partial failure 를 sync 응답에 반환, 또는 `BATCH_PARTIAL_FAILURE` 가 sync 응답에 쓰이면 실패. (D23 — AIP233-C7)","scope":"bulk operation","source_surface":"SURF-748DD1139201E3F044E6","subject":"sync batch"},{"assertion_id":"A27","condition":"다른 계약 의존 (foundation branch)","line_end":435,"line_start":435,"modality":"must","object":"error-codes.yaml (D11) + envelope schema; registry http_status column 이 status 매핑 SSOT","predicate":"consumes","quote":" - [[raw/branch-notes/feature-operational-error-observability-foundation]] 의 `error-codes.yaml`(D11) + envelope schema 에 의존 — registry `http_status` column 이 status 매핑 SSOT. **(엣지) `error-codes.yaml` 미존재 또는 row 누락 시 D11 mapping consistency contract test 동작**: registry 가 존재하는데 row 가 빠진 경우 = test FAIL(drift). registry 자체가 아직 생성 전인 현재 documented-only 단계에서는 D11 test 가 `planned`(비활성) — **registry 생성이 D11 test 활성화의 선행 조건**이며, registry 부재를 SKIP(통과)으로 처리하면 안 됨.","scope":"status mapping 의존","source_surface":"SURF-748DD1139201E3F044E6","subject":"이 branch"},{"assertion_id":"A28","condition":"다른 계약 의존 D3/D4","line_end":437,"line_start":437,"modality":"must","object":"Idempotency-Key key shape/scope/replay semantics owner = rate-limit branch (본 branch 는 header 이름만)","predicate":"delegates","quote":" - [[raw/branch-notes/feature-rate-limit-idempotency-contract]] 에 의존(D3/D4) — `Idempotency-Key` key shape/scope/replay semantics owner. 본 branch 는 header *이름* 만.","scope":"idempotency","source_surface":"SURF-748DD1139201E3F044E6","subject":"이 branch"},{"assertion_id":"A29","condition":"다른 계약 의존 D5/D10","line_end":442,"line_start":442,"modality":"must","object":"OpenAPI snapshot (release-gate owner = verification suite)","predicate":"produces","quote":" - [[raw/branch-notes/feature-contract-verification-test-suite]] 에 의존 — OpenAPI drift release-gate(D5/D10, 본 branch 는 producer).","scope":"OpenAPI drift release gate","source_surface":"SURF-748DD1139201E3F044E6","subject":"이 branch"},{"assertion_id":"A30","condition":"D2 Decisionized Work Item","line_end":328,"line_start":328,"modality":"must","object":"/v1 URI prefix (X-Api-Version supplemental; media-type/header/path 혼용 금지)","predicate":"uses","quote":"| versioning | `/v1` URI prefix | `X-Api-Version` supplemental header | media-type/header/path version 혼용 | OpenAPI path version test | version 없는 public endpoint |","scope":"API versioning","source_surface":"SURF-1E8C87AB41FD1B886F5F","subject":"API versioning"},{"assertion_id":"A31","condition":"D14 method-PATCH","line_end":335,"line_start":335,"modality":"must","object":"default media type application/json + JsonNullable/Optional wrapper 3-상태 구분","predicate":"uses","quote":"| method-PATCH | PATCH default media type = `application/json` · request shape = `JsonNullable<T>` / `Optional<T>` wrapper · absent/null/value 3-상태 구분 (boundary branch B2 SSOT) | 없음 — `application/merge-patch+json` (RFC 7396) / `application/json-patch+json` (RFC 6902) 모두 **금지** | `application/merge-patch+json` / `application/json-patch+json` content type 사용 · absent vs null 미구분 (`null` 이 absent 와 동일 의미로 처리됨) | `no_merge_patch_json_media_type_string` ArchUnit rule (boundary branch B2) + PATCH absent/null 3-상태 mapper contract test | PATCH endpoint 가 `application/merge-patch+json` content type 허용 · Java record canonical constructor 가 absent 와 null 을 같은 기본값으로 수렴 |","scope":"PATCH media type","source_surface":"SURF-1E8C87AB41FD1B886F5F","subject":"PATCH"},{"assertion_id":"A32","condition":"D11 HTTP status mapping SSOT","line_end":340,"line_start":340,"modality":"must","object":"모든 HTTP status 매핑의 SSOT","predicate":"owns","quote":"| HTTP status mapping SSOT | `error-codes.yaml` 의 `http_status` column 이 모든 매핑의 SSOT (registry §21, owner: foundation branch) | 본 branch 는 mapping consistency contract test 의 producer | controller 가 registry 와 다른 HTTP status 반환 | status mapping consistency test (모든 `error.code` row 에 대해 실제 응답의 HTTP status 가 registry 와 일치) | registry-controller drift |","scope":"status mapping SSOT","source_surface":"SURF-1E8C87AB41FD1B886F5F","subject":"error-codes.yaml http_status column (foundation branch)"},{"assertion_id":"A33","condition":"D11 HTTP status mapping SSOT","line_end":340,"line_start":340,"modality":"must","object":"mapping consistency contract test","predicate":"produces","quote":"| HTTP status mapping SSOT | `error-codes.yaml` 의 `http_status` column 이 모든 매핑의 SSOT (registry §21, owner: foundation branch) | 본 branch 는 mapping consistency contract test 의 producer | controller 가 registry 와 다른 HTTP status 반환 | status mapping consistency test (모든 `error.code` row 에 대해 실제 응답의 HTTP status 가 registry 와 일치) | registry-controller drift |","scope":"status mapping","source_surface":"SURF-1E8C87AB41FD1B886F5F","subject":"이 branch"},{"assertion_id":"A34","condition":"D22 cursor pagination shape","line_end":345,"line_start":345,"modality":"must","object":"opaque base64 JSON + HMAC signature + 24h TTL","predicate":"has_schema","quote":"| cursor pagination shape (D22) | opaque base64-encoded JSON token + HMAC signature + 24h TTL · cursor endpoint 는 별도 query param (`?cursor=<token>&size=20`) 또는 별도 path | size cap (D18) 동일 적용 · token 만료 시 400 VALIDATION_FAILED + 권장: 첫 페이지 재요청 | typed cursor (last value 노출) · unsigned token (tamper risk) · cursor + `?page=N` 동시 사용 · TTL 무한 | cursor token roundtrip test + tamper detection test + TTL expiration test + opacity 검증 (client parse 가능하면 실패) | typed/unsigned/no-TTL token |","scope":"cursor pagination","source_surface":"SURF-1E8C87AB41FD1B886F5F","subject":"cursor pagination token"},{"assertion_id":"A35","condition":"D23 bulk operation URL","line_end":346,"line_start":346,"modality":"must","object":"atomic MUST (한 항목 실패 시 전체 rollback + HTTP 4xx + envelope.success=false)","predicate":"has_failure_behavior","quote":"| bulk operation URL (D23) | AIP-136 colon-verb `POST /v1/{resource}:batchCreate` · request body `{ requests: [...] }` · **sync batch** = atomic MUST (한 항목 실패 → 전체 rollback + HTTP 4xx + envelope.success=false) · **async batch** = 202 + Location → polling endpoint 의 `data.result.results[]` 에 항목별 success/error (BATCH_PARTIAL_FAILURE 는 async polling 에서만) | async batch 의 polling endpoint 가 D17 LRO pattern 따름 + BATCH_PARTIAL_FAILURE category 적용 | sync batch 에서 partial failure 허용 (AIP233-C7 위반) · flat array body · kebab subpath · sync batch 의 atomic rollback 누락 | bulk endpoint contract test (sync: atomic rollback 검증 · async: 202+polling+partial result 검증) | sync batch 가 partial success 응답 · BATCH_PARTIAL_FAILURE 가 sync 응답에 사용됨 |","scope":"bulk operation","source_surface":"SURF-1E8C87AB41FD1B886F5F","subject":"sync batch"},{"assertion_id":"A36","condition":"D3 Cross-branch role","line_end":392,"line_start":392,"modality":"must","object":"Idempotency-Key header 이름 (header name only)","predicate":"owns","quote":"| `Idempotency-Key` header *이름* (D3) | **owner** (header name 만) | **owner** of key shape/scope/replay: `feature-rate-limit-idempotency-contract` | 본 branch → rate-limit branch (header name produces, key shape consumes) |","scope":"idempotency header name","source_surface":"SURF-1E8C87AB41FD1B886F5F","subject":"이 branch"},{"assertion_id":"A37","condition":"D11 Cross-branch role","line_end":390,"line_start":390,"modality":"must","object":"HTTP status ↔ envelope error.code 매핑 registry (본 branch 는 consistency test producer)","predicate":"owns","quote":"| HTTP status ↔ envelope `error.code` 매핑 (D11) | **producer** of mapping consistency contract test | **owner** of registry: `feature-operational-error-observability-foundation` (`error-codes.yaml`) | 본 branch ← foundation branch |","scope":"status mapping SSOT","source_surface":"SURF-1E8C87AB41FD1B886F5F","subject":"feature-operational-error-observability-foundation"},{"assertion_id":"A38","condition":"D10 Cross-branch role","line_end":394,"line_start":394,"modality":"must","object":"OpenAPI snapshot (drift gate owner = feature-contract-verification-test-suite)","predicate":"produces","quote":"| OpenAPI snapshot 생성 (D10) | **producer** | **owner** of drift gate: `feature-contract-verification-test-suite` | 본 branch → verification suite |","scope":"OpenAPI drift release gate","source_surface":"SURF-1E8C87AB41FD1B886F5F","subject":"이 branch"},{"assertion_id":"A39","condition":"D15 Cross-branch role","line_end":397,"line_start":397,"modality":"must","object":"Conditional request (ETag/If-Match/412/304) HTTP layer","predicate":"owns","quote":"| Conditional request (`ETag`/`If-Match`/412/304) (D15) | **owner** (HTTP layer) | **consumer**: sample-portfolio fixture (`WorkLogVersion` 이 ETag derivation 의 source) | 본 branch → sample-portfolio fixture |","scope":"conditional request","source_surface":"SURF-1E8C87AB41FD1B886F5F","subject":"이 branch"},{"assertion_id":"A40","condition":"D14 Cross-branch role","line_end":396,"line_start":396,"modality":"must","object":"PATCH semantics owner = boundary branch B2 (본 branch 는 content type application/json only producer)","predicate":"delegates","quote":"| PATCH semantics (D14 정정 후) | **consumer** (content type 정책만 producer — `application/json` only) | **owner**: `feature-boundary-validation-mapping-contract` B2 (RFC 7396 미채택 + absent/null/value 3-상태 mapper + ArchUnit `no_merge_patch_json_media_type_string` enforced) | 본 branch ← boundary branch SSOT |","scope":"PATCH semantics","source_surface":"SURF-1E8C87AB41FD1B886F5F","subject":"이 branch"},{"assertion_id":"A41","condition":"포함 범위","line_end":83,"line_start":83,"modality":"must","object":"API versioning 기준","predicate":"owns","quote":"- API versioning 기준.","scope":"branch scope","source_surface":"SURF-91D6B695FA1EC10C42A1","subject":"이 branch"},{"assertion_id":"A42","condition":"포함 범위","line_end":85,"line_start":85,"modality":"must","object":"idempotency header 표준 (header 이름만; key shape/scope SSOT 는 sibling)","predicate":"owns","quote":"- idempotency header 표준 (header 이름만 — key shape/scope SSOT 는 sibling).","scope":"idempotency header name","source_surface":"SURF-91D6B695FA1EC10C42A1","subject":"이 branch"},{"assertion_id":"A43","condition":"포함 범위","line_end":95,"line_start":95,"modality":"must","object":"HTTP status code ↔ envelope error.code 전체 매핑 SSOT 위치 결정","predicate":"owns","quote":"- HTTP status code ↔ envelope `error.category`/`error.code` 의 전체 매핑 SSOT 위치 결정.","scope":"status mapping SSOT","source_surface":"SURF-91D6B695FA1EC10C42A1","subject":"이 branch"},{"assertion_id":"A44","condition":"제외 범위","line_end":112,"line_start":112,"modality":"must","object":"CORS allowlist / credentials / preflight policy 를 security branch 로 위임","predicate":"delegates","quote":"- CORS allowlist / credentials / preflight policy — **owner**: [[raw/branch-notes/feature-security-operational-baseline]] D9. 본 branch 는 OPTIONS 응답이 envelope 우회한다는 점만 cross-cite.","scope":"CORS","source_surface":"SURF-91D6B695FA1EC10C42A1","subject":"이 branch"},{"assertion_id":"A45","condition":"제외 범위","line_end":120,"line_start":120,"modality":"must","object":"response field naming case 를 schema-serialization branch 로 위임","predicate":"delegates","quote":"- response field naming case (camelCase vs snake_case) — **owner**: [[raw/branch-notes/feature-schema-serialization-contract]]. 본 branch 는 envelope `meta.*` 가 camelCase 라는 cross-cite 만.","scope":"field naming case","source_surface":"SURF-91D6B695FA1EC10C42A1","subject":"이 branch"},{"assertion_id":"A46","condition":"제외 범위","line_end":121,"line_start":121,"modality":"must","object":"URL 구조와 {id} placeholder 연결만 (resource ID format 은 범위 밖)","predicate":"owns","quote":"- resource ID format 자체는 본 branch 범위 밖이며 [[raw/branch-notes/feature-resource-identifier-contract]] D1/D19가 ULID를 소유한다. 본 branch는 URL 구조와 `{id}` placeholder 연결만 소유한다.","scope":"resource URL structure","source_surface":"SURF-91D6B695FA1EC10C42A1","subject":"이 branch"}],"candidate_manifest_sha256":"6b5acf806254518e854a83ba61b6bb4f877411d5fb063f3747fd42716416aeca","candidates":[{"assertion_a":"A04","assertion_b":"A05","candidate_id":"SEM-CBDE4F76A1CFA54CE6DA","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A07","assertion_b":"A08","candidate_id":"SEM-30FB86B3707B7F6197EB","grouping_key":{"condition":"검증 대상, status planned","predicate":"requires","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A07","assertion_b":"A09","candidate_id":"SEM-B1917B6B4DF1E0458BE1","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A07","assertion_b":"A10","candidate_id":"SEM-53BFDA8495CC9CCF1085","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A07","assertion_b":"A12","candidate_id":"SEM-18BC1C16BF8F36D03675","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A07","assertion_b":"A16","candidate_id":"SEM-DF646A99A6705B4DA6E0","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A07","assertion_b":"A27","candidate_id":"SEM-A607497D134EE8457082","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A07","assertion_b":"A28","candidate_id":"SEM-BB074E310C1B5B8E6436","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A07","assertion_b":"A36","candidate_id":"SEM-942FC1582FAA81D058A5","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A08","assertion_b":"A09","candidate_id":"SEM-AA592D614F3D40327A28","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A08","assertion_b":"A10","candidate_id":"SEM-3AF87FC3906856813907","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A08","assertion_b":"A12","candidate_id":"SEM-ABA2C9E08A2ADF777CF2","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A08","assertion_b":"A15","candidate_id":"SEM-1D64524A2099A24F8C5C","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A08","assertion_b":"A27","candidate_id":"SEM-817A81A7FD395F2F3798","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A08","assertion_b":"A30","candidate_id":"SEM-DE0A13B6115913398A09","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A09","assertion_b":"A10","candidate_id":"SEM-D608EDA1C37381DFB9FA","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A09","assertion_b":"A12","candidate_id":"SEM-8E21C5E658B125AD4A95","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A09","assertion_b":"A24","candidate_id":"SEM-F681A21173CCFADFA917","grouping_key":{"condition":"","predicate":"returns","scope":"conditional request","subject":"If-Match mismatch"},"rule_ids":["C5","C6"]},{"assertion_a":"A09","assertion_b":"A27","candidate_id":"SEM-1EABE3ECB52507238C4E","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A09","assertion_b":"A39","candidate_id":"SEM-6A8BE3D703C0A6710EA6","grouping_key":{"condition":"","predicate":"","scope":"conditional request","subject":""},"rule_ids":["C6"]},{"assertion_a":"A10","assertion_b":"A12","candidate_id":"SEM-7212CEF2ACD31B9B3BEF","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A10","assertion_b":"A21","candidate_id":"SEM-82FD610D3ED6432D327F","grouping_key":{"condition":"","predicate":"has_threshold","scope":"pagination","subject":""},"rule_ids":["C6"]},{"assertion_a":"A10","assertion_b":"A27","candidate_id":"SEM-206934F17F21136944A3","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A11","assertion_b":"A13","candidate_id":"SEM-D1091C5F4DABA55917EB","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A11","assertion_b":"A14","candidate_id":"SEM-2A37156B147650C3BA92","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A11","assertion_b":"A19","candidate_id":"SEM-F7745D43DFE77807A768","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A11","assertion_b":"A27","candidate_id":"SEM-F1E8B1ED1522145C92D8","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A11","assertion_b":"A32","candidate_id":"SEM-3D265F054686DCA98C9D","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A11","assertion_b":"A33","candidate_id":"SEM-0515C24E3AC94C29FAFD","grouping_key":{"condition":"","predicate":"","scope":"status mapping","subject":""},"rule_ids":["C6"]},{"assertion_a":"A11","assertion_b":"A37","candidate_id":"SEM-C7EE073573BAD47B036E","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A11","assertion_b":"A43","candidate_id":"SEM-14734583F94C38099ED9","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A12","assertion_b":"A27","candidate_id":"SEM-2A441C1EDA439D54F53C","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A13","assertion_b":"A14","candidate_id":"SEM-C8D5490FCD117127064D","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A13","assertion_b":"A35","candidate_id":"SEM-7FA7AC0DB5CFEF782F7C","grouping_key":{"condition":"","predicate":"has_failure_behavior","scope":"bulk operation","subject":""},"rule_ids":["C6"]},{"assertion_a":"A15","assertion_b":"A21","candidate_id":"SEM-791BDC8E8B1537E99FD2","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A15","assertion_b":"A22","candidate_id":"SEM-1C42238FF1EE3811DE1C","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A15","assertion_b":"A30","candidate_id":"SEM-0C58BB5841DB01109839","grouping_key":{"condition":"","predicate":"uses","scope":"API versioning","subject":"API versioning"},"rule_ids":["C6"]},{"assertion_a":"A16","assertion_b":"A28","candidate_id":"SEM-89145C09640A0FEC16CD","grouping_key":{"condition":"","predicate":"","scope":"","subject":"이 branch"},"rule_ids":["C6"]},{"assertion_a":"A16","assertion_b":"A36","candidate_id":"SEM-AAA94801A40CE9D0E637","grouping_key":{"condition":"","predicate":"owns","scope":"idempotency header name","subject":"이 branch"},"rule_ids":["C6"]},{"assertion_a":"A17","assertion_b":"A18","candidate_id":"SEM-4E89119752E43CB78BE5","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A17","assertion_b":"A19","candidate_id":"SEM-E8B5BBE048CC97D78EB3","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A17","assertion_b":"A36","candidate_id":"SEM-C965B2736081EFA65DF5","grouping_key":{"condition":"","predicate":"","scope":"","subject":"이 branch"},"rule_ids":["C6"]},{"assertion_a":"A18","assertion_b":"A19","candidate_id":"SEM-22FE36C61D9DF359A806","grouping_key":{"condition":"","predicate":"owns","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A18","assertion_b":"A38","candidate_id":"SEM-0DF4FFB3D2BF42CF8F4E","grouping_key":{"condition":"","predicate":"","scope":"OpenAPI drift release gate","subject":""},"rule_ids":["C6"]},{"assertion_a":"A19","assertion_b":"A27","candidate_id":"SEM-77325803A5B73AB9B882","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A19","assertion_b":"A32","candidate_id":"SEM-AD1B9C6B74D4B73D580B","grouping_key":{"condition":"","predicate":"owns","scope":"status mapping SSOT","subject":""},"rule_ids":["C6"]},{"assertion_a":"A19","assertion_b":"A33","candidate_id":"SEM-85ACDFC548AC4BB59C1B","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A19","assertion_b":"A37","candidate_id":"SEM-5B2B1A1AD31A4E96DE4C","grouping_key":{"condition":"","predicate":"owns","scope":"status mapping SSOT","subject":""},"rule_ids":["C6"]},{"assertion_a":"A19","assertion_b":"A43","candidate_id":"SEM-425D7957F4556C50A21F","grouping_key":{"condition":"","predicate":"owns","scope":"status mapping SSOT","subject":""},"rule_ids":["C6"]},{"assertion_a":"A20","assertion_b":"A31","candidate_id":"SEM-C8CCC41AD0D772A7E1AA","grouping_key":{"condition":"","predicate":"uses","scope":"PATCH media type","subject":"PATCH"},"rule_ids":["C6"]},{"assertion_a":"A20","assertion_b":"A40","candidate_id":"SEM-357032D3F517EAA603B3","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A21","assertion_b":"A22","candidate_id":"SEM-AC3FA36B63468B61C261","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A21","assertion_b":"A25","candidate_id":"SEM-DF6B320EF94D40586D78","grouping_key":{"condition":"","predicate":"has_threshold","scope":"pagination","subject":""},"rule_ids":["C6"]},{"assertion_a":"A21","assertion_b":"A31","candidate_id":"SEM-0C34F9C6000C3EC0B6CA","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A22","assertion_b":"A34","candidate_id":"SEM-C6313EA367AEBF9D9B3C","grouping_key":{"condition":"","predicate":"has_schema","scope":"cursor pagination","subject":"cursor pagination token"},"rule_ids":["C6"]},{"assertion_a":"A24","assertion_b":"A39","candidate_id":"SEM-327C1ACF7545EEFFEFD4","grouping_key":{"condition":"","predicate":"","scope":"conditional request","subject":""},"rule_ids":["C6"]},{"assertion_a":"A27","assertion_b":"A32","candidate_id":"SEM-53347448D33D5199A94A","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A27","assertion_b":"A33","candidate_id":"SEM-2EC6B008A7FF4A0C4805","grouping_key":{"condition":"","predicate":"","scope":"","subject":"이 branch"},"rule_ids":["C6"]},{"assertion_a":"A27","assertion_b":"A37","candidate_id":"SEM-8E8E609534335097EFCA","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A28","assertion_b":"A36","candidate_id":"SEM-A6CB3B6140C00F0A30E1","grouping_key":{"condition":"","predicate":"","scope":"","subject":"이 branch"},"rule_ids":["C6"]},{"assertion_a":"A31","assertion_b":"A40","candidate_id":"SEM-09A4573242B3E87C3AEF","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A32","assertion_b":"A33","candidate_id":"SEM-101DD9241094F9A87C16","grouping_key":{"condition":"D11 HTTP status mapping SSOT","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A32","assertion_b":"A37","candidate_id":"SEM-7F9A7E2AB7D1E0B063C2","grouping_key":{"condition":"","predicate":"owns","scope":"status mapping SSOT","subject":""},"rule_ids":["C6"]},{"assertion_a":"A32","assertion_b":"A43","candidate_id":"SEM-DCD9C7D858F39FFF59D2","grouping_key":{"condition":"","predicate":"owns","scope":"status mapping SSOT","subject":""},"rule_ids":["C6"]},{"assertion_a":"A33","assertion_b":"A37","candidate_id":"SEM-85DF12EABCC38DD6BE97","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"A33","assertion_b":"A43","candidate_id":"SEM-AF17EC6501CB7658B2B8","grouping_key":{"condition":"","predicate":"","scope":"","subject":"이 branch"},"rule_ids":["C6"]},{"assertion_a":"A37","assertion_b":"A43","candidate_id":"SEM-C1E74114512201530D0A","grouping_key":{"condition":"","predicate":"owns","scope":"status mapping SSOT","subject":""},"rule_ids":["C6"]}],"coverage":{"assertions":46,"candidate_pairs":67,"eligible_surfaces":6,"processed_surfaces":6},"document_sha256":"d22275b5740a519599d3e92d67a9a460cdf35b7fcb4801e15dd1a65edb594b56","explicit_blocking":[],"mode":"local","ontology_sha256":"76d41a29233c830e1940ebb3244263e2b9bfb8dd6a01f2a1d1c51ce5a1b10468","output_schema":"semantic-audit-result/v1","schema_version":"semantic-verdict-request/v1","subject":"raw/branch-notes/feature-api-contract-baseline.md"},"audit_request_sha256":"0204d1871f560cb47bbae1f75deed3ce7302b96074cbb0df5eea1d75240c078e","audit_result":{"auditor":{"contract_version":"semantic-coherence/v1","model_id":"claude-opus-4-8","run_id":"ac63300cf9c708858"},"mode":"local","request_sha256":"0204d1871f560cb47bbae1f75deed3ce7302b96074cbb0df5eea1d75240c078e","schema_version":"semantic-audit-result/v1","subject":"raw/branch-notes/feature-api-contract-baseline.md","verdicts":[{"candidate_id":"SEM-CBDE4F76A1CFA54CE6DA","evidence_a":{"line_end":52,"line_start":52,"quote":"| `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]] |"},"evidence_b":{"line_end":53,"line_start":53,"quote":"| `DEC-CA-SKELETON-OPERATIONAL-CONTRACT-OPENAPI-001@1` | verification suite가 OpenAPI drift의 release-blocking 판정권을 소유한다 | Work Item 완료 조건에 적용 | [[raw/project-notes/ca-skeleton-operational-contract]] |"},"proof_manifest":null,"rationale":"A04 (inherited API-VERSIONING-001: /v1 URI prefix default) and A05 (inherited OPENAPI-001: verification suite owns OpenAPI drift release-blocking) address disjoint scopes — API versioning vs OpenAPI drift release gate. Both inherited project decisions coexist without either taking over the other's responsibility.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-30FB86B3707B7F6197EB","evidence_a":{"line_end":452,"line_start":452,"quote":"| `Idempotency-Key` 헤더가 모든 POST endpoint 에 실제로 노출되는지 (OpenAPI snapshot 기준) | header 채택 결정은 design 단계, OpenAPI snapshot 에 반영됐는지 별도 확인 필요 | `openapi.yaml` snapshot grep 또는 contract test 로 모든 POST operation 에 `Idempotency-Key` parameter 존재 검증 | `planned` |"},"evidence_b":{"line_end":453,"line_start":453,"quote":"| `/v1` URI prefix 가 모든 public endpoint 에 적용되는지 | versioning 결정과 실제 controller mapping 의 drift 가능성 | ArchUnit / Spring controller mapping inspector 로 `RequestMapping` prefix 검증 | `planned` |"},"proof_manifest":null,"rationale":"Both are separate '검증 대상, status planned' checklist items: A07 verifies Idempotency-Key header exposure on POST endpoints, A08 verifies /v1 prefix on public endpoints. Distinct subjects/objects; together they enumerate the branch's verification surface.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-B1917B6B4DF1E0458BE1","evidence_a":{"line_end":452,"line_start":452,"quote":"| `Idempotency-Key` 헤더가 모든 POST endpoint 에 실제로 노출되는지 (OpenAPI snapshot 기준) | header 채택 결정은 design 단계, OpenAPI snapshot 에 반영됐는지 별도 확인 필요 | `openapi.yaml` snapshot grep 또는 contract test 로 모든 POST operation 에 `Idempotency-Key` parameter 존재 검증 | `planned` |"},"evidence_b":{"line_end":464,"line_start":464,"quote":"| `If-Match` mismatch 가 412 Precondition Failed 로 응답하는지 (D15) | optimistic lock 충돌을 409 Conflict 또는 500 으로 매핑할 위험 | sample-portfolio UPDATE 에 `If-Match: W/\"0\"` (stale version) 전송 → 412 + envelope `error.code=CONFLICT` 또는 별도 `PRECONDITION_FAILED` | `planned` |"},"proof_manifest":null,"rationale":"Two distinct planned verification items: A07 (Idempotency-Key exposure) vs A09 (If-Match mismatch → 412). Different scopes (OpenAPI snapshot vs conditional request); no shared contract property.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-53BFDA8495CC9CCF1085","evidence_a":{"line_end":452,"line_start":452,"quote":"| `Idempotency-Key` 헤더가 모든 POST endpoint 에 실제로 노출되는지 (OpenAPI snapshot 기준) | header 채택 결정은 design 단계, OpenAPI snapshot 에 반영됐는지 별도 확인 필요 | `openapi.yaml` snapshot grep 또는 contract test 로 모든 POST operation 에 `Idempotency-Key` parameter 존재 검증 | `planned` |"},"evidence_b":{"line_end":470,"line_start":470,"quote":"| pagination `size` cap 이 강제되는지 (D18, 최대 footgun) | Spring `Pageable` default max = `DEFAULT_MAX_PAGE_SIZE = 2000` (SPRING-PAGE-C4 정정 — 이전 표현 `Integer.MAX_VALUE` 는 부정확). 2000 도 DoS 위험은 충분 — `?size=2000` × 무거운 응답 = 메모리 폭발 | `?size=10000000` → 400 VALIDATION_FAILED + envelope `error.details.field=size` + `error.details.code=SIZE_EXCEEDS_MAX` · `?size=500` (Spring default 2000 이하지만 project cap 100 초과) → 400 VALIDATION_FAILED | `planned` |"},"proof_manifest":null,"rationale":"A07 (Idempotency-Key exposure) and A10 (pagination size cap enforcement) are separate planned checks covering different concerns (idempotency header vs pagination). Compatible enumeration.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-18BC1C16BF8F36D03675","evidence_a":{"line_end":452,"line_start":452,"quote":"| `Idempotency-Key` 헤더가 모든 POST endpoint 에 실제로 노출되는지 (OpenAPI snapshot 기준) | header 채택 결정은 design 단계, OpenAPI snapshot 에 반영됐는지 별도 확인 필요 | `openapi.yaml` snapshot grep 또는 contract test 로 모든 POST operation 에 `Idempotency-Key` parameter 존재 검증 | `planned` |"},"evidence_b":{"line_end":477,"line_start":477,"quote":"| cursor token 이 opaque (client parse 불가) + signed (tamper 감지) + TTL (24h 후 만료) 3개 invariant 모두 만족하는지 (D22) | typed cursor 노출 / unsigned token / no TTL 중 하나라도 깨지면 contract 위반 | cursor token roundtrip test (next page 정상) + base64 decode 후 client 가 의미 있는 정보 추출 못함 검증 + tamper test (token 1byte 변조 → 400) + TTL test (24h 1초 후 token → 400) | `planned` |"},"proof_manifest":null,"rationale":"A07 (Idempotency-Key exposure) and A12 (cursor token opaque+signed+TTL invariants) are independent planned verification items in different scopes; no overlap.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-DF646A99A6705B4DA6E0","evidence_a":{"line_end":452,"line_start":452,"quote":"| `Idempotency-Key` 헤더가 모든 POST endpoint 에 실제로 노출되는지 (OpenAPI snapshot 기준) | header 채택 결정은 design 단계, OpenAPI snapshot 에 반영됐는지 별도 확인 필요 | `openapi.yaml` snapshot grep 또는 contract test 로 모든 POST operation 에 `Idempotency-Key` parameter 존재 검증 | `planned` |"},"evidence_b":{"line_end":265,"line_start":265,"quote":"| D3 | idempotency header 이름 `Idempotency-Key` 채택 (POST endpoint 표준 surface) | `raw/official-docs/idempotency-stripe-api-ref.md#STRIPE-IDEMP-C1`, `raw/official-docs/idempotency-ietf-draft.md#IETF-IDEMP-C1`, `raw/company-tech-blogs/idempotency-toss-payments-techblog.md#TOSS-IDEMP-C1` | `official-vendor-doc + official-reference + company-case-study` | IETF-IDEMP 는 draft 상태 (정식 RFC 아님); TOSS 는 company-case-study — 표준 lock-in 아님. PayPal 은 다른 header (`PayPal-Request-Id`) 사용 — 호환성은 별도 |"},"proof_manifest":null,"rationale":"A16 (D3: this branch owns Idempotency-Key header name) is the decision; A07 verifies that header's exposure in the OpenAPI snapshot. Verification supplies a check for the owned surface without taking over ownership.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-A607497D134EE8457082","evidence_a":{"line_end":452,"line_start":452,"quote":"| `Idempotency-Key` 헤더가 모든 POST endpoint 에 실제로 노출되는지 (OpenAPI snapshot 기준) | header 채택 결정은 design 단계, OpenAPI snapshot 에 반영됐는지 별도 확인 필요 | `openapi.yaml` snapshot grep 또는 contract test 로 모든 POST operation 에 `Idempotency-Key` parameter 존재 검증 | `planned` |"},"evidence_b":{"line_end":435,"line_start":435,"quote":" - [[raw/branch-notes/feature-operational-error-observability-foundation]] 의 `error-codes.yaml`(D11) + envelope schema 에 의존 — registry `http_status` column 이 status 매핑 SSOT. **(엣지) `error-codes.yaml` 미존재 또는 row 누락 시 D11 mapping consistency contract test 동작**: registry 가 존재하는데 row 가 빠진 경우 = test FAIL(drift). registry 자체가 아직 생성 전인 현재 documented-only 단계에서는 D11 test 가 `planned`(비활성) — **registry 생성이 D11 test 활성화의 선행 조건**이며, registry 부재를 SKIP(통과)으로 처리하면 안 됨."},"proof_manifest":null,"rationale":"A07 (Idempotency-Key exposure, OpenAPI snapshot scope) and A27 (dependency on foundation branch error-codes.yaml, status-mapping scope) address unrelated concerns; compatible.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-BB074E310C1B5B8E6436","evidence_a":{"line_end":452,"line_start":452,"quote":"| `Idempotency-Key` 헤더가 모든 POST endpoint 에 실제로 노출되는지 (OpenAPI snapshot 기준) | header 채택 결정은 design 단계, OpenAPI snapshot 에 반영됐는지 별도 확인 필요 | `openapi.yaml` snapshot grep 또는 contract test 로 모든 POST operation 에 `Idempotency-Key` parameter 존재 검증 | `planned` |"},"evidence_b":{"line_end":437,"line_start":437,"quote":" - [[raw/branch-notes/feature-rate-limit-idempotency-contract]] 에 의존(D3/D4) — `Idempotency-Key` key shape/scope/replay semantics owner. 본 branch 는 header *이름* 만."},"proof_manifest":null,"rationale":"A28 delegates Idempotency-Key key shape/scope/replay semantics to rate-limit branch while this branch owns only the header name; A07 verifies the header-name surface is exposed. Compatible split — verification of the name, semantics owned elsewhere.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-942FC1582FAA81D058A5","evidence_a":{"line_end":452,"line_start":452,"quote":"| `Idempotency-Key` 헤더가 모든 POST endpoint 에 실제로 노출되는지 (OpenAPI snapshot 기준) | header 채택 결정은 design 단계, OpenAPI snapshot 에 반영됐는지 별도 확인 필요 | `openapi.yaml` snapshot grep 또는 contract test 로 모든 POST operation 에 `Idempotency-Key` parameter 존재 검증 | `planned` |"},"evidence_b":{"line_end":392,"line_start":392,"quote":"| `Idempotency-Key` header *이름* (D3) | **owner** (header name 만) | **owner** of key shape/scope/replay: `feature-rate-limit-idempotency-contract` | 본 branch → rate-limit branch (header name produces, key shape consumes) |"},"proof_manifest":null,"rationale":"A36 (D3 cross-branch role: this branch owns Idempotency-Key header name only) is the ownership claim; A07 verifies that header's exposure. Verification detail for the owned surface.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-AA592D614F3D40327A28","evidence_a":{"line_end":453,"line_start":453,"quote":"| `/v1` URI prefix 가 모든 public endpoint 에 적용되는지 | versioning 결정과 실제 controller mapping 의 drift 가능성 | ArchUnit / Spring controller mapping inspector 로 `RequestMapping` prefix 검증 | `planned` |"},"evidence_b":{"line_end":464,"line_start":464,"quote":"| `If-Match` mismatch 가 412 Precondition Failed 로 응답하는지 (D15) | optimistic lock 충돌을 409 Conflict 또는 500 으로 매핑할 위험 | sample-portfolio UPDATE 에 `If-Match: W/\"0\"` (stale version) 전송 → 412 + envelope `error.code=CONFLICT` 또는 별도 `PRECONDITION_FAILED` | `planned` |"},"proof_manifest":null,"rationale":"A08 (/v1 prefix on public endpoints) and A09 (If-Match → 412) are separate planned verification items in different scopes; compatible enumeration.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-3AF87FC3906856813907","evidence_a":{"line_end":453,"line_start":453,"quote":"| `/v1` URI prefix 가 모든 public endpoint 에 적용되는지 | versioning 결정과 실제 controller mapping 의 drift 가능성 | ArchUnit / Spring controller mapping inspector 로 `RequestMapping` prefix 검증 | `planned` |"},"evidence_b":{"line_end":470,"line_start":470,"quote":"| pagination `size` cap 이 강제되는지 (D18, 최대 footgun) | Spring `Pageable` default max = `DEFAULT_MAX_PAGE_SIZE = 2000` (SPRING-PAGE-C4 정정 — 이전 표현 `Integer.MAX_VALUE` 는 부정확). 2000 도 DoS 위험은 충분 — `?size=2000` × 무거운 응답 = 메모리 폭발 | `?size=10000000` → 400 VALIDATION_FAILED + envelope `error.details.field=size` + `error.details.code=SIZE_EXCEEDS_MAX` · `?size=500` (Spring default 2000 이하지만 project cap 100 초과) → 400 VALIDATION_FAILED | `planned` |"},"proof_manifest":null,"rationale":"A08 (/v1 prefix verification) and A10 (pagination size cap verification) are independent planned checks covering versioning vs pagination; no shared property.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-ABA2C9E08A2ADF777CF2","evidence_a":{"line_end":453,"line_start":453,"quote":"| `/v1` URI prefix 가 모든 public endpoint 에 적용되는지 | versioning 결정과 실제 controller mapping 의 drift 가능성 | ArchUnit / Spring controller mapping inspector 로 `RequestMapping` prefix 검증 | `planned` |"},"evidence_b":{"line_end":477,"line_start":477,"quote":"| cursor token 이 opaque (client parse 불가) + signed (tamper 감지) + TTL (24h 후 만료) 3개 invariant 모두 만족하는지 (D22) | typed cursor 노출 / unsigned token / no TTL 중 하나라도 깨지면 contract 위반 | cursor token roundtrip test (next page 정상) + base64 decode 후 client 가 의미 있는 정보 추출 못함 검증 + tamper test (token 1byte 변조 → 400) + TTL test (24h 1초 후 token → 400) | `planned` |"},"proof_manifest":null,"rationale":"A08 (/v1 prefix verification) and A12 (cursor token invariants) are distinct planned items in unrelated scopes; compatible.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-1D64524A2099A24F8C5C","evidence_a":{"line_end":453,"line_start":453,"quote":"| `/v1` URI prefix 가 모든 public endpoint 에 적용되는지 | versioning 결정과 실제 controller mapping 의 drift 가능성 | ArchUnit / Spring controller mapping inspector 로 `RequestMapping` prefix 검증 | `planned` |"},"evidence_b":{"line_end":264,"line_start":264,"quote":"| D2 | API versioning 기본값 `/v1` URI prefix + `X-Api-Version` 은 supplemental | `raw/official-docs/google-aip-185-resource-versioning.md#AIP185-C1` (major version 노출 의무), `#AIP185-C2` (minor/patch 노출 금지 — `/v1.0` 금지 → `/v1`), `#AIP185-C3` (새 major 는 이전 major 에 의존 금지) | `official-reference` (Google AIP — Google 사내 community guideline; IETF/W3C 표준 아님) | AIP-185 본문은 protobuf 컨텍스트 — REST URI path vs header 선택 자체에 대한 normative 진술은 본 인용 범위 밖. `X-Api-Version` 을 supplemental 로 두는 결정의 직접 근거는 별도 (예: ca-tmpl internal design) — AIP-185 는 path 형식 (`/v1`) 만 corroborate. **UNSUPPORTED_IMPL_DECISION**: path version 과 `X-Api-Version` 충돌 시 *path 우선* 규칙은 project-internal convention — AIP-185 에 path/header 우선순위 normative 진술 없음. trade-off: URI 가 1급 계약 표면(캐시·라우팅·로그에서 가시)이므로 path 를 권위 source 로, header 는 실험/전환 보조로 둠 |"},"proof_manifest":null,"rationale":"A15 (D2: versioning uses /v1 default + X-Api-Version supplemental) is the decision; A08 verifies /v1 applied to all public endpoints. Same /v1 value; verification supplies the test stage for the decision.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-817A81A7FD395F2F3798","evidence_a":{"line_end":453,"line_start":453,"quote":"| `/v1` URI prefix 가 모든 public endpoint 에 적용되는지 | versioning 결정과 실제 controller mapping 의 drift 가능성 | ArchUnit / Spring controller mapping inspector 로 `RequestMapping` prefix 검증 | `planned` |"},"evidence_b":{"line_end":435,"line_start":435,"quote":" - [[raw/branch-notes/feature-operational-error-observability-foundation]] 의 `error-codes.yaml`(D11) + envelope schema 에 의존 — registry `http_status` column 이 status 매핑 SSOT. **(엣지) `error-codes.yaml` 미존재 또는 row 누락 시 D11 mapping consistency contract test 동작**: registry 가 존재하는데 row 가 빠진 경우 = test FAIL(drift). registry 자체가 아직 생성 전인 현재 documented-only 단계에서는 D11 test 가 `planned`(비활성) — **registry 생성이 D11 test 활성화의 선행 조건**이며, registry 부재를 SKIP(통과)으로 처리하면 안 됨."},"proof_manifest":null,"rationale":"A08 (/v1 prefix verification, versioning scope) and A27 (dependency on error-codes.yaml, status-mapping scope) address unrelated concerns; compatible.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-DE0A13B6115913398A09","evidence_a":{"line_end":453,"line_start":453,"quote":"| `/v1` URI prefix 가 모든 public endpoint 에 적용되는지 | versioning 결정과 실제 controller mapping 의 drift 가능성 | ArchUnit / Spring controller mapping inspector 로 `RequestMapping` prefix 검증 | `planned` |"},"evidence_b":{"line_end":328,"line_start":328,"quote":"| versioning | `/v1` URI prefix | `X-Api-Version` supplemental header | media-type/header/path version 혼용 | OpenAPI path version test | version 없는 public endpoint |"},"proof_manifest":null,"rationale":"A30 (D2 Decisionized Work Item: /v1 URI prefix, X-Api-Version supplemental) is the decision; A08 verifies /v1 on all public endpoints. Same value; verification stage supplied for the decision.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-D608EDA1C37381DFB9FA","evidence_a":{"line_end":464,"line_start":464,"quote":"| `If-Match` mismatch 가 412 Precondition Failed 로 응답하는지 (D15) | optimistic lock 충돌을 409 Conflict 또는 500 으로 매핑할 위험 | sample-portfolio UPDATE 에 `If-Match: W/\"0\"` (stale version) 전송 → 412 + envelope `error.code=CONFLICT` 또는 별도 `PRECONDITION_FAILED` | `planned` |"},"evidence_b":{"line_end":470,"line_start":470,"quote":"| pagination `size` cap 이 강제되는지 (D18, 최대 footgun) | Spring `Pageable` default max = `DEFAULT_MAX_PAGE_SIZE = 2000` (SPRING-PAGE-C4 정정 — 이전 표현 `Integer.MAX_VALUE` 는 부정확). 2000 도 DoS 위험은 충분 — `?size=2000` × 무거운 응답 = 메모리 폭발 | `?size=10000000` → 400 VALIDATION_FAILED + envelope `error.details.field=size` + `error.details.code=SIZE_EXCEEDS_MAX` · `?size=500` (Spring default 2000 이하지만 project cap 100 초과) → 400 VALIDATION_FAILED | `planned` |"},"proof_manifest":null,"rationale":"A09 (If-Match → 412) and A10 (pagination size cap) are separate planned verification items in different scopes (conditional request vs pagination); compatible.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-8E21C5E658B125AD4A95","evidence_a":{"line_end":464,"line_start":464,"quote":"| `If-Match` mismatch 가 412 Precondition Failed 로 응답하는지 (D15) | optimistic lock 충돌을 409 Conflict 또는 500 으로 매핑할 위험 | sample-portfolio UPDATE 에 `If-Match: W/\"0\"` (stale version) 전송 → 412 + envelope `error.code=CONFLICT` 또는 별도 `PRECONDITION_FAILED` | `planned` |"},"evidence_b":{"line_end":477,"line_start":477,"quote":"| cursor token 이 opaque (client parse 불가) + signed (tamper 감지) + TTL (24h 후 만료) 3개 invariant 모두 만족하는지 (D22) | typed cursor 노출 / unsigned token / no TTL 중 하나라도 깨지면 contract 위반 | cursor token roundtrip test (next page 정상) + base64 decode 후 client 가 의미 있는 정보 추출 못함 검증 + tamper test (token 1byte 변조 → 400) + TTL test (24h 1초 후 token → 400) | `planned` |"},"proof_manifest":null,"rationale":"A09 (If-Match → 412) and A12 (cursor token invariants) are independent planned items in unrelated scopes; compatible enumeration.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-F681A21173CCFADFA917","evidence_a":{"line_end":464,"line_start":464,"quote":"| `If-Match` mismatch 가 412 Precondition Failed 로 응답하는지 (D15) | optimistic lock 충돌을 409 Conflict 또는 500 으로 매핑할 위험 | sample-portfolio UPDATE 에 `If-Match: W/\"0\"` (stale version) 전송 → 412 + envelope `error.code=CONFLICT` 또는 별도 `PRECONDITION_FAILED` | `planned` |"},"evidence_b":{"line_end":427,"line_start":427,"quote":" - **conditional request** — `If-Match` mismatch 를 409/500 으로 매핑하면 실패(→ 412), `If-None-Match` match 304 에 body 동봉하면 실패. (D15)"},"proof_manifest":null,"rationale":"Same subject (If-Match mismatch), same predicate (returns), same scope (conditional request): both A09 (planned verification) and A24 (D15 failure/edge path) assign the identical value 412 Precondition Failed and both forbid mapping to 409/500. Full agreement, no drift.","verdict":"CONSISTENT"},{"candidate_id":"SEM-1EABE3ECB52507238C4E","evidence_a":{"line_end":464,"line_start":464,"quote":"| `If-Match` mismatch 가 412 Precondition Failed 로 응답하는지 (D15) | optimistic lock 충돌을 409 Conflict 또는 500 으로 매핑할 위험 | sample-portfolio UPDATE 에 `If-Match: W/\"0\"` (stale version) 전송 → 412 + envelope `error.code=CONFLICT` 또는 별도 `PRECONDITION_FAILED` | `planned` |"},"evidence_b":{"line_end":435,"line_start":435,"quote":" - [[raw/branch-notes/feature-operational-error-observability-foundation]] 의 `error-codes.yaml`(D11) + envelope schema 에 의존 — registry `http_status` column 이 status 매핑 SSOT. **(엣지) `error-codes.yaml` 미존재 또는 row 누락 시 D11 mapping consistency contract test 동작**: registry 가 존재하는데 row 가 빠진 경우 = test FAIL(drift). registry 자체가 아직 생성 전인 현재 documented-only 단계에서는 D11 test 가 `planned`(비활성) — **registry 생성이 D11 test 활성화의 선행 조건**이며, registry 부재를 SKIP(통과)으로 처리하면 안 됨."},"proof_manifest":null,"rationale":"A09 (If-Match → 412, conditional request) and A27 (dependency on error-codes.yaml, status-mapping) address unrelated concerns; compatible.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-6A8BE3D703C0A6710EA6","evidence_a":{"line_end":464,"line_start":464,"quote":"| `If-Match` mismatch 가 412 Precondition Failed 로 응답하는지 (D15) | optimistic lock 충돌을 409 Conflict 또는 500 으로 매핑할 위험 | sample-portfolio UPDATE 에 `If-Match: W/\"0\"` (stale version) 전송 → 412 + envelope `error.code=CONFLICT` 또는 별도 `PRECONDITION_FAILED` | `planned` |"},"evidence_b":{"line_end":397,"line_start":397,"quote":"| Conditional request (`ETag`/`If-Match`/412/304) (D15) | **owner** (HTTP layer) | **consumer**: sample-portfolio fixture (`WorkLogVersion` 이 ETag derivation 의 source) | 본 branch → sample-portfolio fixture |"},"proof_manifest":null,"rationale":"A39 (D15 cross-branch role: this branch owns the conditional-request ETag/If-Match/412/304 HTTP layer) is the ownership claim; A09 verifies the 412 behavior. Scope conditional request — verification supplies a check for the owned layer.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-7212CEF2ACD31B9B3BEF","evidence_a":{"line_end":470,"line_start":470,"quote":"| pagination `size` cap 이 강제되는지 (D18, 최대 footgun) | Spring `Pageable` default max = `DEFAULT_MAX_PAGE_SIZE = 2000` (SPRING-PAGE-C4 정정 — 이전 표현 `Integer.MAX_VALUE` 는 부정확). 2000 도 DoS 위험은 충분 — `?size=2000` × 무거운 응답 = 메모리 폭발 | `?size=10000000` → 400 VALIDATION_FAILED + envelope `error.details.field=size` + `error.details.code=SIZE_EXCEEDS_MAX` · `?size=500` (Spring default 2000 이하지만 project cap 100 초과) → 400 VALIDATION_FAILED | `planned` |"},"evidence_b":{"line_end":477,"line_start":477,"quote":"| cursor token 이 opaque (client parse 불가) + signed (tamper 감지) + TTL (24h 후 만료) 3개 invariant 모두 만족하는지 (D22) | typed cursor 노출 / unsigned token / no TTL 중 하나라도 깨지면 contract 위반 | cursor token roundtrip test (next page 정상) + base64 decode 후 client 가 의미 있는 정보 추출 못함 검증 + tamper test (token 1byte 변조 → 400) + TTL test (24h 1초 후 token → 400) | `planned` |"},"proof_manifest":null,"rationale":"A10 (pagination size cap) and A12 (cursor token invariants) are distinct planned verification items — offset pagination vs cursor pagination; compatible.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-82FD610D3ED6432D327F","evidence_a":{"line_end":470,"line_start":470,"quote":"| pagination `size` cap 이 강제되는지 (D18, 최대 footgun) | Spring `Pageable` default max = `DEFAULT_MAX_PAGE_SIZE = 2000` (SPRING-PAGE-C4 정정 — 이전 표현 `Integer.MAX_VALUE` 는 부정확). 2000 도 DoS 위험은 충분 — `?size=2000` × 무거운 응답 = 메모리 폭발 | `?size=10000000` → 400 VALIDATION_FAILED + envelope `error.details.field=size` + `error.details.code=SIZE_EXCEEDS_MAX` · `?size=500` (Spring default 2000 이하지만 project cap 100 초과) → 400 VALIDATION_FAILED | `planned` |"},"evidence_b":{"line_end":286,"line_start":286,"quote":"| D18 | Pagination — `page` 0-indexed (Spring `Pageable` 정합), `size` default 20 / max 100 / min 1 · `size > 100` 또는 `size < 1` 또는 `page < 0` 은 400 VALIDATION_FAILED · 빈 list 는 `data: []` (절대 `null` 아님) + `meta.page.total = 0` · 깊은 offset (`page > 10000`) 은 `Deprecation` 헤더 + cursor 권고 | `raw/official-docs/spring-data-pageable-defaults.md#SPRING-PAGE-C1` (`page` 파라미터 0-indexed, default 0), `#SPRING-PAGE-C2` (`size` 파라미터 default 20), `#SPRING-PAGE-C3` (Spring Data 레포지토리 infrastructure 의 `Pageable` 은 zero-indexed), `#SPRING-PAGE-C4` (`PageableHandlerMethodArgumentResolverSupport.DEFAULT_MAX_PAGE_SIZE = 2000` — Spring 기본 max 가 2000 이므로 project 의 100 cap 은 별도 opt-in override 임을 정당화), `#SPRING-PAGE-C5` (annotation 없는 fallback = `PageRequest.of(0, 20)`), `#SPRING-PAGE-C6` (`setOneIndexedParameters` default false → page 0 = first page). 추가 normative 근거: `raw/official-docs/google-aip-158-pagination.md#AIP158-C1` (collection pagination 처음부터 제공 필수, 나중 추가 = backwards-incompatible), `#AIP158-C2` (page_size server-side cap SHOULD coerce down; max 숫자는 server-defined — 100 은 project-internal), `#AIP158-C3` (page_token MUST NOT required; subsequent page_size 변경 MUST honor), `#AIP158-C4` (next_page_token empty = EoC MUST, 유일한 EoC 시그널), `#AIP158-C5` (page token opaque + URL-safe MUST; base64 단독 불충분). JSON:API JSONAPI-PAGE-C1~C6 모두 size cap 또는 index base 의 normative 진술 없음 — JSONAPI-PAGE-C6 가 pagination strategy 에 agnostic 임을 명시. `size` max 100 cap 및 min 1 / `page < 0` 400 처리는 project-internal DoS prevention trade-off (UNSUPPORTED_IMPL_DECISION 잔존 — Spring 기본값 이하 추가 제한) | `official-vendor-doc` (Spring Data Commons — SPRING-PAGE-C1~C6) + `official-reference` (AIP-158 — AIP158-C1~C5) + UNSUPPORTED_IMPL_DECISION (`size` 100 cap / min 1 / 깊은 offset threshold 10000 은 project-internal 숫자; size>100 을 AIP-158 권고 coerce 대신 400-reject 강화도 project-internal) | 가장 critical footgun 결정 — `size=10000000` DoS 방지 + 0/1-indexed Spring 정합. Spring default max 는 2000 이지 Integer.MAX_VALUE 가 아님 (C4 보정). 깊은 offset 의 cursor 권고는 본 branch 가 박지만 cursor endpoint 의 shape (opaque token encoding/TTL) 결정은 future B16 — sibling 또는 후속 작업 |"},"proof_manifest":null,"rationale":"Scope pagination, predicate has_threshold: A21 (D18) supplies the concrete thresholds (default 20 / max 100 / min 1), while A10 is the planned verification that the size cap is enforced. Decision + its verification; same cap, no conflict.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-206934F17F21136944A3","evidence_a":{"line_end":470,"line_start":470,"quote":"| pagination `size` cap 이 강제되는지 (D18, 최대 footgun) | Spring `Pageable` default max = `DEFAULT_MAX_PAGE_SIZE = 2000` (SPRING-PAGE-C4 정정 — 이전 표현 `Integer.MAX_VALUE` 는 부정확). 2000 도 DoS 위험은 충분 — `?size=2000` × 무거운 응답 = 메모리 폭발 | `?size=10000000` → 400 VALIDATION_FAILED + envelope `error.details.field=size` + `error.details.code=SIZE_EXCEEDS_MAX` · `?size=500` (Spring default 2000 이하지만 project cap 100 초과) → 400 VALIDATION_FAILED | `planned` |"},"evidence_b":{"line_end":435,"line_start":435,"quote":" - [[raw/branch-notes/feature-operational-error-observability-foundation]] 의 `error-codes.yaml`(D11) + envelope schema 에 의존 — registry `http_status` column 이 status 매핑 SSOT. **(엣지) `error-codes.yaml` 미존재 또는 row 누락 시 D11 mapping consistency contract test 동작**: registry 가 존재하는데 row 가 빠진 경우 = test FAIL(drift). registry 자체가 아직 생성 전인 현재 documented-only 단계에서는 D11 test 가 `planned`(비활성) — **registry 생성이 D11 test 활성화의 선행 조건**이며, registry 부재를 SKIP(통과)으로 처리하면 안 됨."},"proof_manifest":null,"rationale":"A10 (pagination size cap, pagination scope) and A27 (dependency on error-codes.yaml, status-mapping scope) address unrelated concerns; compatible.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-D1091C5F4DABA55917EB","evidence_a":{"line_end":473,"line_start":473,"quote":"| `error-codes.yaml` 의 모든 row 가 실제 controller 응답의 HTTP status 와 일치하는지 (D11, registry drift detection) | registry row 와 controller drift 가 untracked 위험 | 모든 `error.code` row 에 대해 contract test 가 trigger (오류 발생 fixture) 후 실제 응답 status code 가 `http_status` column 과 일치 검증 | `needs-confirmation` (registry-governance branch 와 정합성 확인) |"},"evidence_b":{"line_end":478,"line_start":478,"quote":"| sync bulk endpoint 가 atomic 인지 + async bulk endpoint 가 LRO polling pattern 인지 (D23 sync/async 분기) | sync 에서 partial failure 허용은 AIP233-C7 위반. controller 작성자가 sync/async 구분 없이 partial failure 응답하거나 flat array body 채택 가능성 | sync batch contract test: `POST /v1/worklogs:batchCreate` 의 한 항목 fail 시 전체 rollback + HTTP 4xx + envelope.success=false. async batch contract test: `POST /v1/operations:batchCreate` 의 202 + Location + polling endpoint 의 `data.result.results[]` 가 항목별 success/error 매핑. BATCH_PARTIAL_FAILURE 가 sync 응답에 나타나면 실패. 1001 항목 size cap 위반 → 400 | `needs-confirmation` (boundary branch B14 BulkEnvelope.partial + D17 LRO endpoint pattern cross-link) |"},"proof_manifest":null,"rationale":"A11 (status-mapping registry drift verification, D11) and A13 (sync bulk atomicity, D23) are separate planned checks in unrelated scopes (status mapping vs bulk operation); compatible.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-2A37156B147650C3BA92","evidence_a":{"line_end":473,"line_start":473,"quote":"| `error-codes.yaml` 의 모든 row 가 실제 controller 응답의 HTTP status 와 일치하는지 (D11, registry drift detection) | registry row 와 controller drift 가 untracked 위험 | 모든 `error.code` row 에 대해 contract test 가 trigger (오류 발생 fixture) 후 실제 응답 status code 가 `http_status` column 과 일치 검증 | `needs-confirmation` (registry-governance branch 와 정합성 확인) |"},"evidence_b":{"line_end":468,"line_start":468,"quote":"| LRO endpoint 가 202 + `Location` + envelope `data.{operationId,statusUrl}` 형식인지 (D17) | 현재 ca-skeleton 에 LRO endpoint 자체가 없음 — sample-portfolio fixture 신설 필요 | sample-portfolio 에 `POST /v1/worklogs:export` 같은 LRO fixture 추가 후 응답 검증 | `needs-confirmation` (sample-portfolio fixture 확장 필요) |"},"proof_manifest":null,"rationale":"A11 (status-mapping verification, D11) and A14 (LRO endpoint 202 shape, D17) are independent planned items in different scopes; compatible.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-F7745D43DFE77807A768","evidence_a":{"line_end":473,"line_start":473,"quote":"| `error-codes.yaml` 의 모든 row 가 실제 controller 응답의 HTTP status 와 일치하는지 (D11, registry drift detection) | registry row 와 controller drift 가 untracked 위험 | 모든 `error.code` row 에 대해 contract test 가 trigger (오류 발생 fixture) 후 실제 응답 status code 가 `http_status` column 과 일치 검증 | `needs-confirmation` (registry-governance branch 와 정합성 확인) |"},"evidence_b":{"line_end":273,"line_start":273,"quote":"| D11 | HTTP status code ↔ envelope `error.category`/`error.code` 매핑 SSOT 는 `error-codes.yaml` (foundation branch 소유 registry §21 row 49 + `http_status` column). 본 branch 는 mapping consistency contract test 의 producer | UNSUPPORTED_DECISION (project-internal SSOT 위치 결정; 외부 표준 직접 근거 없음 — RFC 9110 §15.x 가 *개별 status code* 의미를 정의할 뿐 *registry 형식의 SSOT* 자체는 표준 영역 밖); 보강: 개별 row 의 매핑 (예: 404 ↔ `RESOURCE_NOT_FOUND`) 의 normative 근거는 RFC 9110 §15.x 각 status section — 본 branch 의 contract test 가 *registry 와 응답의 drift* 만 검증, *registry 자체의 row 정합성* 은 foundation branch 책임 | N/A | mapping SSOT 가 registry 인 것 자체는 project-internal 결정. RFC 9110 §15.5.6 (405), §15.5.13 (412), §15.5.15 (414) 같은 다른 status code section 의 raw 발췌가 *다음 세션* 작업 — 이후 각 row 의 Supporting Claim ID 보강 가능. cross-branch: registry row 의 *추가/수정* 은 §21 변경 절차 (registry-governance branch) 적용 |"},"proof_manifest":null,"rationale":"A19 (D11: foundation branch's error-codes.yaml owns the status-mapping SSOT; this branch is consistency-test producer) is the ownership decision; A11 is the planned verification that registry rows match controller status. Ownership + its verification, no conflict.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-F1E8B1ED1522145C92D8","evidence_a":{"line_end":473,"line_start":473,"quote":"| `error-codes.yaml` 의 모든 row 가 실제 controller 응답의 HTTP status 와 일치하는지 (D11, registry drift detection) | registry row 와 controller drift 가 untracked 위험 | 모든 `error.code` row 에 대해 contract test 가 trigger (오류 발생 fixture) 후 실제 응답 status code 가 `http_status` column 과 일치 검증 | `needs-confirmation` (registry-governance branch 와 정합성 확인) |"},"evidence_b":{"line_end":435,"line_start":435,"quote":" - [[raw/branch-notes/feature-operational-error-observability-foundation]] 의 `error-codes.yaml`(D11) + envelope schema 에 의존 — registry `http_status` column 이 status 매핑 SSOT. **(엣지) `error-codes.yaml` 미존재 또는 row 누락 시 D11 mapping consistency contract test 동작**: registry 가 존재하는데 row 가 빠진 경우 = test FAIL(drift). registry 자체가 아직 생성 전인 현재 documented-only 단계에서는 D11 test 가 `planned`(비활성) — **registry 생성이 D11 test 활성화의 선행 조건**이며, registry 부재를 SKIP(통과)으로 처리하면 안 됨."},"proof_manifest":null,"rationale":"A27 declares dependency on the foundation branch's error-codes.yaml (registry http_status = SSOT); A11 verifies registry-controller consistency. Consumer declaration + verification stage; compatible.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-3D265F054686DCA98C9D","evidence_a":{"line_end":473,"line_start":473,"quote":"| `error-codes.yaml` 의 모든 row 가 실제 controller 응답의 HTTP status 와 일치하는지 (D11, registry drift detection) | registry row 와 controller drift 가 untracked 위험 | 모든 `error.code` row 에 대해 contract test 가 trigger (오류 발생 fixture) 후 실제 응답 status code 가 `http_status` column 과 일치 검증 | `needs-confirmation` (registry-governance branch 와 정합성 확인) |"},"evidence_b":{"line_end":340,"line_start":340,"quote":"| HTTP status mapping SSOT | `error-codes.yaml` 의 `http_status` column 이 모든 매핑의 SSOT (registry §21, owner: foundation branch) | 본 branch 는 mapping consistency contract test 의 producer | controller 가 registry 와 다른 HTTP status 반환 | status mapping consistency test (모든 `error.code` row 에 대해 실제 응답의 HTTP status 가 registry 와 일치) | registry-controller drift |"},"proof_manifest":null,"rationale":"A32 (D11: error-codes.yaml http_status column owns all status mapping, foundation branch) is the ownership claim; A11 verifies rows match controller responses. Ownership + verification; no conflict.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-0515C24E3AC94C29FAFD","evidence_a":{"line_end":473,"line_start":473,"quote":"| `error-codes.yaml` 의 모든 row 가 실제 controller 응답의 HTTP status 와 일치하는지 (D11, registry drift detection) | registry row 와 controller drift 가 untracked 위험 | 모든 `error.code` row 에 대해 contract test 가 trigger (오류 발생 fixture) 후 실제 응답 status code 가 `http_status` column 과 일치 검증 | `needs-confirmation` (registry-governance branch 와 정합성 확인) |"},"evidence_b":{"line_end":340,"line_start":340,"quote":"| HTTP status mapping SSOT | `error-codes.yaml` 의 `http_status` column 이 모든 매핑의 SSOT (registry §21, owner: foundation branch) | 본 branch 는 mapping consistency contract test 의 producer | controller 가 registry 와 다른 HTTP status 반환 | status mapping consistency test (모든 `error.code` row 에 대해 실제 응답의 HTTP status 가 registry 와 일치) | registry-controller drift |"},"proof_manifest":null,"rationale":"Scope status mapping: A33 (this branch produces the mapping consistency contract test) is the producer role; A11 is the concrete verification that test performs. Producer + its check; compatible.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-C7EE073573BAD47B036E","evidence_a":{"line_end":473,"line_start":473,"quote":"| `error-codes.yaml` 의 모든 row 가 실제 controller 응답의 HTTP status 와 일치하는지 (D11, registry drift detection) | registry row 와 controller drift 가 untracked 위험 | 모든 `error.code` row 에 대해 contract test 가 trigger (오류 발생 fixture) 후 실제 응답 status code 가 `http_status` column 과 일치 검증 | `needs-confirmation` (registry-governance branch 와 정합성 확인) |"},"evidence_b":{"line_end":390,"line_start":390,"quote":"| HTTP status ↔ envelope `error.code` 매핑 (D11) | **producer** of mapping consistency contract test | **owner** of registry: `feature-operational-error-observability-foundation` (`error-codes.yaml`) | 본 branch ← foundation branch |"},"proof_manifest":null,"rationale":"A37 (D11 cross-branch role: foundation branch owns the registry, this branch is consistency-test producer) plus A11's verification of registry-controller consistency form the ownership + verification split; compatible.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-14734583F94C38099ED9","evidence_a":{"line_end":473,"line_start":473,"quote":"| `error-codes.yaml` 의 모든 row 가 실제 controller 응답의 HTTP status 와 일치하는지 (D11, registry drift detection) | registry row 와 controller drift 가 untracked 위험 | 모든 `error.code` row 에 대해 contract test 가 trigger (오류 발생 fixture) 후 실제 응답 status code 가 `http_status` column 과 일치 검증 | `needs-confirmation` (registry-governance branch 와 정합성 확인) |"},"evidence_b":{"line_end":95,"line_start":95,"quote":"- HTTP status code ↔ envelope `error.category`/`error.code` 의 전체 매핑 SSOT 위치 결정."},"proof_manifest":null,"rationale":"A43 (scope-in: this branch owns the decision of WHERE the full status-mapping SSOT lives) is the location decision, resolved as the foundation branch's error-codes.yaml; A11 verifies registry rows match controller status. Meta-decision + verification, distinct objects; compatible.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-2A441C1EDA439D54F53C","evidence_a":{"line_end":477,"line_start":477,"quote":"| cursor token 이 opaque (client parse 불가) + signed (tamper 감지) + TTL (24h 후 만료) 3개 invariant 모두 만족하는지 (D22) | typed cursor 노출 / unsigned token / no TTL 중 하나라도 깨지면 contract 위반 | cursor token roundtrip test (next page 정상) + base64 decode 후 client 가 의미 있는 정보 추출 못함 검증 + tamper test (token 1byte 변조 → 400) + TTL test (24h 1초 후 token → 400) | `planned` |"},"evidence_b":{"line_end":435,"line_start":435,"quote":" - [[raw/branch-notes/feature-operational-error-observability-foundation]] 의 `error-codes.yaml`(D11) + envelope schema 에 의존 — registry `http_status` column 이 status 매핑 SSOT. **(엣지) `error-codes.yaml` 미존재 또는 row 누락 시 D11 mapping consistency contract test 동작**: registry 가 존재하는데 row 가 빠진 경우 = test FAIL(drift). registry 자체가 아직 생성 전인 현재 documented-only 단계에서는 D11 test 가 `planned`(비활성) — **registry 생성이 D11 test 활성화의 선행 조건**이며, registry 부재를 SKIP(통과)으로 처리하면 안 됨."},"proof_manifest":null,"rationale":"A12 (cursor token invariants, cursor pagination) and A27 (dependency on error-codes.yaml, status-mapping) address unrelated concerns; compatible.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-C8D5490FCD117127064D","evidence_a":{"line_end":478,"line_start":478,"quote":"| sync bulk endpoint 가 atomic 인지 + async bulk endpoint 가 LRO polling pattern 인지 (D23 sync/async 분기) | sync 에서 partial failure 허용은 AIP233-C7 위반. controller 작성자가 sync/async 구분 없이 partial failure 응답하거나 flat array body 채택 가능성 | sync batch contract test: `POST /v1/worklogs:batchCreate` 의 한 항목 fail 시 전체 rollback + HTTP 4xx + envelope.success=false. async batch contract test: `POST /v1/operations:batchCreate` 의 202 + Location + polling endpoint 의 `data.result.results[]` 가 항목별 success/error 매핑. BATCH_PARTIAL_FAILURE 가 sync 응답에 나타나면 실패. 1001 항목 size cap 위반 → 400 | `needs-confirmation` (boundary branch B14 BulkEnvelope.partial + D17 LRO endpoint pattern cross-link) |"},"evidence_b":{"line_end":468,"line_start":468,"quote":"| LRO endpoint 가 202 + `Location` + envelope `data.{operationId,statusUrl}` 형식인지 (D17) | 현재 ca-skeleton 에 LRO endpoint 자체가 없음 — sample-portfolio fixture 신설 필요 | sample-portfolio 에 `POST /v1/worklogs:export` 같은 LRO fixture 추가 후 응답 검증 | `needs-confirmation` (sample-portfolio fixture 확장 필요) |"},"proof_manifest":null,"rationale":"A13 (D23) states async bulk uses the LRO polling pattern; A14 (D17) defines the LRO endpoint shape (202 + Location + data.{operationId,statusUrl}). A14 supplies the concrete shape A13 refers to; compatible detail.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-7FA7AC0DB5CFEF782F7C","evidence_a":{"line_end":478,"line_start":478,"quote":"| sync bulk endpoint 가 atomic 인지 + async bulk endpoint 가 LRO polling pattern 인지 (D23 sync/async 분기) | sync 에서 partial failure 허용은 AIP233-C7 위반. controller 작성자가 sync/async 구분 없이 partial failure 응답하거나 flat array body 채택 가능성 | sync batch contract test: `POST /v1/worklogs:batchCreate` 의 한 항목 fail 시 전체 rollback + HTTP 4xx + envelope.success=false. async batch contract test: `POST /v1/operations:batchCreate` 의 202 + Location + polling endpoint 의 `data.result.results[]` 가 항목별 success/error 매핑. BATCH_PARTIAL_FAILURE 가 sync 응답에 나타나면 실패. 1001 항목 size cap 위반 → 400 | `needs-confirmation` (boundary branch B14 BulkEnvelope.partial + D17 LRO endpoint pattern cross-link) |"},"evidence_b":{"line_end":346,"line_start":346,"quote":"| bulk operation URL (D23) | AIP-136 colon-verb `POST /v1/{resource}:batchCreate` · request body `{ requests: [...] }` · **sync batch** = atomic MUST (한 항목 실패 → 전체 rollback + HTTP 4xx + envelope.success=false) · **async batch** = 202 + Location → polling endpoint 의 `data.result.results[]` 에 항목별 success/error (BATCH_PARTIAL_FAILURE 는 async polling 에서만) | async batch 의 polling endpoint 가 D17 LRO pattern 따름 + BATCH_PARTIAL_FAILURE category 적용 | sync batch 에서 partial failure 허용 (AIP233-C7 위반) · flat array body · kebab subpath · sync batch 의 atomic rollback 누락 | bulk endpoint contract test (sync: atomic rollback 검증 · async: 202+polling+partial result 검증) | sync batch 가 partial success 응답 · BATCH_PARTIAL_FAILURE 가 sync 응답에 사용됨 |"},"proof_manifest":null,"rationale":"Scope bulk operation, predicate has_failure_behavior: A35 (D23 decision) states sync batch is atomic MUST with async = 202+polling; A13 is the planned verification of that same atomic/LRO split. Same value; decision + its verification.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-791BDC8E8B1537E99FD2","evidence_a":{"line_end":264,"line_start":264,"quote":"| D2 | API versioning 기본값 `/v1` URI prefix + `X-Api-Version` 은 supplemental | `raw/official-docs/google-aip-185-resource-versioning.md#AIP185-C1` (major version 노출 의무), `#AIP185-C2` (minor/patch 노출 금지 — `/v1.0` 금지 → `/v1`), `#AIP185-C3` (새 major 는 이전 major 에 의존 금지) | `official-reference` (Google AIP — Google 사내 community guideline; IETF/W3C 표준 아님) | AIP-185 본문은 protobuf 컨텍스트 — REST URI path vs header 선택 자체에 대한 normative 진술은 본 인용 범위 밖. `X-Api-Version` 을 supplemental 로 두는 결정의 직접 근거는 별도 (예: ca-tmpl internal design) — AIP-185 는 path 형식 (`/v1`) 만 corroborate. **UNSUPPORTED_IMPL_DECISION**: path version 과 `X-Api-Version` 충돌 시 *path 우선* 규칙은 project-internal convention — AIP-185 에 path/header 우선순위 normative 진술 없음. trade-off: URI 가 1급 계약 표면(캐시·라우팅·로그에서 가시)이므로 path 를 권위 source 로, header 는 실험/전환 보조로 둠 |"},"evidence_b":{"line_end":286,"line_start":286,"quote":"| D18 | Pagination — `page` 0-indexed (Spring `Pageable` 정합), `size` default 20 / max 100 / min 1 · `size > 100` 또는 `size < 1` 또는 `page < 0` 은 400 VALIDATION_FAILED · 빈 list 는 `data: []` (절대 `null` 아님) + `meta.page.total = 0` · 깊은 offset (`page > 10000`) 은 `Deprecation` 헤더 + cursor 권고 | `raw/official-docs/spring-data-pageable-defaults.md#SPRING-PAGE-C1` (`page` 파라미터 0-indexed, default 0), `#SPRING-PAGE-C2` (`size` 파라미터 default 20), `#SPRING-PAGE-C3` (Spring Data 레포지토리 infrastructure 의 `Pageable` 은 zero-indexed), `#SPRING-PAGE-C4` (`PageableHandlerMethodArgumentResolverSupport.DEFAULT_MAX_PAGE_SIZE = 2000` — Spring 기본 max 가 2000 이므로 project 의 100 cap 은 별도 opt-in override 임을 정당화), `#SPRING-PAGE-C5` (annotation 없는 fallback = `PageRequest.of(0, 20)`), `#SPRING-PAGE-C6` (`setOneIndexedParameters` default false → page 0 = first page). 추가 normative 근거: `raw/official-docs/google-aip-158-pagination.md#AIP158-C1` (collection pagination 처음부터 제공 필수, 나중 추가 = backwards-incompatible), `#AIP158-C2` (page_size server-side cap SHOULD coerce down; max 숫자는 server-defined — 100 은 project-internal), `#AIP158-C3` (page_token MUST NOT required; subsequent page_size 변경 MUST honor), `#AIP158-C4` (next_page_token empty = EoC MUST, 유일한 EoC 시그널), `#AIP158-C5` (page token opaque + URL-safe MUST; base64 단독 불충분). JSON:API JSONAPI-PAGE-C1~C6 모두 size cap 또는 index base 의 normative 진술 없음 — JSONAPI-PAGE-C6 가 pagination strategy 에 agnostic 임을 명시. `size` max 100 cap 및 min 1 / `page < 0` 400 처리는 project-internal DoS prevention trade-off (UNSUPPORTED_IMPL_DECISION 잔존 — Spring 기본값 이하 추가 제한) | `official-vendor-doc` (Spring Data Commons — SPRING-PAGE-C1~C6) + `official-reference` (AIP-158 — AIP158-C1~C5) + UNSUPPORTED_IMPL_DECISION (`size` 100 cap / min 1 / 깊은 offset threshold 10000 은 project-internal 숫자; size>100 을 AIP-158 권고 coerce 대신 400-reject 강화도 project-internal) | 가장 critical footgun 결정 — `size=10000000` DoS 방지 + 0/1-indexed Spring 정합. Spring default max 는 2000 이지 Integer.MAX_VALUE 가 아님 (C4 보정). 깊은 offset 의 cursor 권고는 본 branch 가 박지만 cursor endpoint 의 shape (opaque token encoding/TTL) 결정은 future B16 — sibling 또는 후속 작업 |"},"proof_manifest":null,"rationale":"A15 (D2 versioning /v1) and A21 (D18 pagination thresholds) are orthogonal contract concerns of the same branch; each contributes a distinct compatible part without overlap.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-1C42238FF1EE3811DE1C","evidence_a":{"line_end":264,"line_start":264,"quote":"| D2 | API versioning 기본값 `/v1` URI prefix + `X-Api-Version` 은 supplemental | `raw/official-docs/google-aip-185-resource-versioning.md#AIP185-C1` (major version 노출 의무), `#AIP185-C2` (minor/patch 노출 금지 — `/v1.0` 금지 → `/v1`), `#AIP185-C3` (새 major 는 이전 major 에 의존 금지) | `official-reference` (Google AIP — Google 사내 community guideline; IETF/W3C 표준 아님) | AIP-185 본문은 protobuf 컨텍스트 — REST URI path vs header 선택 자체에 대한 normative 진술은 본 인용 범위 밖. `X-Api-Version` 을 supplemental 로 두는 결정의 직접 근거는 별도 (예: ca-tmpl internal design) — AIP-185 는 path 형식 (`/v1`) 만 corroborate. **UNSUPPORTED_IMPL_DECISION**: path version 과 `X-Api-Version` 충돌 시 *path 우선* 규칙은 project-internal convention — AIP-185 에 path/header 우선순위 normative 진술 없음. trade-off: URI 가 1급 계약 표면(캐시·라우팅·로그에서 가시)이므로 path 를 권위 source 로, header 는 실험/전환 보조로 둠 |"},"evidence_b":{"line_end":284,"line_start":284,"quote":"| D22 | Cursor pagination shape = opaque base64-encoded JSON token + HMAC signature + 24h TTL · cursor endpoint 는 별도 query param 또는 별도 path · client 의 token parse 금지 (opacity 강제) · cursor + `?page=N` 동시 사용 금지 | `raw/official-docs/google-aip-158-pagination.md#AIP158-C3` (page_token MUST NOT required + subsequent page_size 변경 MUST honor), `#AIP158-C5` (page token opaque + URL-safe MUST; base64 단독 불충분 → HMAC signature 추가 정당화) | `official-reference` (Google AIP-158 — opacity/URL-safe MUST normative) + UNSUPPORTED_IMPL_DECISION (24h TTL + HMAC algorithm 선택 + JSON shape 자체는 project-internal trade-off) | TTL 24h 의 정확한 숫자는 AIP-158 에 없음 (project-internal — 짧으면 long-running export 깨짐, 길면 DB layout 변경 시 stale cursor 문제). HMAC key rotation 정책은 별도 결정 (security branch 와 cross-link 필요). cursor endpoint URL 패턴 (`/v1/worklogs:listByCursor` colon-verb vs `?cursor=` query param) 미정 — 후속 D23 colon-verb 채택과 정합성 위해 colon-verb 권고 가능 (future revision) |"},"proof_manifest":null,"rationale":"A15 (D2 versioning /v1) and A22 (D22 cursor pagination shape) are orthogonal concerns; compatible, non-overlapping contract facets.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-0C58BB5841DB01109839","evidence_a":{"line_end":264,"line_start":264,"quote":"| D2 | API versioning 기본값 `/v1` URI prefix + `X-Api-Version` 은 supplemental | `raw/official-docs/google-aip-185-resource-versioning.md#AIP185-C1` (major version 노출 의무), `#AIP185-C2` (minor/patch 노출 금지 — `/v1.0` 금지 → `/v1`), `#AIP185-C3` (새 major 는 이전 major 에 의존 금지) | `official-reference` (Google AIP — Google 사내 community guideline; IETF/W3C 표준 아님) | AIP-185 본문은 protobuf 컨텍스트 — REST URI path vs header 선택 자체에 대한 normative 진술은 본 인용 범위 밖. `X-Api-Version` 을 supplemental 로 두는 결정의 직접 근거는 별도 (예: ca-tmpl internal design) — AIP-185 는 path 형식 (`/v1`) 만 corroborate. **UNSUPPORTED_IMPL_DECISION**: path version 과 `X-Api-Version` 충돌 시 *path 우선* 규칙은 project-internal convention — AIP-185 에 path/header 우선순위 normative 진술 없음. trade-off: URI 가 1급 계약 표면(캐시·라우팅·로그에서 가시)이므로 path 를 권위 source 로, header 는 실험/전환 보조로 둠 |"},"evidence_b":{"line_end":328,"line_start":328,"quote":"| versioning | `/v1` URI prefix | `X-Api-Version` supplemental header | media-type/header/path version 혼용 | OpenAPI path version test | version 없는 public endpoint |"},"proof_manifest":null,"rationale":"Same subject (API versioning), predicate (uses), scope (API versioning): A15 (D2 Decision Evidence Map) and A30 (D2 Decisionized Work Item) both assign /v1 URI prefix as default with X-Api-Version as supplemental. Identical value across two surfaces; no drift.","verdict":"CONSISTENT"},{"candidate_id":"SEM-89145C09640A0FEC16CD","evidence_a":{"line_end":265,"line_start":265,"quote":"| D3 | idempotency header 이름 `Idempotency-Key` 채택 (POST endpoint 표준 surface) | `raw/official-docs/idempotency-stripe-api-ref.md#STRIPE-IDEMP-C1`, `raw/official-docs/idempotency-ietf-draft.md#IETF-IDEMP-C1`, `raw/company-tech-blogs/idempotency-toss-payments-techblog.md#TOSS-IDEMP-C1` | `official-vendor-doc + official-reference + company-case-study` | IETF-IDEMP 는 draft 상태 (정식 RFC 아님); TOSS 는 company-case-study — 표준 lock-in 아님. PayPal 은 다른 header (`PayPal-Request-Id`) 사용 — 호환성은 별도 |"},"evidence_b":{"line_end":437,"line_start":437,"quote":" - [[raw/branch-notes/feature-rate-limit-idempotency-contract]] 에 의존(D3/D4) — `Idempotency-Key` key shape/scope/replay semantics owner. 본 branch 는 header *이름* 만."},"proof_manifest":null,"rationale":"A16 (D3: this branch owns Idempotency-Key header name) and A28 (this branch delegates key shape/scope/replay semantics to rate-limit branch) are the two compatible halves of the idempotency split — name owned here, semantics owned elsewhere.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-AAA94801A40CE9D0E637","evidence_a":{"line_end":265,"line_start":265,"quote":"| D3 | idempotency header 이름 `Idempotency-Key` 채택 (POST endpoint 표준 surface) | `raw/official-docs/idempotency-stripe-api-ref.md#STRIPE-IDEMP-C1`, `raw/official-docs/idempotency-ietf-draft.md#IETF-IDEMP-C1`, `raw/company-tech-blogs/idempotency-toss-payments-techblog.md#TOSS-IDEMP-C1` | `official-vendor-doc + official-reference + company-case-study` | IETF-IDEMP 는 draft 상태 (정식 RFC 아님); TOSS 는 company-case-study — 표준 lock-in 아님. PayPal 은 다른 header (`PayPal-Request-Id`) 사용 — 호환성은 별도 |"},"evidence_b":{"line_end":392,"line_start":392,"quote":"| `Idempotency-Key` header *이름* (D3) | **owner** (header name 만) | **owner** of key shape/scope/replay: `feature-rate-limit-idempotency-contract` | 본 branch → rate-limit branch (header name produces, key shape consumes) |"},"proof_manifest":null,"rationale":"Same subject (이 branch), predicate (owns), scope (idempotency header name): A16 (D3) and A36 (D3 cross-branch role) both assert this branch owns the Idempotency-Key header name only. Identical ownership value; no conflict.","verdict":"CONSISTENT"},{"candidate_id":"SEM-4E89119752E43CB78BE5","evidence_a":{"line_end":266,"line_start":266,"quote":"| D4 | key scope / replay semantics SSOT 는 `feature-rate-limit-idempotency-contract` 로 위임 (이 branch 는 header 이름만 결정) | UNSUPPORTED_DECISION (project-internal SSOT 분할 결정; 외부 표준 근거 없음) | N/A | sibling branch 와의 결정 정합성은 cross-branch review 로 확보 필요 |"},"evidence_b":{"line_end":267,"line_start":267,"quote":"| D5 | OpenAPI drift release-blocking 집행은 `feature-contract-verification-test-suite` 가 owner; 이 branch 는 producer | UNSUPPORTED_DECISION (project-internal owner 분할 결정) | N/A | producer-owner contract 가 깨지면 drift 가 untracked — verification suite 의 입력 spec 정합성 추적 필요 |"},"proof_manifest":null,"rationale":"A17 (D4: delegate idempotency key scope/replay SSOT to rate-limit branch) and A18 (D5: verification suite owns OpenAPI drift enforcement, this branch producer) concern unrelated objects (idempotency semantics vs drift gate); compatible.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-E8B5BBE048CC97D78EB3","evidence_a":{"line_end":266,"line_start":266,"quote":"| D4 | key scope / replay semantics SSOT 는 `feature-rate-limit-idempotency-contract` 로 위임 (이 branch 는 header 이름만 결정) | UNSUPPORTED_DECISION (project-internal SSOT 분할 결정; 외부 표준 근거 없음) | N/A | sibling branch 와의 결정 정합성은 cross-branch review 로 확보 필요 |"},"evidence_b":{"line_end":273,"line_start":273,"quote":"| D11 | HTTP status code ↔ envelope `error.category`/`error.code` 매핑 SSOT 는 `error-codes.yaml` (foundation branch 소유 registry §21 row 49 + `http_status` column). 본 branch 는 mapping consistency contract test 의 producer | UNSUPPORTED_DECISION (project-internal SSOT 위치 결정; 외부 표준 직접 근거 없음 — RFC 9110 §15.x 가 *개별 status code* 의미를 정의할 뿐 *registry 형식의 SSOT* 자체는 표준 영역 밖); 보강: 개별 row 의 매핑 (예: 404 ↔ `RESOURCE_NOT_FOUND`) 의 normative 근거는 RFC 9110 §15.x 각 status section — 본 branch 의 contract test 가 *registry 와 응답의 drift* 만 검증, *registry 자체의 row 정합성* 은 foundation branch 책임 | N/A | mapping SSOT 가 registry 인 것 자체는 project-internal 결정. RFC 9110 §15.5.6 (405), §15.5.13 (412), §15.5.15 (414) 같은 다른 status code section 의 raw 발췌가 *다음 세션* 작업 — 이후 각 row 의 Supporting Claim ID 보강 가능. cross-branch: registry row 의 *추가/수정* 은 §21 변경 절차 (registry-governance branch) 적용 |"},"proof_manifest":null,"rationale":"A17 (D4: delegate idempotency semantics to rate-limit branch) and A19 (D11: foundation owns status-mapping registry) concern different delegated objects; compatible, no overlap.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-C965B2736081EFA65DF5","evidence_a":{"line_end":266,"line_start":266,"quote":"| D4 | key scope / replay semantics SSOT 는 `feature-rate-limit-idempotency-contract` 로 위임 (이 branch 는 header 이름만 결정) | UNSUPPORTED_DECISION (project-internal SSOT 분할 결정; 외부 표준 근거 없음) | N/A | sibling branch 와의 결정 정합성은 cross-branch review 로 확보 필요 |"},"evidence_b":{"line_end":392,"line_start":392,"quote":"| `Idempotency-Key` header *이름* (D3) | **owner** (header name 만) | **owner** of key shape/scope/replay: `feature-rate-limit-idempotency-contract` | 본 branch → rate-limit branch (header name produces, key shape consumes) |"},"proof_manifest":null,"rationale":"A17 delegates idempotency key scope/replay semantics to the rate-limit branch while A36 asserts this branch owns the header name only. Compatible split — name here, semantics elsewhere.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-22FE36C61D9DF359A806","evidence_a":{"line_end":267,"line_start":267,"quote":"| D5 | OpenAPI drift release-blocking 집행은 `feature-contract-verification-test-suite` 가 owner; 이 branch 는 producer | UNSUPPORTED_DECISION (project-internal owner 분할 결정) | N/A | producer-owner contract 가 깨지면 drift 가 untracked — verification suite 의 입력 spec 정합성 추적 필요 |"},"evidence_b":{"line_end":273,"line_start":273,"quote":"| D11 | HTTP status code ↔ envelope `error.category`/`error.code` 매핑 SSOT 는 `error-codes.yaml` (foundation branch 소유 registry §21 row 49 + `http_status` column). 본 branch 는 mapping consistency contract test 의 producer | UNSUPPORTED_DECISION (project-internal SSOT 위치 결정; 외부 표준 직접 근거 없음 — RFC 9110 §15.x 가 *개별 status code* 의미를 정의할 뿐 *registry 형식의 SSOT* 자체는 표준 영역 밖); 보강: 개별 row 의 매핑 (예: 404 ↔ `RESOURCE_NOT_FOUND`) 의 normative 근거는 RFC 9110 §15.x 각 status section — 본 branch 의 contract test 가 *registry 와 응답의 drift* 만 검증, *registry 자체의 row 정합성* 은 foundation branch 책임 | N/A | mapping SSOT 가 registry 인 것 자체는 project-internal 결정. RFC 9110 §15.5.6 (405), §15.5.13 (412), §15.5.15 (414) 같은 다른 status code section 의 raw 발췌가 *다음 세션* 작업 — 이후 각 row 의 Supporting Claim ID 보강 가능. cross-branch: registry row 의 *추가/수정* 은 §21 변경 절차 (registry-governance branch) 적용 |"},"proof_manifest":null,"rationale":"Both use predicate owns but on different objects: A18 (verification-test-suite owns OpenAPI drift enforcement) vs A19 (foundation branch owns status-mapping SSOT). Distinct scopes (drift gate vs status registry); no competing claim over one object.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-0DF4FFB3D2BF42CF8F4E","evidence_a":{"line_end":267,"line_start":267,"quote":"| D5 | OpenAPI drift release-blocking 집행은 `feature-contract-verification-test-suite` 가 owner; 이 branch 는 producer | UNSUPPORTED_DECISION (project-internal owner 분할 결정) | N/A | producer-owner contract 가 깨지면 drift 가 untracked — verification suite 의 입력 spec 정합성 추적 필요 |"},"evidence_b":{"line_end":394,"line_start":394,"quote":"| OpenAPI snapshot 생성 (D10) | **producer** | **owner** of drift gate: `feature-contract-verification-test-suite` | 본 branch → verification suite |"},"proof_manifest":null,"rationale":"Scope OpenAPI drift release gate: A18 (D5) states verification-test-suite owns the drift gate; A38 (D10) states this branch produces the OpenAPI snapshot feeding it. Owner + producer split; consistent roles.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-77325803A5B73AB9B882","evidence_a":{"line_end":273,"line_start":273,"quote":"| D11 | HTTP status code ↔ envelope `error.category`/`error.code` 매핑 SSOT 는 `error-codes.yaml` (foundation branch 소유 registry §21 row 49 + `http_status` column). 본 branch 는 mapping consistency contract test 의 producer | UNSUPPORTED_DECISION (project-internal SSOT 위치 결정; 외부 표준 직접 근거 없음 — RFC 9110 §15.x 가 *개별 status code* 의미를 정의할 뿐 *registry 형식의 SSOT* 자체는 표준 영역 밖); 보강: 개별 row 의 매핑 (예: 404 ↔ `RESOURCE_NOT_FOUND`) 의 normative 근거는 RFC 9110 §15.x 각 status section — 본 branch 의 contract test 가 *registry 와 응답의 drift* 만 검증, *registry 자체의 row 정합성* 은 foundation branch 책임 | N/A | mapping SSOT 가 registry 인 것 자체는 project-internal 결정. RFC 9110 §15.5.6 (405), §15.5.13 (412), §15.5.15 (414) 같은 다른 status code section 의 raw 발췌가 *다음 세션* 작업 — 이후 각 row 의 Supporting Claim ID 보강 가능. cross-branch: registry row 의 *추가/수정* 은 §21 변경 절차 (registry-governance branch) 적용 |"},"evidence_b":{"line_end":435,"line_start":435,"quote":" - [[raw/branch-notes/feature-operational-error-observability-foundation]] 의 `error-codes.yaml`(D11) + envelope schema 에 의존 — registry `http_status` column 이 status 매핑 SSOT. **(엣지) `error-codes.yaml` 미존재 또는 row 누락 시 D11 mapping consistency contract test 동작**: registry 가 존재하는데 row 가 빠진 경우 = test FAIL(drift). registry 자체가 아직 생성 전인 현재 documented-only 단계에서는 D11 test 가 `planned`(비활성) — **registry 생성이 D11 test 활성화의 선행 조건**이며, registry 부재를 SKIP(통과)으로 처리하면 안 됨."},"proof_manifest":null,"rationale":"A19 (foundation branch owns status-mapping SSOT registry) and A27 (this branch consumes error-codes.yaml + envelope schema) are the owner + consumer halves of the same dependency; compatible.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-AD1B9C6B74D4B73D580B","evidence_a":{"line_end":273,"line_start":273,"quote":"| D11 | HTTP status code ↔ envelope `error.category`/`error.code` 매핑 SSOT 는 `error-codes.yaml` (foundation branch 소유 registry §21 row 49 + `http_status` column). 본 branch 는 mapping consistency contract test 의 producer | UNSUPPORTED_DECISION (project-internal SSOT 위치 결정; 외부 표준 직접 근거 없음 — RFC 9110 §15.x 가 *개별 status code* 의미를 정의할 뿐 *registry 형식의 SSOT* 자체는 표준 영역 밖); 보강: 개별 row 의 매핑 (예: 404 ↔ `RESOURCE_NOT_FOUND`) 의 normative 근거는 RFC 9110 §15.x 각 status section — 본 branch 의 contract test 가 *registry 와 응답의 drift* 만 검증, *registry 자체의 row 정합성* 은 foundation branch 책임 | N/A | mapping SSOT 가 registry 인 것 자체는 project-internal 결정. RFC 9110 §15.5.6 (405), §15.5.13 (412), §15.5.15 (414) 같은 다른 status code section 의 raw 발췌가 *다음 세션* 작업 — 이후 각 row 의 Supporting Claim ID 보강 가능. cross-branch: registry row 의 *추가/수정* 은 §21 변경 절차 (registry-governance branch) 적용 |"},"evidence_b":{"line_end":340,"line_start":340,"quote":"| HTTP status mapping SSOT | `error-codes.yaml` 의 `http_status` column 이 모든 매핑의 SSOT (registry §21, owner: foundation branch) | 본 branch 는 mapping consistency contract test 의 producer | controller 가 registry 와 다른 HTTP status 반환 | status mapping consistency test (모든 `error.code` row 에 대해 실제 응답의 HTTP status 가 registry 와 일치) | registry-controller drift |"},"proof_manifest":null,"rationale":"Same predicate (owns), scope (status mapping SSOT): A19 and A32 both assign ownership of the status-mapping SSOT to the foundation branch's error-codes.yaml (http_status column), with this branch as consistency-test producer. Identical ownership; no drift.","verdict":"CONSISTENT"},{"candidate_id":"SEM-85ACDFC548AC4BB59C1B","evidence_a":{"line_end":273,"line_start":273,"quote":"| D11 | HTTP status code ↔ envelope `error.category`/`error.code` 매핑 SSOT 는 `error-codes.yaml` (foundation branch 소유 registry §21 row 49 + `http_status` column). 본 branch 는 mapping consistency contract test 의 producer | UNSUPPORTED_DECISION (project-internal SSOT 위치 결정; 외부 표준 직접 근거 없음 — RFC 9110 §15.x 가 *개별 status code* 의미를 정의할 뿐 *registry 형식의 SSOT* 자체는 표준 영역 밖); 보강: 개별 row 의 매핑 (예: 404 ↔ `RESOURCE_NOT_FOUND`) 의 normative 근거는 RFC 9110 §15.x 각 status section — 본 branch 의 contract test 가 *registry 와 응답의 drift* 만 검증, *registry 자체의 row 정합성* 은 foundation branch 책임 | N/A | mapping SSOT 가 registry 인 것 자체는 project-internal 결정. RFC 9110 §15.5.6 (405), §15.5.13 (412), §15.5.15 (414) 같은 다른 status code section 의 raw 발췌가 *다음 세션* 작업 — 이후 각 row 의 Supporting Claim ID 보강 가능. cross-branch: registry row 의 *추가/수정* 은 §21 변경 절차 (registry-governance branch) 적용 |"},"evidence_b":{"line_end":340,"line_start":340,"quote":"| HTTP status mapping SSOT | `error-codes.yaml` 의 `http_status` column 이 모든 매핑의 SSOT (registry §21, owner: foundation branch) | 본 branch 는 mapping consistency contract test 의 producer | controller 가 registry 와 다른 HTTP status 반환 | status mapping consistency test (모든 `error.code` row 에 대해 실제 응답의 HTTP status 가 registry 와 일치) | registry-controller drift |"},"proof_manifest":null,"rationale":"A19 (foundation branch owns the SSOT registry) and A33 (this branch produces the mapping consistency contract test) are the owner + test-producer halves of the D11 split; compatible.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-5B2B1A1AD31A4E96DE4C","evidence_a":{"line_end":273,"line_start":273,"quote":"| D11 | HTTP status code ↔ envelope `error.category`/`error.code` 매핑 SSOT 는 `error-codes.yaml` (foundation branch 소유 registry §21 row 49 + `http_status` column). 본 branch 는 mapping consistency contract test 의 producer | UNSUPPORTED_DECISION (project-internal SSOT 위치 결정; 외부 표준 직접 근거 없음 — RFC 9110 §15.x 가 *개별 status code* 의미를 정의할 뿐 *registry 형식의 SSOT* 자체는 표준 영역 밖); 보강: 개별 row 의 매핑 (예: 404 ↔ `RESOURCE_NOT_FOUND`) 의 normative 근거는 RFC 9110 §15.x 각 status section — 본 branch 의 contract test 가 *registry 와 응답의 drift* 만 검증, *registry 자체의 row 정합성* 은 foundation branch 책임 | N/A | mapping SSOT 가 registry 인 것 자체는 project-internal 결정. RFC 9110 §15.5.6 (405), §15.5.13 (412), §15.5.15 (414) 같은 다른 status code section 의 raw 발췌가 *다음 세션* 작업 — 이후 각 row 의 Supporting Claim ID 보강 가능. cross-branch: registry row 의 *추가/수정* 은 §21 변경 절차 (registry-governance branch) 적용 |"},"evidence_b":{"line_end":390,"line_start":390,"quote":"| HTTP status ↔ envelope `error.code` 매핑 (D11) | **producer** of mapping consistency contract test | **owner** of registry: `feature-operational-error-observability-foundation` (`error-codes.yaml`) | 본 branch ← foundation branch |"},"proof_manifest":null,"rationale":"Scope status mapping SSOT, predicate owns: A19 (error-codes.yaml / foundation branch) and A37 (feature-operational-error-observability-foundation) name the same foundation-branch registry as owner, with this branch as consistency-test producer. Same owner; no conflict.","verdict":"CONSISTENT"},{"candidate_id":"SEM-425D7957F4556C50A21F","evidence_a":{"line_end":273,"line_start":273,"quote":"| D11 | HTTP status code ↔ envelope `error.category`/`error.code` 매핑 SSOT 는 `error-codes.yaml` (foundation branch 소유 registry §21 row 49 + `http_status` column). 본 branch 는 mapping consistency contract test 의 producer | UNSUPPORTED_DECISION (project-internal SSOT 위치 결정; 외부 표준 직접 근거 없음 — RFC 9110 §15.x 가 *개별 status code* 의미를 정의할 뿐 *registry 형식의 SSOT* 자체는 표준 영역 밖); 보강: 개별 row 의 매핑 (예: 404 ↔ `RESOURCE_NOT_FOUND`) 의 normative 근거는 RFC 9110 §15.x 각 status section — 본 branch 의 contract test 가 *registry 와 응답의 drift* 만 검증, *registry 자체의 row 정합성* 은 foundation branch 책임 | N/A | mapping SSOT 가 registry 인 것 자체는 project-internal 결정. RFC 9110 §15.5.6 (405), §15.5.13 (412), §15.5.15 (414) 같은 다른 status code section 의 raw 발췌가 *다음 세션* 작업 — 이후 각 row 의 Supporting Claim ID 보강 가능. cross-branch: registry row 의 *추가/수정* 은 §21 변경 절차 (registry-governance branch) 적용 |"},"evidence_b":{"line_end":95,"line_start":95,"quote":"- HTTP status code ↔ envelope `error.category`/`error.code` 의 전체 매핑 SSOT 위치 결정."},"proof_manifest":null,"rationale":"A43 (scope-in: this branch owns the decision of WHERE the mapping SSOT lives) and A19 (foundation branch owns the registry itself) describe distinct objects — the location meta-decision resolves TO the foundation registry. They coexist; the text establishes the resolution, so no ambiguity.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-C8CCC41AD0D772A7E1AA","evidence_a":{"line_end":276,"line_start":276,"quote":"| D14 | PATCH default media type = `application/json` (RFC 7396 `application/merge-patch+json` *미채택*). request shape = `JsonNullable<T>` / `Optional<T>` wrapper 로 **absent / null / value 3-상태** 구분 · ArchUnit rule SSOT 는 [[raw/branch-notes/feature-boundary-validation-mapping-contract]] B2 | `raw/official-docs/patch-json-merge-rfc7396.md#RFC7396-C3` (merge patch 는 explicit null 사용 모델에 부적합 — *미채택의 직접 근거*), `#RFC7396-C2` (대안: null=deletion normative — 본 branch *대안으로* 인용); [[raw/branch-notes/feature-boundary-validation-mapping-contract]] B2 (PATCH mapper SSOT, ArchUnit `no_merge_patch_json_media_type_string` enforced) | `official-standard` (RFC 7396 — 미채택 근거) + `cross-branch-SSOT` (boundary branch B2) | content type 정책은 본 branch 가 producer, mapper 구현은 boundary branch 책임. 클라이언트가 merge-patch semantics 가정하지 않도록 OpenAPI spec 에 명시. |"},"evidence_b":{"line_end":335,"line_start":335,"quote":"| method-PATCH | PATCH default media type = `application/json` · request shape = `JsonNullable<T>` / `Optional<T>` wrapper · absent/null/value 3-상태 구분 (boundary branch B2 SSOT) | 없음 — `application/merge-patch+json` (RFC 7396) / `application/json-patch+json` (RFC 6902) 모두 **금지** | `application/merge-patch+json` / `application/json-patch+json` content type 사용 · absent vs null 미구분 (`null` 이 absent 와 동일 의미로 처리됨) | `no_merge_patch_json_media_type_string` ArchUnit rule (boundary branch B2) + PATCH absent/null 3-상태 mapper contract test | PATCH endpoint 가 `application/merge-patch+json` content type 허용 · Java record canonical constructor 가 absent 와 null 을 같은 기본값으로 수렴 |"},"proof_manifest":null,"rationale":"Same subject (PATCH), predicate (uses), scope (PATCH media type): A20 (D14) and A31 (D14 method-PATCH) both set default media type = application/json (merge-patch+json unadopted) with JsonNullable/Optional 3-state wrapper. Identical value; no drift.","verdict":"CONSISTENT"},{"candidate_id":"SEM-357032D3F517EAA603B3","evidence_a":{"line_end":276,"line_start":276,"quote":"| D14 | PATCH default media type = `application/json` (RFC 7396 `application/merge-patch+json` *미채택*). request shape = `JsonNullable<T>` / `Optional<T>` wrapper 로 **absent / null / value 3-상태** 구분 · ArchUnit rule SSOT 는 [[raw/branch-notes/feature-boundary-validation-mapping-contract]] B2 | `raw/official-docs/patch-json-merge-rfc7396.md#RFC7396-C3` (merge patch 는 explicit null 사용 모델에 부적합 — *미채택의 직접 근거*), `#RFC7396-C2` (대안: null=deletion normative — 본 branch *대안으로* 인용); [[raw/branch-notes/feature-boundary-validation-mapping-contract]] B2 (PATCH mapper SSOT, ArchUnit `no_merge_patch_json_media_type_string` enforced) | `official-standard` (RFC 7396 — 미채택 근거) + `cross-branch-SSOT` (boundary branch B2) | content type 정책은 본 branch 가 producer, mapper 구현은 boundary branch 책임. 클라이언트가 merge-patch semantics 가정하지 않도록 OpenAPI spec 에 명시. |"},"evidence_b":{"line_end":396,"line_start":396,"quote":"| PATCH semantics (D14 정정 후) | **consumer** (content type 정책만 producer — `application/json` only) | **owner**: `feature-boundary-validation-mapping-contract` B2 (RFC 7396 미채택 + absent/null/value 3-상태 mapper + ArchUnit `no_merge_patch_json_media_type_string` enforced) | 본 branch ← boundary branch SSOT |"},"proof_manifest":null,"rationale":"A20 (this branch is producer of PATCH content-type policy = application/json) and A40 (PATCH mapper semantics owner = boundary branch B2) are the compatible content-type-vs-semantics split; no competing ownership of one object.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-AC3FA36B63468B61C261","evidence_a":{"line_end":286,"line_start":286,"quote":"| D18 | Pagination — `page` 0-indexed (Spring `Pageable` 정합), `size` default 20 / max 100 / min 1 · `size > 100` 또는 `size < 1` 또는 `page < 0` 은 400 VALIDATION_FAILED · 빈 list 는 `data: []` (절대 `null` 아님) + `meta.page.total = 0` · 깊은 offset (`page > 10000`) 은 `Deprecation` 헤더 + cursor 권고 | `raw/official-docs/spring-data-pageable-defaults.md#SPRING-PAGE-C1` (`page` 파라미터 0-indexed, default 0), `#SPRING-PAGE-C2` (`size` 파라미터 default 20), `#SPRING-PAGE-C3` (Spring Data 레포지토리 infrastructure 의 `Pageable` 은 zero-indexed), `#SPRING-PAGE-C4` (`PageableHandlerMethodArgumentResolverSupport.DEFAULT_MAX_PAGE_SIZE = 2000` — Spring 기본 max 가 2000 이므로 project 의 100 cap 은 별도 opt-in override 임을 정당화), `#SPRING-PAGE-C5` (annotation 없는 fallback = `PageRequest.of(0, 20)`), `#SPRING-PAGE-C6` (`setOneIndexedParameters` default false → page 0 = first page). 추가 normative 근거: `raw/official-docs/google-aip-158-pagination.md#AIP158-C1` (collection pagination 처음부터 제공 필수, 나중 추가 = backwards-incompatible), `#AIP158-C2` (page_size server-side cap SHOULD coerce down; max 숫자는 server-defined — 100 은 project-internal), `#AIP158-C3` (page_token MUST NOT required; subsequent page_size 변경 MUST honor), `#AIP158-C4` (next_page_token empty = EoC MUST, 유일한 EoC 시그널), `#AIP158-C5` (page token opaque + URL-safe MUST; base64 단독 불충분). JSON:API JSONAPI-PAGE-C1~C6 모두 size cap 또는 index base 의 normative 진술 없음 — JSONAPI-PAGE-C6 가 pagination strategy 에 agnostic 임을 명시. `size` max 100 cap 및 min 1 / `page < 0` 400 처리는 project-internal DoS prevention trade-off (UNSUPPORTED_IMPL_DECISION 잔존 — Spring 기본값 이하 추가 제한) | `official-vendor-doc` (Spring Data Commons — SPRING-PAGE-C1~C6) + `official-reference` (AIP-158 — AIP158-C1~C5) + UNSUPPORTED_IMPL_DECISION (`size` 100 cap / min 1 / 깊은 offset threshold 10000 은 project-internal 숫자; size>100 을 AIP-158 권고 coerce 대신 400-reject 강화도 project-internal) | 가장 critical footgun 결정 — `size=10000000` DoS 방지 + 0/1-indexed Spring 정합. Spring default max 는 2000 이지 Integer.MAX_VALUE 가 아님 (C4 보정). 깊은 offset 의 cursor 권고는 본 branch 가 박지만 cursor endpoint 의 shape (opaque token encoding/TTL) 결정은 future B16 — sibling 또는 후속 작업 |"},"evidence_b":{"line_end":284,"line_start":284,"quote":"| D22 | Cursor pagination shape = opaque base64-encoded JSON token + HMAC signature + 24h TTL · cursor endpoint 는 별도 query param 또는 별도 path · client 의 token parse 금지 (opacity 강제) · cursor + `?page=N` 동시 사용 금지 | `raw/official-docs/google-aip-158-pagination.md#AIP158-C3` (page_token MUST NOT required + subsequent page_size 변경 MUST honor), `#AIP158-C5` (page token opaque + URL-safe MUST; base64 단독 불충분 → HMAC signature 추가 정당화) | `official-reference` (Google AIP-158 — opacity/URL-safe MUST normative) + UNSUPPORTED_IMPL_DECISION (24h TTL + HMAC algorithm 선택 + JSON shape 자체는 project-internal trade-off) | TTL 24h 의 정확한 숫자는 AIP-158 에 없음 (project-internal — 짧으면 long-running export 깨짐, 길면 DB layout 변경 시 stale cursor 문제). HMAC key rotation 정책은 별도 결정 (security branch 와 cross-link 필요). cursor endpoint URL 패턴 (`/v1/worklogs:listByCursor` colon-verb vs `?cursor=` query param) 미정 — 후속 D23 colon-verb 채택과 정합성 위해 colon-verb 권고 가능 (future revision) |"},"proof_manifest":null,"rationale":"A21 (D18 offset pagination thresholds) and A22 (D22 cursor pagination shape) are related but distinct pagination facets; A21 size cap even applies to cursor per A22. Compatible, non-competing details.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-DF6B320EF94D40586D78","evidence_a":{"line_end":286,"line_start":286,"quote":"| D18 | Pagination — `page` 0-indexed (Spring `Pageable` 정합), `size` default 20 / max 100 / min 1 · `size > 100` 또는 `size < 1` 또는 `page < 0` 은 400 VALIDATION_FAILED · 빈 list 는 `data: []` (절대 `null` 아님) + `meta.page.total = 0` · 깊은 offset (`page > 10000`) 은 `Deprecation` 헤더 + cursor 권고 | `raw/official-docs/spring-data-pageable-defaults.md#SPRING-PAGE-C1` (`page` 파라미터 0-indexed, default 0), `#SPRING-PAGE-C2` (`size` 파라미터 default 20), `#SPRING-PAGE-C3` (Spring Data 레포지토리 infrastructure 의 `Pageable` 은 zero-indexed), `#SPRING-PAGE-C4` (`PageableHandlerMethodArgumentResolverSupport.DEFAULT_MAX_PAGE_SIZE = 2000` — Spring 기본 max 가 2000 이므로 project 의 100 cap 은 별도 opt-in override 임을 정당화), `#SPRING-PAGE-C5` (annotation 없는 fallback = `PageRequest.of(0, 20)`), `#SPRING-PAGE-C6` (`setOneIndexedParameters` default false → page 0 = first page). 추가 normative 근거: `raw/official-docs/google-aip-158-pagination.md#AIP158-C1` (collection pagination 처음부터 제공 필수, 나중 추가 = backwards-incompatible), `#AIP158-C2` (page_size server-side cap SHOULD coerce down; max 숫자는 server-defined — 100 은 project-internal), `#AIP158-C3` (page_token MUST NOT required; subsequent page_size 변경 MUST honor), `#AIP158-C4` (next_page_token empty = EoC MUST, 유일한 EoC 시그널), `#AIP158-C5` (page token opaque + URL-safe MUST; base64 단독 불충분). JSON:API JSONAPI-PAGE-C1~C6 모두 size cap 또는 index base 의 normative 진술 없음 — JSONAPI-PAGE-C6 가 pagination strategy 에 agnostic 임을 명시. `size` max 100 cap 및 min 1 / `page < 0` 400 처리는 project-internal DoS prevention trade-off (UNSUPPORTED_IMPL_DECISION 잔존 — Spring 기본값 이하 추가 제한) | `official-vendor-doc` (Spring Data Commons — SPRING-PAGE-C1~C6) + `official-reference` (AIP-158 — AIP158-C1~C5) + UNSUPPORTED_IMPL_DECISION (`size` 100 cap / min 1 / 깊은 offset threshold 10000 은 project-internal 숫자; size>100 을 AIP-158 권고 coerce 대신 400-reject 강화도 project-internal) | 가장 critical footgun 결정 — `size=10000000` DoS 방지 + 0/1-indexed Spring 정합. Spring default max 는 2000 이지 Integer.MAX_VALUE 가 아님 (C4 보정). 깊은 offset 의 cursor 권고는 본 branch 가 박지만 cursor endpoint 의 shape (opaque token encoding/TTL) 결정은 future B16 — sibling 또는 후속 작업 |"},"evidence_b":{"line_end":428,"line_start":428,"quote":" - **pagination footgun** — `size > 100` / `size < 1` / `page < 0` 통과하거나 빈 list 가 `data: null` 이면 실패(`data: []` + `meta.page.total=0`). Spring default max 가 2000(≠Integer.MAX_VALUE)이라 project cap 100 은 별도 opt-in override. (D18 — SPRING-PAGE-C4)"},"proof_manifest":null,"rationale":"Scope pagination, predicate has_threshold: A21 (D18) and A25 (D18 failure path, SPRING-PAGE-C4) both set the project size cap = 100 and both note Spring's DEFAULT_MAX_PAGE_SIZE = 2000 as a separate opt-in override (not Integer.MAX_VALUE). Identical cap value; agreement.","verdict":"CONSISTENT"},{"candidate_id":"SEM-0C34F9C6000C3EC0B6CA","evidence_a":{"line_end":286,"line_start":286,"quote":"| D18 | Pagination — `page` 0-indexed (Spring `Pageable` 정합), `size` default 20 / max 100 / min 1 · `size > 100` 또는 `size < 1` 또는 `page < 0` 은 400 VALIDATION_FAILED · 빈 list 는 `data: []` (절대 `null` 아님) + `meta.page.total = 0` · 깊은 offset (`page > 10000`) 은 `Deprecation` 헤더 + cursor 권고 | `raw/official-docs/spring-data-pageable-defaults.md#SPRING-PAGE-C1` (`page` 파라미터 0-indexed, default 0), `#SPRING-PAGE-C2` (`size` 파라미터 default 20), `#SPRING-PAGE-C3` (Spring Data 레포지토리 infrastructure 의 `Pageable` 은 zero-indexed), `#SPRING-PAGE-C4` (`PageableHandlerMethodArgumentResolverSupport.DEFAULT_MAX_PAGE_SIZE = 2000` — Spring 기본 max 가 2000 이므로 project 의 100 cap 은 별도 opt-in override 임을 정당화), `#SPRING-PAGE-C5` (annotation 없는 fallback = `PageRequest.of(0, 20)`), `#SPRING-PAGE-C6` (`setOneIndexedParameters` default false → page 0 = first page). 추가 normative 근거: `raw/official-docs/google-aip-158-pagination.md#AIP158-C1` (collection pagination 처음부터 제공 필수, 나중 추가 = backwards-incompatible), `#AIP158-C2` (page_size server-side cap SHOULD coerce down; max 숫자는 server-defined — 100 은 project-internal), `#AIP158-C3` (page_token MUST NOT required; subsequent page_size 변경 MUST honor), `#AIP158-C4` (next_page_token empty = EoC MUST, 유일한 EoC 시그널), `#AIP158-C5` (page token opaque + URL-safe MUST; base64 단독 불충분). JSON:API JSONAPI-PAGE-C1~C6 모두 size cap 또는 index base 의 normative 진술 없음 — JSONAPI-PAGE-C6 가 pagination strategy 에 agnostic 임을 명시. `size` max 100 cap 및 min 1 / `page < 0` 400 처리는 project-internal DoS prevention trade-off (UNSUPPORTED_IMPL_DECISION 잔존 — Spring 기본값 이하 추가 제한) | `official-vendor-doc` (Spring Data Commons — SPRING-PAGE-C1~C6) + `official-reference` (AIP-158 — AIP158-C1~C5) + UNSUPPORTED_IMPL_DECISION (`size` 100 cap / min 1 / 깊은 offset threshold 10000 은 project-internal 숫자; size>100 을 AIP-158 권고 coerce 대신 400-reject 강화도 project-internal) | 가장 critical footgun 결정 — `size=10000000` DoS 방지 + 0/1-indexed Spring 정합. Spring default max 는 2000 이지 Integer.MAX_VALUE 가 아님 (C4 보정). 깊은 offset 의 cursor 권고는 본 branch 가 박지만 cursor endpoint 의 shape (opaque token encoding/TTL) 결정은 future B16 — sibling 또는 후속 작업 |"},"evidence_b":{"line_end":335,"line_start":335,"quote":"| method-PATCH | PATCH default media type = `application/json` · request shape = `JsonNullable<T>` / `Optional<T>` wrapper · absent/null/value 3-상태 구분 (boundary branch B2 SSOT) | 없음 — `application/merge-patch+json` (RFC 7396) / `application/json-patch+json` (RFC 6902) 모두 **금지** | `application/merge-patch+json` / `application/json-patch+json` content type 사용 · absent vs null 미구분 (`null` 이 absent 와 동일 의미로 처리됨) | `no_merge_patch_json_media_type_string` ArchUnit rule (boundary branch B2) + PATCH absent/null 3-상태 mapper contract test | PATCH endpoint 가 `application/merge-patch+json` content type 허용 · Java record canonical constructor 가 absent 와 null 을 같은 기본값으로 수렴 |"},"proof_manifest":null,"rationale":"A21 (D18 pagination thresholds) and A31 (D14 PATCH media type) are orthogonal contract concerns; each a distinct compatible facet of the branch.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-C6313EA367AEBF9D9B3C","evidence_a":{"line_end":284,"line_start":284,"quote":"| D22 | Cursor pagination shape = opaque base64-encoded JSON token + HMAC signature + 24h TTL · cursor endpoint 는 별도 query param 또는 별도 path · client 의 token parse 금지 (opacity 강제) · cursor + `?page=N` 동시 사용 금지 | `raw/official-docs/google-aip-158-pagination.md#AIP158-C3` (page_token MUST NOT required + subsequent page_size 변경 MUST honor), `#AIP158-C5` (page token opaque + URL-safe MUST; base64 단독 불충분 → HMAC signature 추가 정당화) | `official-reference` (Google AIP-158 — opacity/URL-safe MUST normative) + UNSUPPORTED_IMPL_DECISION (24h TTL + HMAC algorithm 선택 + JSON shape 자체는 project-internal trade-off) | TTL 24h 의 정확한 숫자는 AIP-158 에 없음 (project-internal — 짧으면 long-running export 깨짐, 길면 DB layout 변경 시 stale cursor 문제). HMAC key rotation 정책은 별도 결정 (security branch 와 cross-link 필요). cursor endpoint URL 패턴 (`/v1/worklogs:listByCursor` colon-verb vs `?cursor=` query param) 미정 — 후속 D23 colon-verb 채택과 정합성 위해 colon-verb 권고 가능 (future revision) |"},"evidence_b":{"line_end":345,"line_start":345,"quote":"| cursor pagination shape (D22) | opaque base64-encoded JSON token + HMAC signature + 24h TTL · cursor endpoint 는 별도 query param (`?cursor=<token>&size=20`) 또는 별도 path | size cap (D18) 동일 적용 · token 만료 시 400 VALIDATION_FAILED + 권장: 첫 페이지 재요청 | typed cursor (last value 노출) · unsigned token (tamper risk) · cursor + `?page=N` 동시 사용 · TTL 무한 | cursor token roundtrip test + tamper detection test + TTL expiration test + opacity 검증 (client parse 가능하면 실패) | typed/unsigned/no-TTL token |"},"proof_manifest":null,"rationale":"Same subject (cursor pagination token), predicate (has_schema), scope (cursor pagination): A22 (D22) and A34 (D22 Decisionized Work Item) both specify opaque base64 JSON + HMAC signature + 24h TTL. Identical shape across two surfaces; no drift.","verdict":"CONSISTENT"},{"candidate_id":"SEM-327C1ACF7545EEFFEFD4","evidence_a":{"line_end":427,"line_start":427,"quote":" - **conditional request** — `If-Match` mismatch 를 409/500 으로 매핑하면 실패(→ 412), `If-None-Match` match 304 에 body 동봉하면 실패. (D15)"},"evidence_b":{"line_end":397,"line_start":397,"quote":"| Conditional request (`ETag`/`If-Match`/412/304) (D15) | **owner** (HTTP layer) | **consumer**: sample-portfolio fixture (`WorkLogVersion` 이 ETag derivation 의 source) | 본 branch → sample-portfolio fixture |"},"proof_manifest":null,"rationale":"Scope conditional request: A39 (D15 cross-branch role) states this branch owns the ETag/If-Match/412/304 HTTP layer; A24 (D15 failure path) specifies If-Match mismatch → 412 (409/500 forbidden). Ownership + the specific behavior it governs; consistent, complementary.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-53347448D33D5199A94A","evidence_a":{"line_end":435,"line_start":435,"quote":" - [[raw/branch-notes/feature-operational-error-observability-foundation]] 의 `error-codes.yaml`(D11) + envelope schema 에 의존 — registry `http_status` column 이 status 매핑 SSOT. **(엣지) `error-codes.yaml` 미존재 또는 row 누락 시 D11 mapping consistency contract test 동작**: registry 가 존재하는데 row 가 빠진 경우 = test FAIL(drift). registry 자체가 아직 생성 전인 현재 documented-only 단계에서는 D11 test 가 `planned`(비활성) — **registry 생성이 D11 test 활성화의 선행 조건**이며, registry 부재를 SKIP(통과)으로 처리하면 안 됨."},"evidence_b":{"line_end":340,"line_start":340,"quote":"| HTTP status mapping SSOT | `error-codes.yaml` 의 `http_status` column 이 모든 매핑의 SSOT (registry §21, owner: foundation branch) | 본 branch 는 mapping consistency contract test 의 producer | controller 가 registry 와 다른 HTTP status 반환 | status mapping consistency test (모든 `error.code` row 에 대해 실제 응답의 HTTP status 가 registry 와 일치) | registry-controller drift |"},"proof_manifest":null,"rationale":"A27 (this branch consumes error-codes.yaml, registry http_status = SSOT) and A32 (foundation branch owns that registry as SSOT) are the consumer + owner halves of the same dependency; compatible.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-2EC6B008A7FF4A0C4805","evidence_a":{"line_end":435,"line_start":435,"quote":" - [[raw/branch-notes/feature-operational-error-observability-foundation]] 의 `error-codes.yaml`(D11) + envelope schema 에 의존 — registry `http_status` column 이 status 매핑 SSOT. **(엣지) `error-codes.yaml` 미존재 또는 row 누락 시 D11 mapping consistency contract test 동작**: registry 가 존재하는데 row 가 빠진 경우 = test FAIL(drift). registry 자체가 아직 생성 전인 현재 documented-only 단계에서는 D11 test 가 `planned`(비활성) — **registry 생성이 D11 test 활성화의 선행 조건**이며, registry 부재를 SKIP(통과)으로 처리하면 안 됨."},"evidence_b":{"line_end":340,"line_start":340,"quote":"| HTTP status mapping SSOT | `error-codes.yaml` 의 `http_status` column 이 모든 매핑의 SSOT (registry §21, owner: foundation branch) | 본 branch 는 mapping consistency contract test 의 producer | controller 가 registry 와 다른 HTTP status 반환 | status mapping consistency test (모든 `error.code` row 에 대해 실제 응답의 HTTP status 가 registry 와 일치) | registry-controller drift |"},"proof_manifest":null,"rationale":"A27 (this branch consumes the foundation registry) and A33 (this branch produces the mapping consistency contract test) are complementary roles of this branch over the status-mapping dependency; no conflict.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-8E8E609534335097EFCA","evidence_a":{"line_end":435,"line_start":435,"quote":" - [[raw/branch-notes/feature-operational-error-observability-foundation]] 의 `error-codes.yaml`(D11) + envelope schema 에 의존 — registry `http_status` column 이 status 매핑 SSOT. **(엣지) `error-codes.yaml` 미존재 또는 row 누락 시 D11 mapping consistency contract test 동작**: registry 가 존재하는데 row 가 빠진 경우 = test FAIL(drift). registry 자체가 아직 생성 전인 현재 documented-only 단계에서는 D11 test 가 `planned`(비활성) — **registry 생성이 D11 test 활성화의 선행 조건**이며, registry 부재를 SKIP(통과)으로 처리하면 안 됨."},"evidence_b":{"line_end":390,"line_start":390,"quote":"| HTTP status ↔ envelope `error.code` 매핑 (D11) | **producer** of mapping consistency contract test | **owner** of registry: `feature-operational-error-observability-foundation` (`error-codes.yaml`) | 본 branch ← foundation branch |"},"proof_manifest":null,"rationale":"A27 (this branch consumes error-codes.yaml) and A37 (foundation branch owns that registry SSOT) are the consumer + owner halves of the dependency; compatible.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-A6CB3B6140C00F0A30E1","evidence_a":{"line_end":437,"line_start":437,"quote":" - [[raw/branch-notes/feature-rate-limit-idempotency-contract]] 에 의존(D3/D4) — `Idempotency-Key` key shape/scope/replay semantics owner. 본 branch 는 header *이름* 만."},"evidence_b":{"line_end":392,"line_start":392,"quote":"| `Idempotency-Key` header *이름* (D3) | **owner** (header name 만) | **owner** of key shape/scope/replay: `feature-rate-limit-idempotency-contract` | 본 branch → rate-limit branch (header name produces, key shape consumes) |"},"proof_manifest":null,"rationale":"A28 (delegate Idempotency-Key key shape/scope/replay to rate-limit branch) and A36 (this branch owns the header name only) are the two compatible halves of the idempotency ownership split; no competing claim.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-09A4573242B3E87C3AEF","evidence_a":{"line_end":335,"line_start":335,"quote":"| method-PATCH | PATCH default media type = `application/json` · request shape = `JsonNullable<T>` / `Optional<T>` wrapper · absent/null/value 3-상태 구분 (boundary branch B2 SSOT) | 없음 — `application/merge-patch+json` (RFC 7396) / `application/json-patch+json` (RFC 6902) 모두 **금지** | `application/merge-patch+json` / `application/json-patch+json` content type 사용 · absent vs null 미구분 (`null` 이 absent 와 동일 의미로 처리됨) | `no_merge_patch_json_media_type_string` ArchUnit rule (boundary branch B2) + PATCH absent/null 3-상태 mapper contract test | PATCH endpoint 가 `application/merge-patch+json` content type 허용 · Java record canonical constructor 가 absent 와 null 을 같은 기본값으로 수렴 |"},"evidence_b":{"line_end":396,"line_start":396,"quote":"| PATCH semantics (D14 정정 후) | **consumer** (content type 정책만 producer — `application/json` only) | **owner**: `feature-boundary-validation-mapping-contract` B2 (RFC 7396 미채택 + absent/null/value 3-상태 mapper + ArchUnit `no_merge_patch_json_media_type_string` enforced) | 본 branch ← boundary branch SSOT |"},"proof_manifest":null,"rationale":"A31 (this branch's PATCH content-type = application/json + 3-state wrapper, boundary B2 as mapper SSOT) and A40 (PATCH semantics owner = boundary branch B2) agree on the split: content type here, mapper semantics delegated. Compatible.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-101DD9241094F9A87C16","evidence_a":{"line_end":340,"line_start":340,"quote":"| HTTP status mapping SSOT | `error-codes.yaml` 의 `http_status` column 이 모든 매핑의 SSOT (registry §21, owner: foundation branch) | 본 branch 는 mapping consistency contract test 의 producer | controller 가 registry 와 다른 HTTP status 반환 | status mapping consistency test (모든 `error.code` row 에 대해 실제 응답의 HTTP status 가 registry 와 일치) | registry-controller drift |"},"evidence_b":{"line_end":340,"line_start":340,"quote":"| HTTP status mapping SSOT | `error-codes.yaml` 의 `http_status` column 이 모든 매핑의 SSOT (registry §21, owner: foundation branch) | 본 branch 는 mapping consistency contract test 의 producer | controller 가 registry 와 다른 HTTP status 반환 | status mapping consistency test (모든 `error.code` row 에 대해 실제 응답의 HTTP status 가 registry 와 일치) | registry-controller drift |"},"proof_manifest":null,"rationale":"Condition D11 HTTP status mapping SSOT (same table row): A32 (foundation branch's http_status column owns all mapping) and A33 (this branch produces the consistency contract test) are the owner + test-producer halves; compatible split, no competing ownership.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-7F9A7E2AB7D1E0B063C2","evidence_a":{"line_end":340,"line_start":340,"quote":"| HTTP status mapping SSOT | `error-codes.yaml` 의 `http_status` column 이 모든 매핑의 SSOT (registry §21, owner: foundation branch) | 본 branch 는 mapping consistency contract test 의 producer | controller 가 registry 와 다른 HTTP status 반환 | status mapping consistency test (모든 `error.code` row 에 대해 실제 응답의 HTTP status 가 registry 와 일치) | registry-controller drift |"},"evidence_b":{"line_end":390,"line_start":390,"quote":"| HTTP status ↔ envelope `error.code` 매핑 (D11) | **producer** of mapping consistency contract test | **owner** of registry: `feature-operational-error-observability-foundation` (`error-codes.yaml`) | 본 branch ← foundation branch |"},"proof_manifest":null,"rationale":"Scope status mapping SSOT, predicate owns: A32 (error-codes.yaml http_status column, foundation branch) and A37 (feature-operational-error-observability-foundation) both name the same foundation-branch registry as the mapping SSOT owner. Identical owner; no drift.","verdict":"CONSISTENT"},{"candidate_id":"SEM-DCD9C7D858F39FFF59D2","evidence_a":{"line_end":340,"line_start":340,"quote":"| HTTP status mapping SSOT | `error-codes.yaml` 의 `http_status` column 이 모든 매핑의 SSOT (registry §21, owner: foundation branch) | 본 branch 는 mapping consistency contract test 의 producer | controller 가 registry 와 다른 HTTP status 반환 | status mapping consistency test (모든 `error.code` row 에 대해 실제 응답의 HTTP status 가 registry 와 일치) | registry-controller drift |"},"evidence_b":{"line_end":95,"line_start":95,"quote":"- HTTP status code ↔ envelope `error.category`/`error.code` 의 전체 매핑 SSOT 위치 결정."},"proof_manifest":null,"rationale":"A32 (foundation branch owns the registry SSOT) and A43 (this branch owns the decision of WHERE the SSOT lives) describe distinct objects — the location meta-decision resolves to the foundation registry. Text establishes the resolution; they coexist without ambiguity.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-85DF12EABCC38DD6BE97","evidence_a":{"line_end":340,"line_start":340,"quote":"| HTTP status mapping SSOT | `error-codes.yaml` 의 `http_status` column 이 모든 매핑의 SSOT (registry §21, owner: foundation branch) | 본 branch 는 mapping consistency contract test 의 producer | controller 가 registry 와 다른 HTTP status 반환 | status mapping consistency test (모든 `error.code` row 에 대해 실제 응답의 HTTP status 가 registry 와 일치) | registry-controller drift |"},"evidence_b":{"line_end":390,"line_start":390,"quote":"| HTTP status ↔ envelope `error.code` 매핑 (D11) | **producer** of mapping consistency contract test | **owner** of registry: `feature-operational-error-observability-foundation` (`error-codes.yaml`) | 본 branch ← foundation branch |"},"proof_manifest":null,"rationale":"A33 (this branch produces the mapping consistency contract test) and A37 (foundation branch owns the registry SSOT) are the producer + owner halves of the D11 split; compatible, consistent roles.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-AF17EC6501CB7658B2B8","evidence_a":{"line_end":340,"line_start":340,"quote":"| HTTP status mapping SSOT | `error-codes.yaml` 의 `http_status` column 이 모든 매핑의 SSOT (registry §21, owner: foundation branch) | 본 branch 는 mapping consistency contract test 의 producer | controller 가 registry 와 다른 HTTP status 반환 | status mapping consistency test (모든 `error.code` row 에 대해 실제 응답의 HTTP status 가 registry 와 일치) | registry-controller drift |"},"evidence_b":{"line_end":95,"line_start":95,"quote":"- HTTP status code ↔ envelope `error.category`/`error.code` 의 전체 매핑 SSOT 위치 결정."},"proof_manifest":null,"rationale":"A33 (this branch produces the consistency contract test) and A43 (this branch owns the decision of where the mapping SSOT lives) are two distinct compatible responsibilities of this branch — a production role and a scoping decision; no conflict.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-C1E74114512201530D0A","evidence_a":{"line_end":390,"line_start":390,"quote":"| HTTP status ↔ envelope `error.code` 매핑 (D11) | **producer** of mapping consistency contract test | **owner** of registry: `feature-operational-error-observability-foundation` (`error-codes.yaml`) | 본 branch ← foundation branch |"},"evidence_b":{"line_end":95,"line_start":95,"quote":"- HTTP status code ↔ envelope `error.category`/`error.code` 의 전체 매핑 SSOT 위치 결정."},"proof_manifest":null,"rationale":"A37 (foundation branch owns the status-mapping registry SSOT) and A43 (this branch owns the decision of WHERE that SSOT lives) address distinct objects; the location decision resolves to the foundation registry, so they coexist with established precedence, not ambiguity.","verdict":"COMPLEMENTARY"}]},"auditor":{"contract_version":"semantic-coherence/v1","model_id":"claude-opus-4-8","run_id":"ac63300cf9c708858"},"auditor_contract_sha256":"1cbc67c27e5183a272687f635e3682a26d56785a1cf144da562eb7edd8f6cbce","coverage":{"candidate_pairs":67,"dropped_pairs":0,"eligible_surfaces":6,"processed_pairs":67,"processed_surfaces":6},"document_id":"028d796257593f29fa5962255a0bde4c75db268ac9085bf4739e432ab8b2e50f","document_sha256":"d22275b5740a519599d3e92d67a9a460cdf35b7fcb4801e15dd1a65edb594b56","findings":{"blocking":0,"readiness_blocking":0,"verified":0},"mode":"local","ontology_sha256":"5d601b96f0ca4d75eea89e9086c38e3acf2c4f0c7833846ef58c6a5719603126","policy_sha256":"0465598e9c1c4f2c3400ba51bf4719aa4890d60d56dc83304fb2224e660562b7","proof_manifest_sha256":"37517e5f3dc66819f61f5a7bb8ace1921282415f10551d2defa5c3eb0985b570","schema_version":"semantic-certificate/v1","semantic_audit_sha256":"83ba477af1ca0ac12679c02513f75910a6ab75c1e1f69f4a796142892b2f1fbe","subject":"raw/branch-notes/feature-api-contract-baseline.md","typed_contract_graph_sha256":"481fc3335a494c454ab13ad1d6a6ddccdec99aa8e083f6720ff8886bfa19cc89","verdict":"PASS"}