2 lines
80 KiB
JSON
2 lines
80 KiB
JSON
{"audit_request":{"assertions":[{"assertion_id":"ASSERT-001","condition":"branch done gate","line_end":57,"line_start":57,"modality":"must","object":"ULID format, PostgreSQL uuid persistence, and SecureRandom test all pass","predicate":"requires","quote":"- **완료 조건**: ULID format·PostgreSQL uuid persistence·SecureRandom test가 통과한다","scope":"feature-resource-identifier-contract branch","source_surface":"SURF-80DAD83EBEDEA196BCF6","subject":"branch completion condition"},{"assertion_id":"ASSERT-002","condition":"inherited project decision DEC-...STACK-DATABASE-001@1","line_end":64,"line_start":64,"modality":"must","object":"PostgreSQL 16 single stack","predicate":"uses","quote":"| `DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-DATABASE-001@1` | database는 PostgreSQL 16 단일 stack이다 | Work Item 완료 조건에 적용 | [[raw/project-notes/ca-skeleton-operational-contract]] |","scope":"database stack","source_surface":"SURF-80DAD83EBEDEA196BCF6","subject":"database"},{"assertion_id":"ASSERT-003","condition":"inherited project decision DEC-...STACK-RANDOM-001@1","line_end":65,"line_start":65,"modality":"must","object":"SecureRandom","predicate":"uses","quote":"| `DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-RANDOM-001@1` | ULID·idempotency key·token 생성의 random source는 SecureRandom이다 | Work Item 완료 조건에 적용 | [[raw/project-notes/ca-skeleton-operational-contract]] |","scope":"random source policy","source_surface":"SURF-80DAD83EBEDEA196BCF6","subject":"ULID / idempotency key / token generation random source"},{"assertion_id":"ASSERT-004","condition":"branch-local decision ownership","line_end":70,"line_start":70,"modality":"must","object":"branch-local decisions (not duplicated in this contract packet)","predicate":"owns","quote":"> 기존 branch-local 결정은 아래 `## Decision Evidence Map / 결정-근거 매핑`의 D-row가 소유하며 이 packet에서 복제하지 않는다.","scope":"branch-local decisions","source_surface":"SURF-80DAD83EBEDEA196BCF6","subject":"Decision Evidence Map D-row"},{"assertion_id":"ASSERT-005","condition":"ULID spec timestamp field","line_end":858,"line_start":858,"modality":"observed","object":"exposes 48-bit millisecond timestamp in plaintext (verified, ULID-C2)","predicate":"other","quote":"| ULID 가 48-bit millisecond timestamp 평문 노출 | spec 확인 필요 | ULID spec §1 timestamp 영역 | `verified` (ULID-C2) |","scope":"ULID identifier format","source_surface":"SURF-DD1BAC2A5F4C46358467","subject":"ULID"},{"assertion_id":"ASSERT-006","condition":"library version dependent, not yet independently confirmed","line_end":874,"line_start":874,"modality":"unknown","object":"SecureRandom (needs-confirmation, D9 JVM scope)","predicate":"uses","quote":"| `ulid-creator` Java 라이브러리의 `SecureRandom` 사용 | 라이브러리 버전 의존 | ulid-creator source / README | `needs-confirmation` (D9 JVM 적용 범위) |","scope":"D9 SecureRandom obligation","source_surface":"SURF-DD1BAC2A5F4C46358467","subject":"ulid-creator Java library"},{"assertion_id":"ASSERT-007","condition":"draft may change","line_end":871,"line_start":871,"modality":"unknown","object":"recommends 422 status code (needs-confirmation, D14)","predicate":"has_failure_behavior","quote":"| IETF `Idempotency-Key` HTTP header draft 의 422 status code 권고 | draft 변경 가능 | IETF datatracker | `needs-confirmation` (D14 fingerprint mismatch 응답) |","scope":"D14 fingerprint mismatch response","source_surface":"SURF-DD1BAC2A5F4C46358467","subject":"IETF Idempotency-Key HTTP header draft"},{"assertion_id":"ASSERT-008","condition":"skeleton default, Java 21 stack commitment","line_end":504,"line_start":504,"modality":"must","object":"ULID (26-char Crockford base32, time-ordered); rejects sequential, UUID v4, Snowflake, UUID v7","predicate":"uses","quote":"| D1 | Resource ID default = ULID (26-char Crockford base32, time-ordered). 거부: sequential, UUID v4, Snowflake, UUID v7 (Java 21 native 미지원) | RFC9562-C1/C3 (UUIDv7 time-ordered, SHOULD), ULID-C1~C6 (spec full), CROCKFORD-C1~C4 (Crockford base32 alphabet + 디코딩 정규화), NANOID-C1~C5 (NanoID 대안 비교), CUID2-C1~C5 (timestamp-leak-free 대안), SNOWFLAKE-C1~C5 (Snowflake 거부 근거 — coordination 부담), STRIPE-C2 (typed prefix 변경 가능성 = lock-in 경고), project §34 Stack Commitment (Java 21 = UUIDv7 native 미지원 → ULID 확정) | `official-standard` (RFC9562·RFC3986·ULID) + `project-ssot` (§34) | trade-off 소멸 — Java 21 stack commit 으로 UUIDv7 거부 확정 |","scope":"resource ID format","source_surface":"SURF-7CF5BD4C6E83E01D8B0D","subject":"Resource ID default"},{"assertion_id":"ASSERT-009","condition":"URL safety","line_end":506,"line_start":506,"modality":"must","object":"RFC 3986 unreserved proper subset + canonical uppercase output + case-insensitive input normalization","predicate":"enforces","quote":"| D3 | URL-safe = RFC 3986 `unreserved` 진부분집합 + canonical uppercase 출력 + case-insensitive 입력 수용 (서버 normalize) | RFC3986-C1 (`unreserved` charset 정의), RFC3986-C3 (path case-sensitive), RFC3986-C4 (case normalization 규칙), CROCKFORD-C3 (case-insensitive 디코딩 `i`/`l`→`1`, `o`→`0`), CROCKFORD-C4 (하이픈 무시 정책) | `official-standard` (RFC 3986) | 클라이언트가 lowercase 입력 시 서버 normalize 누락하면 cache key miss 발생 — D17 ArchUnit rule 또는 boundary layer normalization 강제 필요 |","scope":"ID charset / URL safety","source_surface":"SURF-7CF5BD4C6E83E01D8B0D","subject":"URL-safe policy"},{"assertion_id":"ASSERT-010","condition":"unconditional","line_end":507,"line_start":507,"modality":"must","object":"server-assigned generation; Idempotency-Key is client-generated; PUT upsert client-provided ID rejected","predicate":"owns","quote":"| D4 | resource ID = server-assigned, Idempotency-Key = client-generated. PUT upsert (client-provided ID) 거부 | BRANDUR-IDEMP-C8 (idempotency = client generated), BRANDUR-IDEMP-C11 (HTTP header 전송 = client 구성), STRIPE-C1 (Idempotency-Key 별개 명시), AIP148-C2 (uid = system-assigned opaque),[[raw/branch-notes/feature-rate-limit-idempotency-contract]] D-row SSOT | `engineering-blog` (BRANDUR) + `official-vendor-doc` (Stripe·AIP-148) | resource ID 전반의 server-assigned 의무는 normative IETF 표준 없음 — AIP-148 + Stripe 관행 + DDD 정통의 결합 |","scope":"ID generation responsibility","source_surface":"SURF-7CF5BD4C6E83E01D8B0D","subject":"resource ID"},{"assertion_id":"ASSERT-011","condition":"unconditional","line_end":508,"line_start":508,"modality":"must","object":"Domain port (WorkLogIdFactory) + Application injection; rejects Infrastructure-managed and Domain static self-generation","predicate":"owns","quote":"| D5 | ID generation layer = **Domain port (`WorkLogIdFactory`) + Application 주입 (use case orchestration)**. 거부: ① Infrastructure-managed (Hibernate generator), ② Domain static self-generation (`WorkLog.create()` 안 `UUID.randomUUID()` — 현재 코드 마이그레이션 대상) | AIP148-C1/C2 (server/system-assigned 관례), DDD factory pattern (factory = 도메인 service, entity 가 아님) | `official-vendor-doc` (AIP-148) + branch decision (DDD factory pattern) | UNSUPPORTED_IMPL_DECISION: type-specific port (`WorkLogIdFactory`) vs generic `IdFactory<WorkLogId>` 주입은 구현 컨벤션 trade-off — skeleton default = type-specific (도메인 의도 명시) |","scope":"ID generation architecture layer","source_surface":"SURF-7CF5BD4C6E83E01D8B0D","subject":"ID generation layer"},{"assertion_id":"ASSERT-012","condition":"public identifier (non-PII)","line_end":512,"line_start":512,"modality":"must","object":"SecureRandom obligation; constant-time comparison not applied (public id uses standard equals)","predicate":"requires","quote":"| D9 | **SecureRandom 의무** + constant-time 비교 미적용 — 공개 resource id 는 표준 `equals` 사용. constant-time 은 비밀값 (token/API key/session) 영역으로 [[raw/branch-notes/feature-security-operational-baseline]] 위임 | NANOID-C4 (crypto-strong random 의무), D8 (공개 식별자 분류 — non-PII, 비밀값 아님),[[raw/branch-notes/feature-security-operational-baseline]] D-row (비밀값 비교) | `official-reference` (NANOID) + branch decision (공개 id 의 record `equals` 정합) | NANOID-C4 는 JS 구현 기준이며 JVM `ulid-creator` 의 실제 `SecureRandom` 사용은 라이브러리 버전별 확인 전까지 `needs-confirmation`. 2026-06-01 spec drift 정정: 이전 \"constant-time comparison\" 본문은 D8 공개 식별자 분류와 모순 → 미적용으로 통일 |","scope":"random source / comparison","source_surface":"SURF-7CF5BD4C6E83E01D8B0D","subject":"public resource id generation"},{"assertion_id":"ASSERT-013","condition":"project §34 single DB","line_end":513,"line_start":513,"modality":"must","object":"PostgreSQL 16 uuid native; rejects varchar(26/36), BIGINT, MySQL BINARY(16)","predicate":"uses","quote":"| D10 | DB PK = PostgreSQL 16 `uuid` native (project §34 단일 DB). 거부: `varchar(26/36)`, `BIGINT`, MySQL `BINARY(16)` (out of scope) | RFC9562-C4 (monotonicity backbone), ULID-C4/C5/C6 (정렬·monotonic·binary 레이아웃), PERCONA-UUID-C2~C5 (random vs ordered UUID 일반 원리 —*parallel evidence* only, MySQL InnoDB 직접 적용 불가), project §34 (PostgreSQL 16 단일 DB stack) | `official-standard` (RFC9562·ULID) + `project-ssot` (§34) + `company-case-study` (Percona = parallel only) | UNSUPPORTED_IMPL_DECISION: PostgreSQL 16 `uuid` column index locality (ULID time-ordered insert 의 BTREE page split 완화) 정량 벤치마크 미보관 — 별도 raw 보강 필요 |","scope":"DB primary key","source_surface":"SURF-7CF5BD4C6E83E01D8B0D","subject":"DB primary key"},{"assertion_id":"ASSERT-014","condition":"fingerprint mismatch","line_end":517,"line_start":517,"modality":"must","object":"separate formats; fingerprint mismatch returns HTTP 422","predicate":"has_failure_behavior","quote":"| D14 | Resource ID (ULID, server-assigned, URL path) vs Idempotency-Key (UUID v4, client-generated, HTTP header, 24h TTL) —*별개 형식*. fingerprint mismatch → HTTP 422 | BRANDUR-IDEMP-C8~C12 (분리 차원 모두), STRIPE-C1/C5 (별개 + POST 전용),[[raw/branch-notes/feature-rate-limit-idempotency-contract]] D-row | `engineering-blog` (BRANDUR) + `official-vendor-doc` (Stripe) | IETF `Idempotency-Key` draft 의 422 vs Brandur 409 불일치 — IETF draft raw 보강 권고 |","scope":"ID vs idempotency key distinction","source_surface":"SURF-7CF5BD4C6E83E01D8B0D","subject":"Resource ID vs Idempotency-Key"},{"assertion_id":"ASSERT-015","condition":"soft-delete and hard-delete","line_end":518,"line_start":518,"modality":"must_not","object":"never reuse ID (soft-delete and hard-delete both NEVER reuse)","predicate":"forbids","quote":"| D15 | ID 재사용 금지 (soft-delete + hard-delete 모두 NEVER reuse) — audit trail + ULID monotonicity 정합 | AIP148-C2 (uid 재사용 금지 AIP-164 위임), ULID-C5 (monotonic 위반 회피) | `official-vendor-doc` (AIP-148 간접) | UNSUPPORTED_DECISION: AIP-164 원문 raw 미보관 — 재사용 금지의 normative 근거 보강 권고 |","scope":"ID reuse policy","source_surface":"SURF-7CF5BD4C6E83E01D8B0D","subject":"resource ID reuse"},{"assertion_id":"ASSERT-016","condition":"decision SSOT here, code hosting in boundary branch","line_end":520,"line_start":520,"modality":"must","object":"4 ArchUnit rules decision SSOT (no_long_id_pk, no_uuid_random_in_controller, no_math_random_for_id, no_varchar_255_for_id_column); code hosting delegated to boundary suite","predicate":"owns","quote":"| D17 | **4 ArchUnit rules** (결정 SSOT = 본 branch, 코드 호스팅 = boundary suite):`no_long_id_pk` (`..domain..` 한정), `no_uuid_random_in_controller`, `no_math_random_for_id`, `no_varchar_255_for_id_column` — [[raw/branch-notes/feature-boundary-validation-mapping-contract]] suite (archunit-junit5 1.3.0 per project §34). `no_find_by_id_without_tenant` (D13 보강 rule) 는 `feature-tenant-context-policy` 로 이관 | [[raw/branch-notes/feature-boundary-validation-mapping-contract]] ArchUnit pattern (sibling SSOT, hosts code) + project §34 (archunit-junit5 1.3.0) | `project-ssot` (§34) + branch decision | 본 branch §6 = reference skeleton. 실제 코드 = boundary branch §구현 가이드.`haveExplicitColumnLength()` custom condition 의 1.3.0 API 호환성 검증 필요 |","scope":"ArchUnit rule SSOT","source_surface":"SURF-7CF5BD4C6E83E01D8B0D","subject":"feature-resource-identifier-contract branch"},{"assertion_id":"ASSERT-017","condition":"unconditional","line_end":522,"line_start":522,"modality":"must","object":"value 01ARZ3NDEKTSV4RRFFQ69G5FAV (26-char uppercase Crockford base32 ULID); validation regex ^[0-9A-HJKMNP-TV-Z]{26}$","predicate":"has_schema","quote":"| D19 | sample-portfolio `WorkLogId` fixture = `01ARZ3NDEKTSV4RRFFQ69G5FAV` (26-char uppercase Crockford base32 ULID). Validation regex `^[0-9A-HJKMNP-TV-Z]{26}$` | ULID-C1 (26자 형식), CROCKFORD-C1 (charset) | `official-reference` | baseline branch + project-note §17/§22 가 본 fixture cite — cross-branch 정합 검증 필요 |","scope":"sample fixture value","source_surface":"SURF-7CF5BD4C6E83E01D8B0D","subject":"sample-portfolio WorkLogId fixture"},{"assertion_id":"ASSERT-018","condition":"wire format mismatch","line_end":850,"line_start":850,"modality":"observed","object":"breaks API and snapshot consumer simultaneously","predicate":"has_failure_behavior","quote":"- wire format 길이·대소문자·parser가 어긋나면 API와 snapshot consumer가 동시에 깨진다.","scope":"wire format contract","source_surface":"SURF-6D48475931E00BEFEA92","subject":"wire format length/case/parser mismatch"},{"assertion_id":"ASSERT-019","condition":"inherited storage and random-source policy","line_end":851,"line_start":851,"modality":"must_not","object":"controller direct ID generation (inherits database native uuid storage + random-source policy)","predicate":"forbids","quote":"- database native `uuid` 저장과 random-source 정책을 상속하며 controller 직접 생성을 금지한다.","scope":"ID generation location","source_surface":"SURF-6D48475931E00BEFEA92","subject":"this branch"},{"assertion_id":"ASSERT-020","condition":"approved parent decision revision update","line_end":852,"line_start":852,"modality":"must","object":"UUIDv7 transition; ULID-based wording must migrate together on approved parent decision revision update","predicate":"owns","quote":"- [[raw/branch-notes/chore-ulid-to-uuidv7]]가 UUIDv7 전환을 소유하므로 ULID 기준 문구는 승인된 parent decision revision 갱신 시 함께 migration해야 한다.","scope":"UUIDv7 migration","source_surface":"SURF-6D48475931E00BEFEA92","subject":"chore-ulid-to-uuidv7 branch"},{"assertion_id":"ASSERT-021","condition":"unconditional","line_end":526,"line_start":526,"modality":"must","object":"reference to Decision ID + Supporting Claim ID (R1); UNSUPPORTED_IMPL_DECISION label + trade-off for ungrounded detail (R2); out-of-scope areas migrated to sibling SSOT (R3)","predicate":"requires","quote":"> 본 § 의 각 sub-section 은 `Decision ID` + `Supporting Claim ID` 를 reference (R1). 메커니즘 / 명명 / glob / API 모양 중 *근거 없는 detail* 은 `UNSUPPORTED_IMPL_DECISION` 라벨 + trade-off 한 줄 (R2). 본 branch 범위 밖 영역은 *남기지 않고* sibling SSOT 로 이관 (R3).","scope":"implementation guide authoring","source_surface":"SURF-BF6B6A540CF30C05929E","subject":"implementation guide sub-section"},{"assertion_id":"ASSERT-022","condition":"§1 domain layer implementation","line_end":530,"line_start":530,"modality":"observed","object":"D1 (ULID), D4 (server-assigned), D5 (domain factory), D17 (no_long_id_pk)","predicate":"maps_to","quote":"> Trace: D1 (ULID), D4 (server-assigned), D5 (domain factory), D17 (`no_long_id_pk`)","scope":"domain layer identifier","source_surface":"SURF-BF6B6A540CF30C05929E","subject":"Domain layer WorkLogId value object + IdFactory<T> port"},{"assertion_id":"ASSERT-023","condition":"ULID adoption","line_end":723,"line_start":723,"modality":"must_not","object":"format: uuid (D1 ULID adopted; format assumes UUID v4)","predicate":"forbids","quote":"- OpenAPI 3.1 `format: uuid` **사용 안 함** (D1 ULID 채택, UUID v4 가정의 format).","scope":"OpenAPI schema","source_surface":"SURF-BF6B6A540CF30C05929E","subject":"OpenAPI 3.1 schema for WorkLogId"},{"assertion_id":"ASSERT-024","condition":"library version verification pending","line_end":600,"line_start":600,"modality":"observed","object":"UlidCreator.getMonotonicUlid() monotonic within same ms (ULID-C5); internal SecureRandom compliance needs-confirmation","predicate":"uses","quote":"- `UlidCreator.getMonotonicUlid()` → 동일 ms 내 monotonic 동작을 사용한다(ULID-C5). 내부 난수원이 D9의 `SecureRandom` 의무를 충족하는지는 사용 버전 source/README 확인 전까지 `needs-confirmation`이다.","scope":"infrastructure adapter ULID generation","source_surface":"SURF-BF6B6A540CF30C05929E","subject":"UlidWorkLogIdFactory"},{"assertion_id":"ASSERT-025","condition":"included scope","line_end":216,"line_start":216,"modality":"must","object":"resource ID format default decision (UUID v4/v7/ULID/NanoID/KSUID/TSID/CUID2/opaque prefix string/Snowflake; sequential rejected)","predicate":"owns","quote":"- resource ID 형식 default 결정 — UUID v4 / v7 / ULID / NanoID / KSUID / TSID / CUID2 / opaque prefix string / Snowflake 중 선택 (sequential 거부)","scope":"resource ID format default","source_surface":"SURF-105E85D48062F78B0F54","subject":"feature-resource-identifier-contract branch"},{"assertion_id":"ASSERT-026","condition":"included scope","line_end":224,"line_start":224,"modality":"must","object":"PostgreSQL 16 uuid native (project §34 single DB)","predicate":"uses","quote":"- ID 의 DB primary key 정책 (PostgreSQL 16 `uuid` native — project §34 단일 DB)","scope":"DB primary key policy","source_surface":"SURF-105E85D48062F78B0F54","subject":"resource ID DB primary key policy"},{"assertion_id":"ASSERT-027","condition":"included scope","line_end":233,"line_start":233,"modality":"must","object":"ArchUnit rule SSOT blocking anti-patterns (no_long_id_pk, no_uuid_random_in_controller, no_math_random_for_id, no_varchar_255_for_id_column)","predicate":"owns","quote":"- ArchUnit rule SSOT — anti-pattern 차단 (`no_long_id_pk`, `no_uuid_random_in_controller`, `no_math_random_for_id`, `no_varchar_255_for_id_column`)","scope":"ArchUnit rule SSOT","source_surface":"SURF-105E85D48062F78B0F54","subject":"feature-resource-identifier-contract branch"},{"assertion_id":"ASSERT-028","condition":"excluded scope","line_end":240,"line_start":240,"modality":"must_not","object":"feature-security-operational-baseline responsibility (out of scope)","predicate":"delegates","quote":"- API key / OAuth client_id format (`feature-security-operational-baseline` 책임)","scope":"excluded scope delegation","source_surface":"SURF-105E85D48062F78B0F54","subject":"API key / OAuth client_id format"}],"candidate_manifest_sha256":"0558100957543a0715acfcf52c5344d48b61cb1d0f66ef93903b45823d2a9f7e","candidates":[{"assertion_a":"ASSERT-002","assertion_b":"ASSERT-003","candidate_id":"SEM-7C14DD6BCAEE50C0096D","grouping_key":{"condition":"","predicate":"uses","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"ASSERT-006","assertion_b":"ASSERT-007","candidate_id":"SEM-CA965E6D9FB379ACACA6","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"ASSERT-006","assertion_b":"ASSERT-012","candidate_id":"SEM-CE1194F250FE4AF31080","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"ASSERT-006","assertion_b":"ASSERT-024","candidate_id":"SEM-9159B3CE93D4AC5AECFC","grouping_key":{"condition":"","predicate":"uses","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"ASSERT-007","assertion_b":"ASSERT-012","candidate_id":"SEM-6F87FEF820B7AE5F426A","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"ASSERT-007","assertion_b":"ASSERT-014","candidate_id":"SEM-0E3EAC997994665A97F4","grouping_key":{"condition":"","predicate":"has_failure_behavior","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"ASSERT-007","assertion_b":"ASSERT-024","candidate_id":"SEM-A2C71D753481DD795C28","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"ASSERT-008","assertion_b":"ASSERT-009","candidate_id":"SEM-10F5FB72502D56E8B1C4","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"ASSERT-008","assertion_b":"ASSERT-013","candidate_id":"SEM-99A1982649431592C3F7","grouping_key":{"condition":"","predicate":"uses","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"ASSERT-008","assertion_b":"ASSERT-016","candidate_id":"SEM-5374D7E865723016C7E3","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"ASSERT-009","assertion_b":"ASSERT-013","candidate_id":"SEM-45A1E09F71F9C83209DF","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"ASSERT-010","assertion_b":"ASSERT-011","candidate_id":"SEM-D59981F009278D596821","grouping_key":{"condition":"unconditional","predicate":"owns","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"ASSERT-010","assertion_b":"ASSERT-014","candidate_id":"SEM-263019D122A94776139C","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"ASSERT-010","assertion_b":"ASSERT-015","candidate_id":"SEM-84A5994F42597306E003","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"ASSERT-011","assertion_b":"ASSERT-014","candidate_id":"SEM-6262CFC757C213092AB3","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"ASSERT-011","assertion_b":"ASSERT-015","candidate_id":"SEM-5460F93B339BB17D42D2","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"ASSERT-012","assertion_b":"ASSERT-017","candidate_id":"SEM-86C43E13AD7A5310A87C","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"ASSERT-012","assertion_b":"ASSERT-024","candidate_id":"SEM-78BF76121885BDC8464D","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"ASSERT-013","assertion_b":"ASSERT-016","candidate_id":"SEM-CAE55A9C8D031D45B24B","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"ASSERT-013","assertion_b":"ASSERT-019","candidate_id":"SEM-8AA2F3167590C0AFDB18","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"ASSERT-013","assertion_b":"ASSERT-026","candidate_id":"SEM-6A2BDE53A65A1015D2A3","grouping_key":{"condition":"","predicate":"uses","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"ASSERT-014","assertion_b":"ASSERT-015","candidate_id":"SEM-BE3E59E1F6CB72EE4040","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"ASSERT-016","assertion_b":"ASSERT-022","candidate_id":"SEM-1550757A6E27F224DF05","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"ASSERT-016","assertion_b":"ASSERT-027","candidate_id":"SEM-558B4933BB1C716BA273","grouping_key":{"condition":"","predicate":"owns","scope":"ArchUnit rule SSOT","subject":"feature-resource-identifier-contract branch"},"rule_ids":["C6"]},{"assertion_a":"ASSERT-019","assertion_b":"ASSERT-026","candidate_id":"SEM-D80904049DD8F091ADDA","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]},{"assertion_a":"ASSERT-022","assertion_b":"ASSERT-027","candidate_id":"SEM-C7E81FAC40CEBC08AD73","grouping_key":{"condition":"","predicate":"","scope":"","subject":""},"rule_ids":["C6"]}],"coverage":{"assertions":28,"candidate_pairs":26,"eligible_surfaces":6,"processed_surfaces":6},"document_sha256":"6d9b7e25716697a12113c493964bdf391df37c6b29e981ef49ca22ba3598660f","explicit_blocking":[],"mode":"local","ontology_sha256":"76d41a29233c830e1940ebb3244263e2b9bfb8dd6a01f2a1d1c51ce5a1b10468","output_schema":"semantic-audit-result/v1","schema_version":"semantic-verdict-request/v1","subject":"raw/branch-notes/feature-resource-identifier-contract.md"},"audit_request_sha256":"5ab1a397999510e28dc4a7e46555c35755e7123da722eba1a85e88cda15118f9","audit_result":{"auditor":{"contract_version":"semantic-coherence/v1","model_id":"claude-opus-4-8","run_id":"af0fd5585dd959ed2"},"mode":"local","request_sha256":"5ab1a397999510e28dc4a7e46555c35755e7123da722eba1a85e88cda15118f9","schema_version":"semantic-audit-result/v1","subject":"raw/branch-notes/feature-resource-identifier-contract.md","verdicts":[{"candidate_id":"SEM-7C14DD6BCAEE50C0096D","evidence_a":{"line_end":64,"line_start":64,"quote":"| `DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-DATABASE-001@1` | database는 PostgreSQL 16 단일 stack이다 | Work Item 완료 조건에 적용 | [[raw/project-notes/ca-skeleton-operational-contract]] |"},"evidence_b":{"line_end":65,"line_start":65,"quote":"| `DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-RANDOM-001@1` | ULID·idempotency key·token 생성의 random source는 SecureRandom이다 | Work Item 완료 조건에 적용 | [[raw/project-notes/ca-skeleton-operational-contract]] |"},"proof_manifest":null,"rationale":"ASSERT-002 (database uses PostgreSQL 16 single stack) and ASSERT-003 (random source uses SecureRandom) are two distinct inherited project decisions covering different contract properties (DB stack vs random-source policy), both applied to the branch's work-item completion condition. Neither takes over the other's responsibility; they add compatible detail.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-CA965E6D9FB379ACACA6","evidence_a":{"line_end":874,"line_start":874,"quote":"| `ulid-creator` Java 라이브러리의 `SecureRandom` 사용 | 라이브러리 버전 의존 | ulid-creator source / README | `needs-confirmation` (D9 JVM 적용 범위) |"},"evidence_b":{"line_end":871,"line_start":871,"quote":"| IETF `Idempotency-Key` HTTP header draft 의 422 status code 권고 | draft 변경 가능 | IETF datatracker | `needs-confirmation` (D14 fingerprint mismatch 응답) |"},"proof_manifest":null,"rationale":"ASSERT-006 (ulid-creator SecureRandom, needs-confirmation, D9) and ASSERT-007 (IETF draft 422 status, needs-confirmation, D14) are separate spec-evidence rows on unrelated subjects (random source vs HTTP status). They coexist as independent verification items with no shared property to conflict on.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-CE1194F250FE4AF31080","evidence_a":{"line_end":874,"line_start":874,"quote":"| `ulid-creator` Java 라이브러리의 `SecureRandom` 사용 | 라이브러리 버전 의존 | ulid-creator source / README | `needs-confirmation` (D9 JVM 적용 범위) |"},"evidence_b":{"line_end":512,"line_start":512,"quote":"| D9 | **SecureRandom 의무** + constant-time 비교 미적용 — 공개 resource id 는 표준 `equals` 사용. constant-time 은 비밀값 (token/API key/session) 영역으로 [[raw/branch-notes/feature-security-operational-baseline]] 위임 | NANOID-C4 (crypto-strong random 의무), D8 (공개 식별자 분류 — non-PII, 비밀값 아님),[[raw/branch-notes/feature-security-operational-baseline]] D-row (비밀값 비교) | `official-reference` (NANOID) + branch decision (공개 id 의 record `equals` 정합) | NANOID-C4 는 JS 구현 기준이며 JVM `ulid-creator` 의 실제 `SecureRandom` 사용은 라이브러리 버전별 확인 전까지 `needs-confirmation`. 2026-06-01 spec drift 정정: 이전 \"constant-time comparison\" 본문은 D8 공개 식별자 분류와 모순 → 미적용으로 통일 |"},"proof_manifest":null,"rationale":"ASSERT-012 states the D9 SecureRandom obligation for public resource ids; ASSERT-006 supplies the empirical caveat that the ulid-creator library's actual SecureRandom compliance is needs-confirmation. A normative obligation plus an unresolved verification status about the same subject are compatible detail, not conflicting values — ASSERT-012 itself already carries the same needs-confirmation caveat.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-9159B3CE93D4AC5AECFC","evidence_a":{"line_end":874,"line_start":874,"quote":"| `ulid-creator` Java 라이브러리의 `SecureRandom` 사용 | 라이브러리 버전 의존 | ulid-creator source / README | `needs-confirmation` (D9 JVM 적용 범위) |"},"evidence_b":{"line_end":600,"line_start":600,"quote":"- `UlidCreator.getMonotonicUlid()` → 동일 ms 내 monotonic 동작을 사용한다(ULID-C5). 내부 난수원이 D9의 `SecureRandom` 의무를 충족하는지는 사용 버전 source/README 확인 전까지 `needs-confirmation`이다."},"proof_manifest":null,"rationale":"ASSERT-006 and ASSERT-024 make the same claim: the ulid-creator library's internal SecureRandom compliance is needs-confirmation pending library-version source/README verification. They agree on subject, modality, and status.","verdict":"CONSISTENT"},{"candidate_id":"SEM-6F87FEF820B7AE5F426A","evidence_a":{"line_end":871,"line_start":871,"quote":"| IETF `Idempotency-Key` HTTP header draft 의 422 status code 권고 | draft 변경 가능 | IETF datatracker | `needs-confirmation` (D14 fingerprint mismatch 응답) |"},"evidence_b":{"line_end":512,"line_start":512,"quote":"| D9 | **SecureRandom 의무** + constant-time 비교 미적용 — 공개 resource id 는 표준 `equals` 사용. constant-time 은 비밀값 (token/API key/session) 영역으로 [[raw/branch-notes/feature-security-operational-baseline]] 위임 | NANOID-C4 (crypto-strong random 의무), D8 (공개 식별자 분류 — non-PII, 비밀값 아님),[[raw/branch-notes/feature-security-operational-baseline]] D-row (비밀값 비교) | `official-reference` (NANOID) + branch decision (공개 id 의 record `equals` 정합) | NANOID-C4 는 JS 구현 기준이며 JVM `ulid-creator` 의 실제 `SecureRandom` 사용은 라이브러리 버전별 확인 전까지 `needs-confirmation`. 2026-06-01 spec drift 정정: 이전 \"constant-time comparison\" 본문은 D8 공개 식별자 분류와 모순 → 미적용으로 통일 |"},"proof_manifest":null,"rationale":"ASSERT-007 (IETF Idempotency-Key draft 422 status, D14) and ASSERT-012 (SecureRandom obligation + constant-time not applied, D9) address different contract properties (HTTP failure status vs random-source/comparison policy). No shared property to conflict on.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-0E3EAC997994665A97F4","evidence_a":{"line_end":871,"line_start":871,"quote":"| IETF `Idempotency-Key` HTTP header draft 의 422 status code 권고 | draft 변경 가능 | IETF datatracker | `needs-confirmation` (D14 fingerprint mismatch 응답) |"},"evidence_b":{"line_end":517,"line_start":517,"quote":"| D14 | Resource ID (ULID, server-assigned, URL path) vs Idempotency-Key (UUID v4, client-generated, HTTP header, 24h TTL) —*별개 형식*. fingerprint mismatch → HTTP 422 | BRANDUR-IDEMP-C8~C12 (분리 차원 모두), STRIPE-C1/C5 (별개 + POST 전용),[[raw/branch-notes/feature-rate-limit-idempotency-contract]] D-row | `engineering-blog` (BRANDUR) + `official-vendor-doc` (Stripe) | IETF `Idempotency-Key` draft 의 422 vs Brandur 409 불일치 — IETF draft raw 보강 권고 |"},"proof_manifest":null,"rationale":"Both assign HTTP 422 to the D14 fingerprint-mismatch response: ASSERT-014 is the decision, ASSERT-007 is its supporting IETF-draft evidence. They agree on the same failure behavior (422).","verdict":"CONSISTENT"},{"candidate_id":"SEM-A2C71D753481DD795C28","evidence_a":{"line_end":871,"line_start":871,"quote":"| IETF `Idempotency-Key` HTTP header draft 의 422 status code 권고 | draft 변경 가능 | IETF datatracker | `needs-confirmation` (D14 fingerprint mismatch 응답) |"},"evidence_b":{"line_end":600,"line_start":600,"quote":"- `UlidCreator.getMonotonicUlid()` → 동일 ms 내 monotonic 동작을 사용한다(ULID-C5). 내부 난수원이 D9의 `SecureRandom` 의무를 충족하는지는 사용 버전 source/README 확인 전까지 `needs-confirmation`이다."},"proof_manifest":null,"rationale":"ASSERT-007 (IETF draft 422 status, D14) and ASSERT-024 (UlidWorkLogIdFactory / SecureRandom, D9) concern unrelated subjects (HTTP status vs infrastructure ULID generation). They coexist without a shared property.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-10F5FB72502D56E8B1C4","evidence_a":{"line_end":504,"line_start":504,"quote":"| D1 | Resource ID default = ULID (26-char Crockford base32, time-ordered). 거부: sequential, UUID v4, Snowflake, UUID v7 (Java 21 native 미지원) | RFC9562-C1/C3 (UUIDv7 time-ordered, SHOULD), ULID-C1~C6 (spec full), CROCKFORD-C1~C4 (Crockford base32 alphabet + 디코딩 정규화), NANOID-C1~C5 (NanoID 대안 비교), CUID2-C1~C5 (timestamp-leak-free 대안), SNOWFLAKE-C1~C5 (Snowflake 거부 근거 — coordination 부담), STRIPE-C2 (typed prefix 변경 가능성 = lock-in 경고), project §34 Stack Commitment (Java 21 = UUIDv7 native 미지원 → ULID 확정) | `official-standard` (RFC9562·RFC3986·ULID) + `project-ssot` (§34) | trade-off 소멸 — Java 21 stack commit 으로 UUIDv7 거부 확정 |"},"evidence_b":{"line_end":506,"line_start":506,"quote":"| D3 | URL-safe = RFC 3986 `unreserved` 진부분집합 + canonical uppercase 출력 + case-insensitive 입력 수용 (서버 normalize) | RFC3986-C1 (`unreserved` charset 정의), RFC3986-C3 (path case-sensitive), RFC3986-C4 (case normalization 규칙), CROCKFORD-C3 (case-insensitive 디코딩 `i`/`l`→`1`, `o`→`0`), CROCKFORD-C4 (하이픈 무시 정책) | `official-standard` (RFC 3986) | 클라이언트가 lowercase 입력 시 서버 normalize 누락하면 cache key miss 발생 — D17 ArchUnit rule 또는 boundary layer normalization 강제 필요 |"},"proof_manifest":null,"rationale":"ASSERT-008 (Resource ID default = ULID format, D1) and ASSERT-009 (URL-safe RFC 3986 charset/normalization, D3) describe different facets of the ID format — the encoding choice vs the URL-safety/charset constraint. They compose without either taking over the other.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-99A1982649431592C3F7","evidence_a":{"line_end":504,"line_start":504,"quote":"| D1 | Resource ID default = ULID (26-char Crockford base32, time-ordered). 거부: sequential, UUID v4, Snowflake, UUID v7 (Java 21 native 미지원) | RFC9562-C1/C3 (UUIDv7 time-ordered, SHOULD), ULID-C1~C6 (spec full), CROCKFORD-C1~C4 (Crockford base32 alphabet + 디코딩 정규화), NANOID-C1~C5 (NanoID 대안 비교), CUID2-C1~C5 (timestamp-leak-free 대안), SNOWFLAKE-C1~C5 (Snowflake 거부 근거 — coordination 부담), STRIPE-C2 (typed prefix 변경 가능성 = lock-in 경고), project §34 Stack Commitment (Java 21 = UUIDv7 native 미지원 → ULID 확정) | `official-standard` (RFC9562·RFC3986·ULID) + `project-ssot` (§34) | trade-off 소멸 — Java 21 stack commit 으로 UUIDv7 거부 확정 |"},"evidence_b":{"line_end":513,"line_start":513,"quote":"| D10 | DB PK = PostgreSQL 16 `uuid` native (project §34 단일 DB). 거부: `varchar(26/36)`, `BIGINT`, MySQL `BINARY(16)` (out of scope) | RFC9562-C4 (monotonicity backbone), ULID-C4/C5/C6 (정렬·monotonic·binary 레이아웃), PERCONA-UUID-C2~C5 (random vs ordered UUID 일반 원리 —*parallel evidence* only, MySQL InnoDB 직접 적용 불가), project §34 (PostgreSQL 16 단일 DB stack) | `official-standard` (RFC9562·ULID) + `project-ssot` (§34) + `company-case-study` (Percona = parallel only) | UNSUPPORTED_IMPL_DECISION: PostgreSQL 16 `uuid` column index locality (ULID time-ordered insert 의 BTREE page split 완화) 정량 벤치마크 미보관 — 별도 raw 보강 필요 |"},"proof_manifest":null,"rationale":"ASSERT-008 (Resource ID wire format = 26-char Crockford base32 ULID, D1) and ASSERT-013 (DB PK = PostgreSQL 16 uuid native, D10) operate on different layers. ULID is a 128-bit value stored in the 16-byte uuid column; D1's rejection targets UUID v4/v7 as generation schemes, not the binary storage type. The decision rationale itself cites ULID binary layout for the uuid column, so these are compatible, not contradictory.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-5374D7E865723016C7E3","evidence_a":{"line_end":504,"line_start":504,"quote":"| D1 | Resource ID default = ULID (26-char Crockford base32, time-ordered). 거부: sequential, UUID v4, Snowflake, UUID v7 (Java 21 native 미지원) | RFC9562-C1/C3 (UUIDv7 time-ordered, SHOULD), ULID-C1~C6 (spec full), CROCKFORD-C1~C4 (Crockford base32 alphabet + 디코딩 정규화), NANOID-C1~C5 (NanoID 대안 비교), CUID2-C1~C5 (timestamp-leak-free 대안), SNOWFLAKE-C1~C5 (Snowflake 거부 근거 — coordination 부담), STRIPE-C2 (typed prefix 변경 가능성 = lock-in 경고), project §34 Stack Commitment (Java 21 = UUIDv7 native 미지원 → ULID 확정) | `official-standard` (RFC9562·RFC3986·ULID) + `project-ssot` (§34) | trade-off 소멸 — Java 21 stack commit 으로 UUIDv7 거부 확정 |"},"evidence_b":{"line_end":520,"line_start":520,"quote":"| D17 | **4 ArchUnit rules** (결정 SSOT = 본 branch, 코드 호스팅 = boundary suite):`no_long_id_pk` (`..domain..` 한정), `no_uuid_random_in_controller`, `no_math_random_for_id`, `no_varchar_255_for_id_column` — [[raw/branch-notes/feature-boundary-validation-mapping-contract]] suite (archunit-junit5 1.3.0 per project §34). `no_find_by_id_without_tenant` (D13 보강 rule) 는 `feature-tenant-context-policy` 로 이관 | [[raw/branch-notes/feature-boundary-validation-mapping-contract]] ArchUnit pattern (sibling SSOT, hosts code) + project §34 (archunit-junit5 1.3.0) | `project-ssot` (§34) + branch decision | 본 branch §6 = reference skeleton. 실제 코드 = boundary branch §구현 가이드.`haveExplicitColumnLength()` custom condition 의 1.3.0 API 호환성 검증 필요 |"},"proof_manifest":null,"rationale":"ASSERT-008 (Resource ID default = ULID, D1) and ASSERT-016 (4 ArchUnit rules SSOT, D17) cover different contract properties (ID format vs architecture-enforcement rules). No shared property to conflict on.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-45A1E09F71F9C83209DF","evidence_a":{"line_end":506,"line_start":506,"quote":"| D3 | URL-safe = RFC 3986 `unreserved` 진부분집합 + canonical uppercase 출력 + case-insensitive 입력 수용 (서버 normalize) | RFC3986-C1 (`unreserved` charset 정의), RFC3986-C3 (path case-sensitive), RFC3986-C4 (case normalization 규칙), CROCKFORD-C3 (case-insensitive 디코딩 `i`/`l`→`1`, `o`→`0`), CROCKFORD-C4 (하이픈 무시 정책) | `official-standard` (RFC 3986) | 클라이언트가 lowercase 입력 시 서버 normalize 누락하면 cache key miss 발생 — D17 ArchUnit rule 또는 boundary layer normalization 강제 필요 |"},"evidence_b":{"line_end":513,"line_start":513,"quote":"| D10 | DB PK = PostgreSQL 16 `uuid` native (project §34 단일 DB). 거부: `varchar(26/36)`, `BIGINT`, MySQL `BINARY(16)` (out of scope) | RFC9562-C4 (monotonicity backbone), ULID-C4/C5/C6 (정렬·monotonic·binary 레이아웃), PERCONA-UUID-C2~C5 (random vs ordered UUID 일반 원리 —*parallel evidence* only, MySQL InnoDB 직접 적용 불가), project §34 (PostgreSQL 16 단일 DB stack) | `official-standard` (RFC9562·ULID) + `project-ssot` (§34) + `company-case-study` (Percona = parallel only) | UNSUPPORTED_IMPL_DECISION: PostgreSQL 16 `uuid` column index locality (ULID time-ordered insert 의 BTREE page split 완화) 정량 벤치마크 미보관 — 별도 raw 보강 필요 |"},"proof_manifest":null,"rationale":"ASSERT-009 (URL-safe RFC 3986 charset, D3) and ASSERT-013 (DB PK PostgreSQL uuid native, D10) describe different facets — wire/URL charset vs DB storage type. They compose without overlap.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-D59981F009278D596821","evidence_a":{"line_end":507,"line_start":507,"quote":"| D4 | resource ID = server-assigned, Idempotency-Key = client-generated. PUT upsert (client-provided ID) 거부 | BRANDUR-IDEMP-C8 (idempotency = client generated), BRANDUR-IDEMP-C11 (HTTP header 전송 = client 구성), STRIPE-C1 (Idempotency-Key 별개 명시), AIP148-C2 (uid = system-assigned opaque),[[raw/branch-notes/feature-rate-limit-idempotency-contract]] D-row SSOT | `engineering-blog` (BRANDUR) + `official-vendor-doc` (Stripe·AIP-148) | resource ID 전반의 server-assigned 의무는 normative IETF 표준 없음 — AIP-148 + Stripe 관행 + DDD 정통의 결합 |"},"evidence_b":{"line_end":508,"line_start":508,"quote":"| D5 | ID generation layer = **Domain port (`WorkLogIdFactory`) + Application 주입 (use case orchestration)**. 거부: ① Infrastructure-managed (Hibernate generator), ② Domain static self-generation (`WorkLog.create()` 안 `UUID.randomUUID()` — 현재 코드 마이그레이션 대상) | AIP148-C1/C2 (server/system-assigned 관례), DDD factory pattern (factory = 도메인 service, entity 가 아님) | `official-vendor-doc` (AIP-148) + branch decision (DDD factory pattern) | UNSUPPORTED_IMPL_DECISION: type-specific port (`WorkLogIdFactory`) vs generic `IdFactory<WorkLogId>` 주입은 구현 컨벤션 trade-off — skeleton default = type-specific (도메인 의도 명시) |"},"proof_manifest":null,"rationale":"Both are 'owns' claims but on different objects: ASSERT-010 (D4) owns the generation responsibility (resource ID = server-assigned, Idempotency-Key = client-generated), while ASSERT-011 (D5) owns the architecture layer (Domain port WorkLogIdFactory + Application injection). Server-assigned generation via a Domain port injected into Application is a single coherent design; they assign values to distinct properties, so no contradiction.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-263019D122A94776139C","evidence_a":{"line_end":507,"line_start":507,"quote":"| D4 | resource ID = server-assigned, Idempotency-Key = client-generated. PUT upsert (client-provided ID) 거부 | BRANDUR-IDEMP-C8 (idempotency = client generated), BRANDUR-IDEMP-C11 (HTTP header 전송 = client 구성), STRIPE-C1 (Idempotency-Key 별개 명시), AIP148-C2 (uid = system-assigned opaque),[[raw/branch-notes/feature-rate-limit-idempotency-contract]] D-row SSOT | `engineering-blog` (BRANDUR) + `official-vendor-doc` (Stripe·AIP-148) | resource ID 전반의 server-assigned 의무는 normative IETF 표준 없음 — AIP-148 + Stripe 관행 + DDD 정통의 결합 |"},"evidence_b":{"line_end":517,"line_start":517,"quote":"| D14 | Resource ID (ULID, server-assigned, URL path) vs Idempotency-Key (UUID v4, client-generated, HTTP header, 24h TTL) —*별개 형식*. fingerprint mismatch → HTTP 422 | BRANDUR-IDEMP-C8~C12 (분리 차원 모두), STRIPE-C1/C5 (별개 + POST 전용),[[raw/branch-notes/feature-rate-limit-idempotency-contract]] D-row | `engineering-blog` (BRANDUR) + `official-vendor-doc` (Stripe) | IETF `Idempotency-Key` draft 의 422 vs Brandur 409 불일치 — IETF draft raw 보강 권고 |"},"proof_manifest":null,"rationale":"ASSERT-010 (D4: resource ID server-assigned, Idempotency-Key client-generated) and ASSERT-014 (D14: separate formats, fingerprint mismatch 422) both distinguish resource ID from Idempotency-Key and agree that the Idempotency-Key is client-generated. ASSERT-014 adds format-separation and failure-status detail, complementing D4.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-84A5994F42597306E003","evidence_a":{"line_end":507,"line_start":507,"quote":"| D4 | resource ID = server-assigned, Idempotency-Key = client-generated. PUT upsert (client-provided ID) 거부 | BRANDUR-IDEMP-C8 (idempotency = client generated), BRANDUR-IDEMP-C11 (HTTP header 전송 = client 구성), STRIPE-C1 (Idempotency-Key 별개 명시), AIP148-C2 (uid = system-assigned opaque),[[raw/branch-notes/feature-rate-limit-idempotency-contract]] D-row SSOT | `engineering-blog` (BRANDUR) + `official-vendor-doc` (Stripe·AIP-148) | resource ID 전반의 server-assigned 의무는 normative IETF 표준 없음 — AIP-148 + Stripe 관행 + DDD 정통의 결합 |"},"evidence_b":{"line_end":518,"line_start":518,"quote":"| D15 | ID 재사용 금지 (soft-delete + hard-delete 모두 NEVER reuse) — audit trail + ULID monotonicity 정합 | AIP148-C2 (uid 재사용 금지 AIP-164 위임), ULID-C5 (monotonic 위반 회피) | `official-vendor-doc` (AIP-148 간접) | UNSUPPORTED_DECISION: AIP-164 원문 raw 미보관 — 재사용 금지의 normative 근거 보강 권고 |"},"proof_manifest":null,"rationale":"ASSERT-010 (D4: server-assigned generation) and ASSERT-015 (D15: no ID reuse) cover different contract properties (generation responsibility vs reuse policy). They compose without conflict.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-6262CFC757C213092AB3","evidence_a":{"line_end":508,"line_start":508,"quote":"| D5 | ID generation layer = **Domain port (`WorkLogIdFactory`) + Application 주입 (use case orchestration)**. 거부: ① Infrastructure-managed (Hibernate generator), ② Domain static self-generation (`WorkLog.create()` 안 `UUID.randomUUID()` — 현재 코드 마이그레이션 대상) | AIP148-C1/C2 (server/system-assigned 관례), DDD factory pattern (factory = 도메인 service, entity 가 아님) | `official-vendor-doc` (AIP-148) + branch decision (DDD factory pattern) | UNSUPPORTED_IMPL_DECISION: type-specific port (`WorkLogIdFactory`) vs generic `IdFactory<WorkLogId>` 주입은 구현 컨벤션 trade-off — skeleton default = type-specific (도메인 의도 명시) |"},"evidence_b":{"line_end":517,"line_start":517,"quote":"| D14 | Resource ID (ULID, server-assigned, URL path) vs Idempotency-Key (UUID v4, client-generated, HTTP header, 24h TTL) —*별개 형식*. fingerprint mismatch → HTTP 422 | BRANDUR-IDEMP-C8~C12 (분리 차원 모두), STRIPE-C1/C5 (별개 + POST 전용),[[raw/branch-notes/feature-rate-limit-idempotency-contract]] D-row | `engineering-blog` (BRANDUR) + `official-vendor-doc` (Stripe) | IETF `Idempotency-Key` draft 의 422 vs Brandur 409 불일치 — IETF draft raw 보강 권고 |"},"proof_manifest":null,"rationale":"ASSERT-011 (D5: ID generation architecture layer = Domain port) and ASSERT-014 (D14: resource ID vs Idempotency-Key distinction + 422) address unrelated properties. No shared property to conflict on.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-5460F93B339BB17D42D2","evidence_a":{"line_end":508,"line_start":508,"quote":"| D5 | ID generation layer = **Domain port (`WorkLogIdFactory`) + Application 주입 (use case orchestration)**. 거부: ① Infrastructure-managed (Hibernate generator), ② Domain static self-generation (`WorkLog.create()` 안 `UUID.randomUUID()` — 현재 코드 마이그레이션 대상) | AIP148-C1/C2 (server/system-assigned 관례), DDD factory pattern (factory = 도메인 service, entity 가 아님) | `official-vendor-doc` (AIP-148) + branch decision (DDD factory pattern) | UNSUPPORTED_IMPL_DECISION: type-specific port (`WorkLogIdFactory`) vs generic `IdFactory<WorkLogId>` 주입은 구현 컨벤션 trade-off — skeleton default = type-specific (도메인 의도 명시) |"},"evidence_b":{"line_end":518,"line_start":518,"quote":"| D15 | ID 재사용 금지 (soft-delete + hard-delete 모두 NEVER reuse) — audit trail + ULID monotonicity 정합 | AIP148-C2 (uid 재사용 금지 AIP-164 위임), ULID-C5 (monotonic 위반 회피) | `official-vendor-doc` (AIP-148 간접) | UNSUPPORTED_DECISION: AIP-164 원문 raw 미보관 — 재사용 금지의 normative 근거 보강 권고 |"},"proof_manifest":null,"rationale":"ASSERT-011 (D5: Domain-port generation layer) and ASSERT-015 (D15: no ID reuse) cover different properties (architecture layer vs reuse policy). They coexist without overlap.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-86C43E13AD7A5310A87C","evidence_a":{"line_end":512,"line_start":512,"quote":"| D9 | **SecureRandom 의무** + constant-time 비교 미적용 — 공개 resource id 는 표준 `equals` 사용. constant-time 은 비밀값 (token/API key/session) 영역으로 [[raw/branch-notes/feature-security-operational-baseline]] 위임 | NANOID-C4 (crypto-strong random 의무), D8 (공개 식별자 분류 — non-PII, 비밀값 아님),[[raw/branch-notes/feature-security-operational-baseline]] D-row (비밀값 비교) | `official-reference` (NANOID) + branch decision (공개 id 의 record `equals` 정합) | NANOID-C4 는 JS 구현 기준이며 JVM `ulid-creator` 의 실제 `SecureRandom` 사용은 라이브러리 버전별 확인 전까지 `needs-confirmation`. 2026-06-01 spec drift 정정: 이전 \"constant-time comparison\" 본문은 D8 공개 식별자 분류와 모순 → 미적용으로 통일 |"},"evidence_b":{"line_end":522,"line_start":522,"quote":"| D19 | sample-portfolio `WorkLogId` fixture = `01ARZ3NDEKTSV4RRFFQ69G5FAV` (26-char uppercase Crockford base32 ULID). Validation regex `^[0-9A-HJKMNP-TV-Z]{26}$` | ULID-C1 (26자 형식), CROCKFORD-C1 (charset) | `official-reference` | baseline branch + project-note §17/§22 가 본 fixture cite — cross-branch 정합 검증 필요 |"},"proof_manifest":null,"rationale":"ASSERT-012 (D9: SecureRandom obligation + comparison policy) and ASSERT-017 (D19: sample fixture value + validation regex) concern unrelated properties (random source vs a concrete fixture/schema). No shared property to conflict on.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-78BF76121885BDC8464D","evidence_a":{"line_end":512,"line_start":512,"quote":"| D9 | **SecureRandom 의무** + constant-time 비교 미적용 — 공개 resource id 는 표준 `equals` 사용. constant-time 은 비밀값 (token/API key/session) 영역으로 [[raw/branch-notes/feature-security-operational-baseline]] 위임 | NANOID-C4 (crypto-strong random 의무), D8 (공개 식별자 분류 — non-PII, 비밀값 아님),[[raw/branch-notes/feature-security-operational-baseline]] D-row (비밀값 비교) | `official-reference` (NANOID) + branch decision (공개 id 의 record `equals` 정합) | NANOID-C4 는 JS 구현 기준이며 JVM `ulid-creator` 의 실제 `SecureRandom` 사용은 라이브러리 버전별 확인 전까지 `needs-confirmation`. 2026-06-01 spec drift 정정: 이전 \"constant-time comparison\" 본문은 D8 공개 식별자 분류와 모순 → 미적용으로 통일 |"},"evidence_b":{"line_end":600,"line_start":600,"quote":"- `UlidCreator.getMonotonicUlid()` → 동일 ms 내 monotonic 동작을 사용한다(ULID-C5). 내부 난수원이 D9의 `SecureRandom` 의무를 충족하는지는 사용 버전 source/README 확인 전까지 `needs-confirmation`이다."},"proof_manifest":null,"rationale":"ASSERT-012 states the D9 SecureRandom obligation; ASSERT-024 supplies the infrastructure-adapter detail (UlidCreator.getMonotonicUlid monotonic within same ms) and the same needs-confirmation caveat about internal SecureRandom compliance. The obligation and the adapter's verification status are compatible, matching the caveat already in ASSERT-012.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-CAE55A9C8D031D45B24B","evidence_a":{"line_end":513,"line_start":513,"quote":"| D10 | DB PK = PostgreSQL 16 `uuid` native (project §34 단일 DB). 거부: `varchar(26/36)`, `BIGINT`, MySQL `BINARY(16)` (out of scope) | RFC9562-C4 (monotonicity backbone), ULID-C4/C5/C6 (정렬·monotonic·binary 레이아웃), PERCONA-UUID-C2~C5 (random vs ordered UUID 일반 원리 —*parallel evidence* only, MySQL InnoDB 직접 적용 불가), project §34 (PostgreSQL 16 단일 DB stack) | `official-standard` (RFC9562·ULID) + `project-ssot` (§34) + `company-case-study` (Percona = parallel only) | UNSUPPORTED_IMPL_DECISION: PostgreSQL 16 `uuid` column index locality (ULID time-ordered insert 의 BTREE page split 완화) 정량 벤치마크 미보관 — 별도 raw 보강 필요 |"},"evidence_b":{"line_end":520,"line_start":520,"quote":"| D17 | **4 ArchUnit rules** (결정 SSOT = 본 branch, 코드 호스팅 = boundary suite):`no_long_id_pk` (`..domain..` 한정), `no_uuid_random_in_controller`, `no_math_random_for_id`, `no_varchar_255_for_id_column` — [[raw/branch-notes/feature-boundary-validation-mapping-contract]] suite (archunit-junit5 1.3.0 per project §34). `no_find_by_id_without_tenant` (D13 보강 rule) 는 `feature-tenant-context-policy` 로 이관 | [[raw/branch-notes/feature-boundary-validation-mapping-contract]] ArchUnit pattern (sibling SSOT, hosts code) + project §34 (archunit-junit5 1.3.0) | `project-ssot` (§34) + branch decision | 본 branch §6 = reference skeleton. 실제 코드 = boundary branch §구현 가이드.`haveExplicitColumnLength()` custom condition 의 1.3.0 API 호환성 검증 필요 |"},"proof_manifest":null,"rationale":"ASSERT-013 (D10: DB PK PostgreSQL uuid native) and ASSERT-016 (D17: ArchUnit rule SSOT) cover different properties (DB storage type vs architecture-enforcement rules). They compose without conflict.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-8AA2F3167590C0AFDB18","evidence_a":{"line_end":513,"line_start":513,"quote":"| D10 | DB PK = PostgreSQL 16 `uuid` native (project §34 단일 DB). 거부: `varchar(26/36)`, `BIGINT`, MySQL `BINARY(16)` (out of scope) | RFC9562-C4 (monotonicity backbone), ULID-C4/C5/C6 (정렬·monotonic·binary 레이아웃), PERCONA-UUID-C2~C5 (random vs ordered UUID 일반 원리 —*parallel evidence* only, MySQL InnoDB 직접 적용 불가), project §34 (PostgreSQL 16 단일 DB stack) | `official-standard` (RFC9562·ULID) + `project-ssot` (§34) + `company-case-study` (Percona = parallel only) | UNSUPPORTED_IMPL_DECISION: PostgreSQL 16 `uuid` column index locality (ULID time-ordered insert 의 BTREE page split 완화) 정량 벤치마크 미보관 — 별도 raw 보강 필요 |"},"evidence_b":{"line_end":851,"line_start":851,"quote":"- database native `uuid` 저장과 random-source 정책을 상속하며 controller 직접 생성을 금지한다."},"proof_manifest":null,"rationale":"ASSERT-013 (D10) is the decision that DB PK = PostgreSQL 16 uuid native; ASSERT-019 states this branch inherits that native uuid storage while forbidding controller-direct ID generation. ASSERT-019 correctly restates the inherited storage (no meaning change) and adds the generation-location prohibition, so it complements rather than drifts.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-6A2BDE53A65A1015D2A3","evidence_a":{"line_end":513,"line_start":513,"quote":"| D10 | DB PK = PostgreSQL 16 `uuid` native (project §34 단일 DB). 거부: `varchar(26/36)`, `BIGINT`, MySQL `BINARY(16)` (out of scope) | RFC9562-C4 (monotonicity backbone), ULID-C4/C5/C6 (정렬·monotonic·binary 레이아웃), PERCONA-UUID-C2~C5 (random vs ordered UUID 일반 원리 —*parallel evidence* only, MySQL InnoDB 직접 적용 불가), project §34 (PostgreSQL 16 단일 DB stack) | `official-standard` (RFC9562·ULID) + `project-ssot` (§34) + `company-case-study` (Percona = parallel only) | UNSUPPORTED_IMPL_DECISION: PostgreSQL 16 `uuid` column index locality (ULID time-ordered insert 의 BTREE page split 완화) 정량 벤치마크 미보관 — 별도 raw 보강 필요 |"},"evidence_b":{"line_end":224,"line_start":224,"quote":"- ID 의 DB primary key 정책 (PostgreSQL 16 `uuid` native — project §34 단일 DB)"},"proof_manifest":null,"rationale":"ASSERT-013 (D10) and ASSERT-026 both assert the DB primary key = PostgreSQL 16 uuid native under project §34's single DB. They agree on subject, predicate (uses), and value; ASSERT-013 merely enumerates the rejected alternatives.","verdict":"CONSISTENT"},{"candidate_id":"SEM-BE3E59E1F6CB72EE4040","evidence_a":{"line_end":517,"line_start":517,"quote":"| D14 | Resource ID (ULID, server-assigned, URL path) vs Idempotency-Key (UUID v4, client-generated, HTTP header, 24h TTL) —*별개 형식*. fingerprint mismatch → HTTP 422 | BRANDUR-IDEMP-C8~C12 (분리 차원 모두), STRIPE-C1/C5 (별개 + POST 전용),[[raw/branch-notes/feature-rate-limit-idempotency-contract]] D-row | `engineering-blog` (BRANDUR) + `official-vendor-doc` (Stripe) | IETF `Idempotency-Key` draft 의 422 vs Brandur 409 불일치 — IETF draft raw 보강 권고 |"},"evidence_b":{"line_end":518,"line_start":518,"quote":"| D15 | ID 재사용 금지 (soft-delete + hard-delete 모두 NEVER reuse) — audit trail + ULID monotonicity 정합 | AIP148-C2 (uid 재사용 금지 AIP-164 위임), ULID-C5 (monotonic 위반 회피) | `official-vendor-doc` (AIP-148 간접) | UNSUPPORTED_DECISION: AIP-164 원문 raw 미보관 — 재사용 금지의 normative 근거 보강 권고 |"},"proof_manifest":null,"rationale":"ASSERT-014 (D14: fingerprint-mismatch 422) and ASSERT-015 (D15: no ID reuse) cover different properties (failure response vs reuse policy). No shared property to conflict on.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-1550757A6E27F224DF05","evidence_a":{"line_end":520,"line_start":520,"quote":"| D17 | **4 ArchUnit rules** (결정 SSOT = 본 branch, 코드 호스팅 = boundary suite):`no_long_id_pk` (`..domain..` 한정), `no_uuid_random_in_controller`, `no_math_random_for_id`, `no_varchar_255_for_id_column` — [[raw/branch-notes/feature-boundary-validation-mapping-contract]] suite (archunit-junit5 1.3.0 per project §34). `no_find_by_id_without_tenant` (D13 보강 rule) 는 `feature-tenant-context-policy` 로 이관 | [[raw/branch-notes/feature-boundary-validation-mapping-contract]] ArchUnit pattern (sibling SSOT, hosts code) + project §34 (archunit-junit5 1.3.0) | `project-ssot` (§34) + branch decision | 본 branch §6 = reference skeleton. 실제 코드 = boundary branch §구현 가이드.`haveExplicitColumnLength()` custom condition 의 1.3.0 API 호환성 검증 필요 |"},"evidence_b":{"line_end":530,"line_start":530,"quote":"> Trace: D1 (ULID), D4 (server-assigned), D5 (domain factory), D17 (`no_long_id_pk`)"},"proof_manifest":null,"rationale":"ASSERT-016 (D17 ArchUnit rule SSOT, code hosting delegated to boundary suite) and ASSERT-022 (Domain-layer WorkLogId implementation traces to D1/D4/D5/D17) coexist: the domain-layer implementation cites D17 among its traces while the branch remains the D17 decision SSOT. Compatible detail, no takeover.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-558B4933BB1C716BA273","evidence_a":{"line_end":520,"line_start":520,"quote":"| D17 | **4 ArchUnit rules** (결정 SSOT = 본 branch, 코드 호스팅 = boundary suite):`no_long_id_pk` (`..domain..` 한정), `no_uuid_random_in_controller`, `no_math_random_for_id`, `no_varchar_255_for_id_column` — [[raw/branch-notes/feature-boundary-validation-mapping-contract]] suite (archunit-junit5 1.3.0 per project §34). `no_find_by_id_without_tenant` (D13 보강 rule) 는 `feature-tenant-context-policy` 로 이관 | [[raw/branch-notes/feature-boundary-validation-mapping-contract]] ArchUnit pattern (sibling SSOT, hosts code) + project §34 (archunit-junit5 1.3.0) | `project-ssot` (§34) + branch decision | 본 branch §6 = reference skeleton. 실제 코드 = boundary branch §구현 가이드.`haveExplicitColumnLength()` custom condition 의 1.3.0 API 호환성 검증 필요 |"},"evidence_b":{"line_end":233,"line_start":233,"quote":"- ArchUnit rule SSOT — anti-pattern 차단 (`no_long_id_pk`, `no_uuid_random_in_controller`, `no_math_random_for_id`, `no_varchar_255_for_id_column`)"},"proof_manifest":null,"rationale":"ASSERT-016 and ASSERT-027 both assert the same subject (feature-resource-identifier-contract branch) owns the same ArchUnit rule SSOT with the identical four rules (no_long_id_pk, no_uuid_random_in_controller, no_math_random_for_id, no_varchar_255_for_id_column). Same owner, same object, agreeing; ASSERT-016 only adds the clarification that code hosting sits in the boundary suite.","verdict":"CONSISTENT"},{"candidate_id":"SEM-D80904049DD8F091ADDA","evidence_a":{"line_end":851,"line_start":851,"quote":"- database native `uuid` 저장과 random-source 정책을 상속하며 controller 직접 생성을 금지한다."},"evidence_b":{"line_end":224,"line_start":224,"quote":"- ID 의 DB primary key 정책 (PostgreSQL 16 `uuid` native — project §34 단일 DB)"},"proof_manifest":null,"rationale":"ASSERT-019 (branch inherits database native uuid storage + random-source policy, forbids controller-direct generation) and ASSERT-026 (DB PK policy = PostgreSQL 16 uuid native) agree on the inherited uuid storage. ASSERT-019 adds the generation-location prohibition, complementing the DB PK policy without altering it.","verdict":"COMPLEMENTARY"},{"candidate_id":"SEM-C7E81FAC40CEBC08AD73","evidence_a":{"line_end":530,"line_start":530,"quote":"> Trace: D1 (ULID), D4 (server-assigned), D5 (domain factory), D17 (`no_long_id_pk`)"},"evidence_b":{"line_end":233,"line_start":233,"quote":"- ArchUnit rule SSOT — anti-pattern 차단 (`no_long_id_pk`, `no_uuid_random_in_controller`, `no_math_random_for_id`, `no_varchar_255_for_id_column`)"},"proof_manifest":null,"rationale":"ASSERT-022 (Domain-layer implementation maps to D1/D4/D5/D17) and ASSERT-027 (branch owns the ArchUnit rule SSOT) coexist: the domain layer traces to D17 among others while the branch owns the D17 rule SSOT. Different facets (implementation trace vs rule ownership), no conflict.","verdict":"COMPLEMENTARY"}]},"auditor":{"contract_version":"semantic-coherence/v1","model_id":"claude-opus-4-8","run_id":"af0fd5585dd959ed2"},"auditor_contract_sha256":"1cbc67c27e5183a272687f635e3682a26d56785a1cf144da562eb7edd8f6cbce","coverage":{"candidate_pairs":26,"dropped_pairs":0,"eligible_surfaces":6,"processed_pairs":26,"processed_surfaces":6},"document_id":"adf2184e2ab560ff995aafb80430a3b69b635754a759d3a5a829be34786e98f4","document_sha256":"6d9b7e25716697a12113c493964bdf391df37c6b29e981ef49ca22ba3598660f","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":"393544a62d94c30643c1be9fd112f0c5748eba03546e6e61d12802a542853fa2","subject":"raw/branch-notes/feature-resource-identifier-contract.md","typed_contract_graph_sha256":"481fc3335a494c454ab13ad1d6a6ddccdec99aa8e083f6720ff8886bfa19cc89","verdict":"PASS"}
|