Files
llm-wiki/raw/branch-notes/feature-sample-domain-contract-fixture.md

37 KiB
Raw Permalink Blame History

title, source_type, status, branch, parent_branch, related_projects, governing_docs, tags, created, target_merge, status_label, id, kind, project, work_item, inherits, refines, overrides, depends_on, contract_packet, contract_packet_sha256
title source_type status branch parent_branch related_projects governing_docs tags created target_merge status_label id kind project work_item inherits refines overrides depends_on contract_packet contract_packet_sha256
branch / feature-sample-domain-contract-fixture branch-note raw feature-sample-domain-contract-fixture
ca-skeleton
wiki/projects/ca-tmpl/sample-fixture-and-adoption
branch
ca-skeleton
sample-domain
contract-fixture
2026-05-21 in-progress BR-CA-SKELETON-OPERATIONAL-CONTRACT-014 project-work-item ca-skeleton-operational-contract WI-CA-SKELETON-OPERATIONAL-CONTRACT-014
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-MODULE-LAYOUT-001@1
1 6d4baf1c3d8e37e66b1a0ad293a732d9e1bf0462c598959d9dc0ccdc12e49964

branch: feature-sample-domain-contract-fixture

Layer: raw/branch-notes/ — skeleton 계약 검증을 위한 sample domain fixture 기준을 정의합니다.

부모 (필수)

ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note 의 운영 계약 중 해당 영역 (§<관련 섹션>) 의 결정/근거/금지 사항을 정제한다.

브랜치 계약 패킷

  • 생성 시 프로젝트 개정: 1
  • 패킷 스키마: contract_packet: 1
  • 완료 조건: sample domain fixture가 module contract를 검증하고 제거 smoke가 통과한다

상속한 프로젝트 결정

Decision Ref Project Summary Branch Application Source
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-MODULE-LAYOUT-001@1 Gradle multi-module에서 domain-core·application-core·adapter-*·shared-contract·app-bootstrap·sample-portfolio 책임을 분리한다 Work Item 완료 조건에 적용 raw/project-notes/ca-skeleton-operational-contract

브랜치 지역 결정

기존 branch-local 결정은 아래 ## Decision Evidence Map / 결정-근거 매핑의 D-row가 소유하며 이 packet에서 복제하지 않는다.

Decision ID Decision Relation Supporting Claims Status

선언한 예외

Override ID Overrides Reason Approval Status

목표

도메인/비즈니스 로직은 제거하지만, 샘플 도메인이 없으면 경계 validation, mapper, repository capability, transaction, response contract를 실제 흐름으로 검증할 수 없습니다. sample은 기능이 아니라 contract fixture입니다.

  • 이슈:
  • PR:

범위

포함 범위

  • sample-portfolio fixture.
  • create/read/update/delete 최소 흐름.
  • validation/not found/conflict/optimistic lock fixture.
  • pagination fixture.
  • repository capability fixture.
  • idempotent command fixture.
  • sample package/module/profile 격리 기준.

제외 범위

  • 실제 서비스 도메인 기능.
  • portfolio/blog/interview 직접 파생.
  • production feature로 노출.

근거 (필수, 최소 1개+)

본 branch의 결정 근거. 상세 비교는 §외부 근거 / 대안 조사 (있다면) 참조.

Source 정당화하는 결정
raw/official-docs/sample-spring-petclinic-github Spring 공식; "demo지 best-practice 아님" 본인 선언
raw/official-docs/sample-realworld-gothinkster-github cross-stack spec; 풍부하나 minimum 아니고 contract scenario 부재
raw/official-docs/sample-microservices-spring-cloud-github fixture 수준 초과; microservices variant

외부 근거 / 대안 조사 (2026-05-22 — Group G-H: Sample Domain Fixture)

본 branch의 sample-portfolio (12 scenario matrix + 6-field minimum model + OPEN→IN_PROGRESS→CLOSED state machine + optimistic lock + idempotency key) 결정에 대한 외부 source.

  • 채택 결정 (skeleton contract 검증 fixture로서 sample-portfolio):
    • (ca-tmpl 고유; sample은 demo/tutorial이 아닌 contract 검증 도구라는 목적 정의)
  • 검토한 대안:
  • 비교 핵심: ca-tmpl sample-portfolio은 12 scenario × 6 model × state machine이 skeleton contract(envelope/error/capability/transaction/idempotency)를 모두 트리거하는 minimal fixture. Petclinic/RealWorld는 demo/teaching 목적이라 contract 검증 매트릭스 부재.

