37 KiB
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 |
|
|
|
2026-05-21 | in-progress | BR-CA-SKELETON-OPERATIONAL-CONTRACT-014 | project-work-item | ca-skeleton-operational-contract | WI-CA-SKELETON-OPERATIONAL-CONTRACT-014 |
|
1 | 6d4baf1c3d8e37e66b1a0ad293a732d9e1bf0462c598959d9dc0ccdc12e49964 |
branch: feature-sample-domain-contract-fixture
Layer:
raw/branch-notes/— skeleton 계약 검증을 위한 sample domain fixture 기준을 정의합니다.
부모 (필수)
- Parent project (canonical SSOT): raw/project-notes/ca-skeleton-operational-contract
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-portfoliofixture.- 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 검증 도구라는 목적 정의)
- 검토한 대안:
- 대안 1: Spring Petclinic — raw/official-docs/sample-spring-petclinic-github (Spring 공식; "demo지 best-practice 아님" 본인 선언)
- 대안 2: RealWorld (gothinkster Conduit) — raw/official-docs/sample-realworld-gothinkster-github (cross-stack spec; 풍부하나 minimum 아니고 contract scenario 부재)
- 대안 3: Spring Cloud Microservices sample — raw/official-docs/sample-microservices-spring-cloud-github (fixture 수준 초과; microservices variant)
- 대안 4: Shopping cart (Stripe testmode) — payment domain 한정, ca-tmpl 일반 skeleton 부적합
- 대안 5: No fixture (unit tests only) — 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상태 머신과WorkLogOwnerminimum 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에서 missingWorkLogStatus/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) 신설 →WorkLogController가SecurityContextHolder의AuthenticatedUser.idpUserId()를 주입(currentOwnerSubject(),RateLimitKeyResolver와 동일 null-safe idiom) →CreateWorkLogUseCase/BatchCreateWorkLogsUseCase가WorkLogOwner.of(cmd.owner())로 생성. privacy:WorkLogResponse/WorkLogSummaryResponse에서 rawowner제거(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·*CleanArchitectureTestexit 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_SCOPE → raw/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_SCOPE → feature-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_SCOPE → feature-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_SCOPE → feature-sample-removal-adoption-contract |
planned |
| sample-off smoke | sample/core decoupling · dual-mode CI | CI workflow / smoke test 코드 없음 → planned · OUT_OF_BRANCH_SCOPE → feature-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_bodyL145), HEAD 지원 (L173), batch atomic (batch_create_is_atomic_one_bad_item_fails_whole_batchL310), 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 L576–581) |
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-contract → OUT_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) + domainrequireValidTitle()(invariant,WorkLogInvariantException.Reason.TITLE_BLANK) 2중 방어 — domain 검증이 web 뒤에서도 독립 동작 (WorkLogInvariantTest.null_title_stays_a_null_check_not_an_invariant_violationL43). - 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) 재노출 가능.
- stale If-Match → 412: 동시 update 시 version 불일치. 기대:
- 다른 계약 의존 (owner branch + 소비 대상):
- raw/branch-notes/feature-api-contract-baseline
D15— version/ETag/If-Match/412 optimistic-lock 메커니즘. 바뀌면 conflict scenario 전부 영향. - raw/branch-notes/feature-rate-limit-idempotency-contract —
Idempotency-Key헤더 + dedup scope. idempotent replay scenario 의존. - raw/branch-notes/feature-repository-access-permission-contract —
EXTERNAL_OUTBOUND_ALLOWEDcapability. outbound forbidden scenario 의존. - raw/branch-notes/feature-domain-modeling-guardrails
D3/D6— title invariant. validation scenario 의존. - raw/branch-notes/feature-resource-identifier-contract
D4/D5— WorkLogId factory (ULID). id 정규화 scenario 의존. - raw/branch-notes/feature-boundary-validation-mapping-contract — request/response mapper + unknown-field/mapping-failed 계약. 본 fixture 의 WorkLog 도메인이 이 branch sub-project B(2026-05-29)에서 실제 구현됨.
- raw/branch-notes/feature-sample-removal-adoption-contract —
APP_SAMPLE_ENABLED+ dual-mode CI + sample-off smoke. sample-disabled/sample-off scenario (D3/D6) 위임처. - raw/branch-notes/feature-data-retention-privacy-contract
D2/D7— principal pseudonymization (HMAC-SHA-256 + rotating salt). owner 는 현재 raw 저장 + 응답 비노출이며, pseudonymized 표현은 이 branch 소유 → 준비 시 consume. - raw/branch-notes/feature-authentication-authorization-contract
D2— permission 기반 RBAC. owner-scoped authz(worklog.owner==principal)는 이 branch 가 YAGNI 로 보류한 ABAC 확장점 — fixture 는 permission 기반만 소비(owner 로 authz 안 함).
- raw/branch-notes/feature-api-contract-baseline
Audit & Findings (2026-06-10 ca-tmpl ground-truth 대조)
/branch-spec가 ca-tmplsrc/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 machineOPEN→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.javaL20–50). - 권고: §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 참조). canonicalsample-ticket명명 drift는 별도/ingest/canonical 갱신 영역으로 남음.
- 거버닝 canonical wiki/projects/ca-tmpl/sample-fixture-and-adoption:
- 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
- 원인: Codex sandbox 기본 권한에서
묶음
- raw/official-docs/sample-microservices-spring-cloud-github
- raw/official-docs/sample-realworld-gothinkster-github
- raw/official-docs/sample-spring-petclinic-github
본 feature branch 는 leaf — 자식 자료 없음. Phase C2 실 코드 작성 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.
오류 기록 (본 feature 작업 중 발생)
- raw/errors/gradle-wrapper-lock-read-only-sandbox-2026-06-10 — Codex sandbox에서 Gradle wrapper가
~/.gradlelock 파일을 쓰지 못해 테스트 명령을 승인 실행으로 재시도.
면접 준비 (이 작업에서 나올 수 있는 면접 질문)
- raw/interviews/sample-domain-contract-fixture-clean-architecture — 샘플 도메인을 production 기능이 아니라 계약 fixture로 두는 이유와 계층 경계 설명.
job-posting tie-ins (이 작업에서 파생된 글감)
- raw/blog-topics/sample-domain-contract-fixture-clean-architecture-2026-06-10 — Clean Architecture 템플릿에서 sample domain fixture로 계약을 검증하는 구현 글감.
관련 일일 노트
이 브랜치를 작업한 날짜. 양방향 nav 유지.
- (아직 연결된 일일 노트 없음 — Phase C2 실 작업일 기록 시
[[raw/daily-notes/YYYY-MM-DD]]추가)
완료 후 정리
- PR 링크:
- 리뷰 메모:
- 머지 결과 / 배포 환경:
- wiki 추출 대상 (verified만,
wiki/projects/로만 추출):actually-implemented항목:locally-verified항목:prod-verified항목:
- 추출하지 않을 항목 (planned / documented-only / abandoned):