TODO

TODO drained 2026-05-22 — 결정은 아래 "결정 사항" / "Sample-portfolio Matrix" / "Minimum Model" / "테스트 계약" 참조. package/module/profile 격리, CRUD fixture, validation/not found/conflict/lock fixture, pagination, repository capability, idempotent command, production runtime 비활성화 구조 모두 Sample-portfolio Matrix row 또는 결정 라인으로 반영됨. 잔존 TODO 없음.

Work Item Contract

각 TODO는 아래 판정 단위로 재작성되어야 canonical 승급 가능합니다. TODO가 단순히 기준 작성으로 남아 있으면 이 branch는 완료로 보지 않습니다.

field required rule
Decision yes 구현자가 선택해야 하는 기본값
Allowed yes 허용되는 예외와 조건
Forbidden yes 절대 금지되는 구현/문서 상태
Required registry update conditional error/env/header/log/metric/capability 변경 시 필수
Required contract test yes 계약 위반 시 실패해야 하는 테스트
Failure condition yes review/build에서 실패로 판정할 상태
Canonical extraction target yes wiki/projects 승급 위치

진행 중 메모

  • sample은 business feature가 아니라 skeleton contract를 보여주는 living example입니다.
  • 2026-06-10 구현: delegated 항목(idempotency dedup/storage, sample-off/profile isolation, dual-mode CI)은 제외하고, 본 branch covered-here gap이던 WorkLogStatus 상태 머신과 WorkLogOwner minimum model을 코드에 반영했다. 상태 전이는 OPEN -> IN_PROGRESS -> CLOSED만 허용하며, status=OPEN 되돌리기와 CLOSED 상태의 내용 변경은 domain invariant conflict로 실패한다.
  • 2026-06-10 구현 파일: WorkLog.java, WorkLogStatus.java, WorkLogOwner.java, WorkLogInvariantException.java, UpdateWorkLogCommand.java, UpdateWorkLogUseCase.java, UpdateWorkLogRequest.java, WorkLogController.java, WorkLogWebMapper.java, response DTO 2종, WorkLogEntity.java, WorkLogPersistenceMapper.java, V2__work_log.sql, 관련 domain/application/persistence/web tests.
  • 2026-06-10 검증: focused RED는 :sample-portfolio:compileTestJava에서 missing WorkLogStatus/WorkLogOwner/status accessor/command status patch/CLOSED_WORKLOG_MUTATION으로 실패 확인. GREEN 후 :sample-portfolio:test, verifyCleanArchitectureDependencies, :app-bootstrap:test --tests '*CleanArchitectureTest', test, check 모두 exit 0.
  • 2026-06-10 owner=principal capture (Option A, spec ca-tmpl docs/superpowers/specs/2026-06-10-worklog-owner-principal-capture-design.md): WorkLogOwner 가 더 이상 상수 sample-owner 가 아니라 create 시점 인증 principal 의 subject. CreateWorkLogCommand.owner(String) 신설 → WorkLogControllerSecurityContextHolderAuthenticatedUser.idpUserId() 를 주입(currentOwnerSubject(), RateLimitKeyResolver 와 동일 null-safe idiom) → CreateWorkLogUseCase/BatchCreateWorkLogsUseCaseWorkLogOwner.of(cmd.owner()) 로 생성. privacy: WorkLogResponse/WorkLogSummaryResponse 에서 raw owner 제거(WorkLogWebMapper 정합) — raw principal id 응답 비노출. pseudonymization(raw/branch-notes/feature-data-retention-privacy-contract)·owner-scoped authz(raw/branch-notes/feature-authentication-authorization-contract D2 ABAC 보류)는 sibling SSOT 위임. TDD RED→GREEN: WorkLogUseCasesTest.create_sets_owner_from_command_principal, WorkLogControllerWireTest.create_captures_authenticated_principal_as_owner, ..._response_does_not_expose_owner(privacy). 게이트 :sample-portfolio:test·verifyCleanArchitectureDependencies·*CleanArchitectureTest exit 0.

결정 사항 (decisions)

  • 2026-05-21: sample domain fixture는 필요.
  • 2026-05-22: sample domain 이름은 sample-portfolio을 기본값으로 둠.
  • 2026-05-22: sample은 production feature가 아니라 contract fixture이며 prod profile에서는 기본 비활성화.
  • 2026-05-22: worklog status transition, owner/assignee policy, optimistic lock, idempotent create, pagination을 검증 대상으로 둠.
  • 2026-05-22: sample-portfolio fixture의 SSOT는 이 branch. verification/DX/scorecard/onboarding branch는 scenario와 minimum model을 소비만 함.
  • 2026-05-22: sample-off smoke scenario는 feature-sample-removal-adoption-contract와 함께 release-blocking verification 대상.

판정 기준

구분 기준
Decision sample-portfolio을 skeleton contract fixture로 사용
Allowed 실제 프로젝트 생성 시 sample-off profile로 runtime 노출 차단. fork cleanup은 선택 사항이며 template의 sample-portfolio module은 fixture/reference로 유지
Forbidden sample 결과를 portfolio/blog/interview 산출물로 직접 파생
Required fixture create/read/update/close, validation, not found, conflict, optimistic lock, pagination, repository capability, idempotency
Failure condition sample 없이 boundary/repo/transaction/error contract test를 검증하려 하면 실패

결정-근거 매핑

본 branch 의 핵심 결정 (sample-portfolio = contract fixture, 12 scenario matrix, 6-field minimum model, state machine, optimistic lock, idempotency) 은 ca-tmpl 고유 모델. 외부 raw 는 "기존 sample 들이 contract fixture 목적에는 부적합" 이라는 대조 근거만 제공.

Decision ID Decision Supporting Claims Evidence Strength Open Risk
D1 sample domain fixture 가 필요 (skeleton 계약 검증 도구) raw/official-docs/sample-spring-petclinic-github.md#SAMPLE-PC-C4 (PetClinic 의 "best practice 아님" disclaimer — 기존 sample 차용 위험 근거) official-vendor-doc (대조 근거) "fixture 가 필요" 자체는 ca-tmpl 고유 결정 — 외부 raw 는 기존 sample 의 한계만 증명
D2 sample domain 이름 = sample-portfolio (기본값) (ca-tmpl 고유 명명; 외부 raw 가 worklog 도메인을 권장하지 않음) UNSUPPORTED_DECISION naming 자체 외부 근거 없음
D3 sample 은 production feature 가 아닌 contract fixture, prod profile 기본 비활성화 raw/official-docs/sample-spring-petclinic-github.md#SAMPLE-PC-C1 (PetClinic 이 demo 목적임을 시인), raw/official-docs/sample-realworld-gothinkster-github.md#SMP-RW-C1 (RealWorld 가 demo apps 묶음임) official-vendor-doc + engineering-blog (RealWorld 는 OSS community spec → 본 branch 가 engineering-blog 로 분류; 공식 best practice 격상 금지) "sample = fixture" 정의 자체는 ca-tmpl 고유. 외부 raw 는 기존 sample 들이 demo 라는 사실만 증명
D4 worklog status transition / owner/assignee policy / optimistic lock / idempotent create / pagination 을 검증 대상 raw/official-docs/sample-spring-petclinic-github.md#SAMPLE-PC-C4 ("not a best practice" — PetClinic 에 이 시나리오 없음을 대조), raw/official-docs/sample-realworld-gothinkster-github.md#SMP-RW-C2 (RealWorld API spec 이 corner case cover 안 함 시사), raw/official-docs/sample-realworld-gothinkster-github.md#SMP-RW-C3 (modularity 만 보장 — concurrency / optimistic lock 등 corner case 미보장은 "Does not prove" 컬럼) official-vendor-doc + engineering-blog (대조 근거만) optimistic lock / idempotency 가 본 branch 가 필수로 둔다는 사실은 ca-tmpl 고유 — 외부 표준이 권장하지 않음
D5 sample-portfolio fixture SSOT 는 본 branch. verification/DX/scorecard/onboarding branch 는 scenario / minimum model consume only (ca-tmpl 고유 SSOT 정책; 외부 raw 직접 근거 없음) UNSUPPORTED_DECISION sibling branch 간 governance 결정
D6 sample-off smoke scenario 는 feature-sample-removal-adoption-contract 와 함께 release-blocking verification (sibling branch 간 contract; 외부 raw 직접 근거 없음) UNSUPPORTED_DECISION sibling branch 의 dual-mode CI matrix 와 일관성 외 외부 표준 없음

구현 가이드

결정 (D1~D6) 이 "무엇 을" 이라면, 본 §는 sample-portfolio fixture 가 ca-tmpl /home/donghyeon/workspace/ca-tmpl/src/sample-portfolio/실제로 어떻게 구현됐는지의 코드 정합 명세다 (2026-06-10 ground-truth 대조). 노트(2026-05-22)가 설계로 적은 모델/명명과 코드가 어긋난 지점은 §Audit & Findings 에 drift 로 분리하고, 본 §에는 코드로 확인된 사실(actually-implemented)아직 코드 없는 항목(planned/delegated) 만 남긴다.

3-rule (CLAUDE.md §15.5): 각 row 는 Decision ID + 근거(코드 anchor 또는 Claim ID). 근거 없는 임의 detail = UNSUPPORTED_IMPL_DECISION. 본 branch 결정 범위 밖(다른 owner branch 소유 계약) detail = OUT_OF_BRANCH_SCOPE 로 owner 에 위임.

1. Fixture scenario → 트리거 계약 → 실제 검증 test (D1, D4)

Trace: D1 (fixture 필요) + D4 (worklog 검증 시나리오) → ca-tmpl src/sample-portfolio. 등급은 src/ grep 으로 확정(2026-06-10; note 자기보고 아님).

  • UNSUPPORTED_IMPL_DECISION: test 클래스 명명 규약. 노트는 Sample{ScenarioName}ContractTest.java / SampleIdempotentReplayContractTest 로 추정했으나 실제 규약은 {Domain}{Layer}{Type}Test (WorkLogControllerWireTest, WorkLogAuthorizationContractTest, GetRepoStatsUseCaseTest). trade-off: 추정 명명을 따르면 신규 test 가 기존 규약과 어긋남 → 실제 규약 채택.
노트 scenario 트리거 계약 unit 실제 검증 (file · method, ca-tmpl@2026-06-10) 등급
create success request mapper · command validation · WRITE capability · transaction · response mapper WorkLogControllerWireTest.batch_create_all_ok_returns_array (L286) · WorkLogTest.create_keeps_assigned_id_and_fields (L21) · CreateWorkLogUseCase @UseCaseCapability(WRITE, NOT_IDEMPOTENT) (L23) actually-implemented
create validation failure structured validation details · client-safe msg WorkLogControllerWireTest.create_with_blank_title_fails_validation (L231) / create_with_unknown_field_is_rejected_b1 (L239) / ..._unmappable_link_routes_to_mapping_failed_b3 (L248) · domain WorkLogInvariantTest.blank_title_is_rejected_with_a_safe_reason_on_create (L28) actually-implemented
get not found WORKLOG_NOT_FOUND · 404 · retryable=false WorkLogControllerWireTest.get_missing_returns_404_worklog_not_found (L208) · PortfolioErrorCode.WORKLOG_NOT_FOUND(Category.NOT_FOUND,404,false) (adapter/web/error/PortfolioErrorCode.java) actually-implemented
list pagination page meta · sort/filter WorkLogControllerWireTest.list_is_wrapped_in_envelope_with_page_meta (L79) / empty_list_is_data_array_not_null_with_zero_total (L92) / size_over_cap_is_400_validation_with_field_and_code (L102) actually-implemented
optimistic lock conflict version → ETag/If-Match → HTTP 412 WorkLogControllerWireTest.patch_with_stale_if_match_returns_412 (L152) / get_emits_etag_header (L136) / patch_with_matching_if_match_applies_update (L161) · WorkLog.version:Long (domain/worklog/WorkLog.java L37) actually-implemented · 메커니즘 OUT_OF_BRANCH_SCOPEraw/branch-notes/feature-api-contract-baseline D15
unauthorized update auth/authz 분리 · fail-closed WorkLogAuthorizationContractTest.unauthenticated_caller_is_denied_fail_closed (L125) / authenticated_user_without_close_permission_is_denied_delete (L102) · WorkLogAuthorizationE2ETest actually-implemented
outbound forbidden use case EXTERNAL_OUTBOUND_ALLOWED capability gate GetRepoStatsUseCaseTest.delegates_to_port (L14) · GetRepoStatsUseCase @UseCaseCapability(READ_ONLY, externalOutboundAllowed=true) (L15) actually-implemented · capability OUT_OF_BRANCH_SCOPEfeature-repository-access-permission-contract
idempotent create replay Idempotency-Key 헤더 수용 · dedup storage 헤더 수용: WorkLogControllerWireTest.post_accepts_idempotency_key_header (L196) — actually-implemented. dedup/replay storage: 코드 없음planned · OUT_OF_BRANCH_SCOPEfeature-rate-limit-idempotency-contract (Idempotency-Key header owner) planned (dedup)
update invalid transition (노트: status state machine) 코드에 status state machine 없음 — 도메인은 rename()/recategorize() (WorkLog.java) 만, OPEN→IN_PROGRESS→CLOSED 부재 → §Audit MODEL_DRIFT. 상태전이 검증 test 없음 planned / drift
sample disabled startup prod profile isolation 코드에 @Profile/@ConditionalOnProperty 없음APP_SAMPLE_ENABLED env 만 registry 선언 → planned · OUT_OF_BRANCH_SCOPEfeature-sample-removal-adoption-contract planned
sample-off smoke sample/core decoupling · dual-mode CI CI workflow / smoke test 코드 없음planned · OUT_OF_BRANCH_SCOPEfeature-sample-removal-adoption-contract (dual-mode CI matrix owner) planned

코드가 노트 matrix 를 초과 cover (SCENARIO_EXPANSION, §Audit): 실제 fixture 는 12-scenario 외에 ETag 304 (get_with_matching_if_none_match_returns_304_no_body L145), HEAD 지원 (L173), batch atomic (batch_create_is_atomic_one_bad_item_fails_whole_batch L310), sort syntax 검증 (native accept / jsonapi-prefix reject L111/L119), ULID 정규화 (L223) 도 검증한다 — 노트 §Sample-portfolio Matrix 갱신 시 반영 권고.

2. Sample module 격리 + SSOT governance 정적 강제 (D2, D5)

Trace: D2 (이름 sample-portfolio) + D5 (본 branch 가 fixture SSOT) → 실제 package + ArchUnit.

  • UNSUPPORTED_IMPL_DECISION: 격리 강제 메커니즘 선택(ArchUnit vs Gradle module 경계 vs @Profile). 노트는 원칙(production 노출 차단)만 결정 — 실제 코드는 ArchUnit 채택. trade-off: 컴파일 차단(Gradle 경계)보다 약하나 단일 test 모듈에서 검증 가능.
강제 대상 메커니즘 (실제) 등급
production code 가 sample import 금지 (D5 SSOT, D2 격리) ArchUnit production_code_does_not_depend_on_sample_portfolio = noClasses().that().resideOutsideOfPackage("..sample.portfolio..").should().dependOnClassesThat().resideInAPackage("..sample.portfolio..") (app-bootstrap/.../architecture/CleanArchitectureTest.java L576581) actually-implemented
sample package 명명 (D2) dev.caskeleton.sample.portfolio.* (domain/application/adapter 4-layer) actually-implemented
prod runtime 노출 차단 (D3) APP_SAMPLE_ENABLED (default true, prod_profile_must_be_false) — owner_branch=feature-sample-removal-adoption-contractOUT_OF_BRANCH_SCOPE. 런타임 @Profile/@ConditionalOnProperty 코드 아직 없음 documented-only · delegated
sibling 이 scenario/minimum model 독자 변경 금지 (D5) 자동 강제 메커니즘 없음 — wikilink cross-ref + review. UNSUPPORTED_IMPL_DECISION: 사회적 강제만, 정적 분석 불가 documented-only

엣지·실패·의존

R4 캡처. sample-portfolio fixture 는 자체 계약을 거의 소유하지 않고 sibling branch 계약을 소비/트리거한다 — 그 계약이 바뀌면 fixture test 가 깨진다.

  • 실패·엣지 경로:
    • stale If-Match → 412: 동시 update 시 version 불일치. 기대: patch_with_stale_if_match_returns_412 (412 PRECONDITION_FAILED, Category.CONFLICT). raw JPA optimistic-lock exception 이 presentation 까지 전파되면 실패.
    • blank/누락 title: web @NotBlank (syntax) + domain requireValidTitle() (invariant, WorkLogInvariantException.Reason.TITLE_BLANK) 2중 방어 — domain 검증이 web 뒤에서도 독립 동작 (WorkLogInvariantTest.null_title_stays_a_null_check_not_an_invariant_violation L43).
    • unknown/unmappable field: create_with_unknown_field_is_rejected_b1 / ..._unmappable_link_routes_to_mapping_failed_b3 — boundary-validation 계약 위반 시 실패.
    • fail-closed authz: 미인증 호출이 deny 안 되면 (unauthenticated_caller_is_denied_fail_closed) 실패.
    • batch 부분 실패: batch_create_is_atomic_one_bad_item_fails_whole_batch — 1건 실패가 전체 롤백 안 되면 transaction 경계 위반.
    • idempotency dedup 미구현: 동일 Idempotency-Key 재요청 시 현재 헤더만 수용, 중복 생성 방지 storage 없음 → replay 시 worklog 중복 가능 (planned gap).
    • owner raw 노출 금지 (privacy): owner 는 raw principal id 이므로 응답 payload 에 노출되면 실패 — create_response_does_not_expose_owner / patch 응답 $.data.owner 부재로 강제. pseudonymized 형태 준비 시(privacy branch) 재노출 가능.
  • 다른 계약 의존 (owner branch + 소비 대상):

Audit & Findings (2026-06-10 ca-tmpl ground-truth 대조)

/branch-spec 가 ca-tmpl src/sample-portfolio + registry + 거버닝 canonical 과 대조해 발견한 drift. 사용자/canonical 결정 영역이므로 자동 rewrite 하지 않고 정합 권고만 남긴다 (CLAUDE.md §11, branch-spec §2).

  • MODEL_DRIFT (3-layer — 가장 중요) — 동일 fixture 가 세 층에서 다른 모델:
    • 거버닝 canonical wiki/projects/ca-tmpl/sample-fixture-and-adoption: sample-ticket · TicketStatus/TicketOwner · state machine OPEN→IN_PROGRESS→CLOSED.
    • 본 노트 (2026-05-22, §Minimum Model): sample-portfolio · WorkLogStatus(OPEN/IN_PROGRESS/CLOSED)/WorkLogOwner.
    • 실제 코드 (2026-06-10): sample-portfolio · WorkLog{title:String, category:WorkCategory(INFRASTRUCTURE/DATABASE/BACKEND/PLATFORM), summary, content, techStack, links, period:Period, version:Long}status state machine 없음, owner 필드 없음, title 은 VO 아닌 String invariant (domain/worklog/WorkLog.java L2050).
    • 권고: §Minimum Model 과 canonical 6-field(Ticket*) 을 실제 WorkLog 모델로 정합 갱신. "update invalid transition" scenario 는 state machine 부재로 rename/recategorize + 412 conflict 로 재정의.
    • 2026-06-10 구현 갱신: 실제 코드에 WorkLogStatus(OPEN, IN_PROGRESS, CLOSED)WorkLogOwner가 추가되어 본 노트의 minimum model/state-machine drift 중 sample-portfolio 코드 gap은 해소됨. owner 후속 갱신: owner 가 create 시점 인증 principal subject 로 capture(상수 sample-owner 아님), pseudonymization·owner-scoped authz 는 sibling 위임, 응답 payload 비노출 — §Minimum Model owner 목적도 이에 맞춰 갱신함(§진행 중 메모 2026-06-10 owner=principal capture 참조). canonical sample-ticket 명명 drift는 별도 /ingest/canonical 갱신 영역으로 남음.
  • NAME_DRIFT: canonical 은 sample-ticket, 노트/코드는 sample-portfolio. canonical(status=draft) /ingest 재실행 시 정합 권고.
  • TEST_NAMING_DRIFT: 노트 추정 Sample{Scenario}ContractTest.java ≠ 코드 실제 WorkLogControllerWireTest/WorkLogAuthorizationContractTest/GetRepoStatsUseCaseTest (§구현 가이드 1 에 실제 규약 반영).
  • ERROR_CODE_DRIFT: 노트의 RESOURCE_NOT_FOUND/RESOURCE_CONFLICT ≠ 코드 PortfolioErrorCode.WORKLOG_NOT_FOUND(Category.NOT_FOUND,404,false) (sample 전용 enum, registry row 아님). conflict 는 별도 코드가 아니라 412/If-Match 경로.
  • SCENARIO_EXPANSION (Should-fix): 실제 fixture 가 노트 12-scenario 초과 — ETag/304, HEAD, batch atomic, sort syntax, ULID 정규화 추가 검증 (§구현 가이드 1 하단).
  • STATUS_DRIFT: 노트 §Claims To Verify / §Sample-portfolio Matrix 가 전부 planned 이나 대다수 이미 actually-implemented (§구현 가이드 1 등급). 머지//ingest 전 등급 재판정 필요.
  • 무근거 미수정: 위는 전부 정합 권고. 실제 갱신은 사용자가 모델 SSOT(노트 vs canonical) 방향을 확정한 뒤 — /branch-spec 는 drift surface 만 수행.

검증해야 할 주장

외부 sample 들과의 비교는 대조 근거이지 ca-tmpl sample-portfolio 의 동작 보장이 아님. 실제 구현 후 검증 대상.

Claim Why uncertain How to verify Status
sample-portfolio 의 12 scenario 가 skeleton 의 모든 contract (envelope / error / capability / transaction / idempotency / boundary / lock) 를 누락 없이 트리거 12 scenario 망라성은 본 branch 가 정의 — 외부 표준이 권장 scenario 목록을 제공하지 않음 각 scenario 별 contract test 작성 → contract 단위 (envelope / error / etc) coverage matrix 작성 → 누락 항목 발견 시 scenario 추가 planned
sample controller 가 request DTO 를 application 으로 직접 넘기지 않는다 architecture rule 위반은 ArchUnit 등으로만 자동 차단 가능 ArchUnit rule + contract test (Sample{ScenarioName}ContractTest.java) 작성 → CI 에서 실행 planned
sample domain object 가 response 로 직접 노출되지 않는다 직렬화 mapper 가 누락되어도 컴파일은 통과 — 별도 검증 필요 response mapper 강제 ArchUnit rule + integration test 에서 response payload 검사 planned
sample write use case 가 repository capability 없이 write 하면 실패한다 capability 선언이 없어도 코드는 동작 가능 — gate 명시 필요 EXTERNAL_OUTBOUND_ALLOWED 등 capability annotation + ArchUnit rule + contract test planned
optimistic lock / conflict / not found 가 structured error 로 분류된다 raw JPA exception 전파 위험 — error mapping layer 가 없으면 누락 가능 error registry 의 RESOURCE_NOT_FOUND / RESOURCE_CONFLICT 등 매핑 + contract test planned
sample-off 상태에서 core app smoke test 와 core contract test 가 유지된다 sample-on / sample-off dual-mode 가 본 branch + feature-sample-removal-adoption-contract 가 정의 CI matrix.profile = [sample-on, sample-off] 양쪽 green planned
다른 branch 가 sample-portfolio scenario / minimum model 을 독자 변경하지 않는다 SSOT 정책 (D5) 의 사회적 강제 — 자동 차단 메커니즘 없음 wikilink 기반 cross-reference + branch note review 시 차이 확인. 정적 분석은 어려움 planned
PetClinic / RealWorld / Microservices sample 과 ca-tmpl sample-portfolio 의 비교 매트릭스가 wiki/concepts 에 추출 가능 비교는 본 branch 외부 근거 / 대안 조사 섹션에 있으나 wiki 변환 시 PetClinic disclaimer (SAMPLE-PC-C4) 의 강한 부정 표현이 외부 산출물에 보존되어야 함 /ingest 시 PetClinic 의 "not a best practice" 인용 verbatim 유지 + RealWorld 의 engineering-blog 강도 표시 유지 planned
sample disabled startup 시 prod profile 에서 sample endpoint 가 노출되지 않는다 profile isolation 은 Spring 의 @Profile 만으로는 실수 가능 — 별도 contract test 필요 sample-off profile 로 startup → endpoint 목록에 sample.worklog 부재 검사 planned
idempotency key 메커니즘이 retry 시 worklog 중복 생성을 막는다 idempotency storage 가 없거나 잘못 구현되면 중복 발생 — 외부 표준 부재 (자체 정책) replay 시뮬레이션 contract test (SampleIdempotentReplayContractTest) planned

Sample-portfolio Matrix

scenario verifies failure condition
create worklog success request mapper, command validation, write capability, transaction, response mapper controller가 domain/entity를 직접 생성하거나 반환
create worklog validation failure structured validation details, client-safe message malformed request가 raw exception 또는 500으로 노출
idempotent create replay idempotency storage, duplicate write 방지, replay meta retry 시 worklog 중복 생성
get worklog not found RESOURCE_NOT_FOUND, 404, retryable false not found가 500으로 변환
list worklogs pagination pagination meta, sorting/filtering pagination 정보가 data payload에 섞임
update worklog invalid transition domain invariant, conflict mapping CLOSED worklog update가 성공
optimistic lock conflict persistence failure mapping raw JPA exception이 presentation까지 전파
unauthorized update auth/authz separation, privacy log token/principal raw value가 log에 남음
outbound forbidden use case EXTERNAL_OUTBOUND_ALLOWED capability capability 없이 외부 adapter 호출
sample disabled startup prod profile isolation prod profile에서 sample endpoint 노출
sample-off smoke sample/core decoupling sample-off 상태에서 core contract test 실패

Minimum Model

model required fields purpose
WorkLogId opaque id path variable mapping, value object
WorkLogTitle normalized non-empty string syntax validation + domain invariant
WorkLogStatus OPEN, IN_PROGRESS, CLOSED enum serialization + conflict
WorkLogVersion numeric version optimistic locking
WorkLogOwner create 시점 인증 principal subject (raw IdP sub) 신원 capture (actually-implemented, 2026-06-10). pseudonymization·owner-scoped authz 는 sibling SSOT 위임(미적용), 응답 payload 비노출(privacy)
IdempotencyKey opaque key duplicate write prevention

테스트 계약

  • sample controller가 request DTO를 application으로 직접 넘기면 실패.
  • sample domain object가 response로 직접 노출되면 실패.
  • sample write use case가 repository capability 없이 write하면 실패.
  • sample optimistic lock/conflict/not found가 structured error로 분류되어야 함.
  • sample 제거 후 core app smoke test와 core contract test가 유지되어야 함.
  • 다른 branch가 sample-portfolio scenario/minimum model을 독자 변경하면 실패.

관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)

/coverage 가 채우는 생성물 (coverage-auditor, 2026-06-10). governing_docs = wiki/projects/ca-tmpl/sample-fixture-and-adoption. 기준: rules/coverage-gate.md. 상태: covered-here(이 브랜치 결정) / delegated(다른 owner 브랜치) / missing(아무도 안 맡음 → Blocking). Verdict: Covered (Blocking 0).

관심사 (governing doc) 상태 owner 심각도 근거
C1: 12 scenario matrix covered-here D4 + §Sample-portfolio Matrix
C2: 6-field minimum model covered-here §Minimum Model; 필드명 drift 는 §Audit MODEL_DRIFT
C3: state machine OPEN→IN_PROGRESS→CLOSED covered-here Advisory D4; 코드 gap 은 §Audit MODEL_DRIFT (depth 영역)
C4: optimistic lock covered-here D4 + §구현 가이드 1 (WorkLog.version L37, WireTest L152)
C5a: idempotency key 헤더 수용 covered-here D4 + WorkLogController.java L156/199
C5b: idempotency dedup/storage delegated raw/branch-notes/feature-rate-limit-idempotency-contract OK §구현 가이드 1 OUT_OF_BRANCH_SCOPE + §엣지 위임 링크
C6: sample-off / profile isolation delegated raw/branch-notes/feature-sample-removal-adoption-contract OK D3 + §구현 가이드 2 OUT_OF_BRANCH_SCOPE + env-keys.yaml L1269
C7: dual-mode CI matrix (sample-on/off release-blocking) delegated raw/branch-notes/feature-sample-removal-adoption-contract OK D6 + §구현 가이드 1 OUT_OF_BRANCH_SCOPE
C8: multi-module adoption checklist delegated raw/branch-notes/feature-domain-feature-onboarding-contract (via raw/branch-notes/feature-sample-removal-adoption-contract) Advisory governing doc L54; sibling D5 consume 구조

마주친 문제

  • 2026-06-10 Gradle wrapper lock sandbox 권한 문제.
    • 원인: Codex sandbox 기본 권한에서 ~/.gradle/wrapper/dists/...zip.lck 쓰기가 read-only로 차단.
    • 시도: 동일 Gradle 명령을 승인 실행으로 재시도.
    • 해결: 승인 실행 후 RED/GREEN 검증 및 full check 통과.
    • 별도 에러 노트로 분리됨: raw/errors/gradle-wrapper-lock-read-only-sandbox-2026-06-10

묶음

본 feature branch 는 leaf — 자식 자료 없음. Phase C2 실 코드 작성 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.

오류 기록 (본 feature 작업 중 발생)

면접 준비 (이 작업에서 나올 수 있는 면접 질문)

job-posting tie-ins (이 작업에서 파생된 글감)

관련 일일 노트

이 브랜치를 작업한 날짜. 양방향 nav 유지.

  • (아직 연결된 일일 노트 없음 — Phase C2 실 작업일 기록 시 [[raw/daily-notes/YYYY-MM-DD]] 추가)

완료 후 정리

  • PR 링크:
  • 리뷰 메모:
  • 머지 결과 / 배포 환경:
  • wiki 추출 대상 (verified만, wiki/projects/로만 추출):
    • actually-implemented 항목:
    • locally-verified 항목:
    • prod-verified 항목:
  • 추출하지 않을 항목 (planned / documented-only / abandoned):