53 KiB
title, source_type, status, branch, parent_branch, governing_docs, related_projects, 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 | governing_docs | related_projects | tags | created | target_merge | status_label | id | kind | project | work_item | inherits | refines | overrides | depends_on | contract_packet | contract_packet_sha256 | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| branch / feature-repository-access-permission-contract | branch-note | raw | feature-repository-access-permission-contract |
|
|
|
2026-05-21 | in-progress | BR-CA-SKELETON-OPERATIONAL-CONTRACT-005 | project-work-item | ca-skeleton-operational-contract | WI-CA-SKELETON-OPERATIONAL-CONTRACT-005 |
|
1 | 50c7cd20afc5ff3d2eb1c7aea5ff6409e78e87aa3263173240c362ad7f1ac330 |
branch: feature-repository-access-permission-contract
Layer:
raw/branch-notes/— use case 단위 repository capability 정책을 정의합니다. 구조 메모: 이 노트는 2026-05-21 생성(현 branch-note 템플릿 이전 포맷). 2026-06-05/branch-spec에서 템플릿 순서로 재정렬했고, 템플릿에 없는 pre-template 결정 보조 섹션(판정 기준 / Work Item Contract / Decisionized Work Items / 테스트 계약)은capabilities.yaml주석이 이름으로 참조하므로 삭제하지 않고 말미 §부록으로 분리·보존했다.
부모 (필수)
- 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 - 완료 조건: repository access rule과 forbidden fixture가 통과한다
상속한 프로젝트 결정
| 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 |
DEC-CA-SKELETON-OPERATIONAL-CONTRACT-TRANSACTION-001@1 |
application은 Spring transaction annotation 대신 TransactionPort 또는 TransactionalUseCaseRunner를 사용한다 | 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 |
|---|
목표
read/write repository 분리는 기본입니다. 추가로 어떤 use case가 어떤 repository capability를 사용할 수 있는지 annotation/policy로 제한해야 합니다. 특정 상황에서 허용되지 않은 repo 사용은 skeleton contract violation입니다.
- 이슈:
- PR:
범위
포함 범위
- use case capability annotation 기준.
- repository capability vocabulary.
- read/write/sensitive/bulk/transaction/outbound capability 분류.
- policy violation error 분류.
- architecture/contract test 기준.
제외 범위
- 세부 도메인별 repository 구현.
- runtime authorization과 repository access policy 혼동.
- DB row-level security 구현.
근거 (필수, 최소 1개+)
본 branch의 결정 근거. 상세 비교는 아래 §외부 근거 / 대안 조사 참조.
외부 근거 / 대안 조사 (2026-05-22 — Topic 6)
본 branch의 CROSS_TENANT_ADMIN capability 결정에 대한 외부 source. tenant resolution과 isolation은 feature-tenant-context-policy SSOT consume.
- 공통 참조 (cross-tenant admin은 isolation model과 무관):
- raw/official-docs/multitenancy-aws-saas-tenant-isolation-whitepaper — Silo/Pool/Bridge 어느 모델이든 admin은 cross-tenant
- raw/official-docs/multitenancy-hibernate-user-guide — DISCRIMINATOR/SCHEMA/DATABASE 어느 strategy든 admin은 filter bypass 필요
- raw/company-tech-blogs/multitenancy-atlassian-tenant-context — admin context override 운영 사례
- tenant resolution SSOT: raw/branch-notes/feature-tenant-context-policy (본 branch는 consume only)
- 검토한 대안 (배경 reference):
- raw/company-tech-blogs/multitenancy-auth0-tenant-resolution — JWT/header/subdomain
- raw/company-tech-blogs/multitenancy-subdomain-resolution-patterns — subdomain 대안
- raw/company-tech-blogs/multitenancy-stripe-citus-schema-per-tenant — schema-per-tenant 대안
- raw/official-docs/multitenancy-microservices-io-pattern — db-per-tenant 대안
- raw/official-docs/multitenancy-azure-architecture-patterns, raw/company-tech-blogs/multitenancy-hybrid-pooled-siloed-mix — hybrid 대안
- 비교 핵심: cross-tenant admin access는 6종 대안 모두 공통 —
Silo/Pool어느 model이든 admin role은 cross-tenant query 필요. capability 명시 선언은 ca-tmpl 고유 — auditability 확보.
TODO
TODO drained 2026-05-22 — 결정은 아래 "결정 사항" / 부록 "판정 기준" / "Decisionized Work Items" 참조.
@UseCaseRepositoryAccessannotation / capability enum / read·write·sensitive·bulk·transaction·outbound 의미 / use case-operation 매칭 / 위반 error code / architecture·contract test 기준 모두 결정 라인 또는 표로 반영됨. 잔존 TODO 없음.
진행 중 메모
- 이 권한은 사용자 권한이 아니라 application use case가 infrastructure capability를 사용할 수 있는지의 권한입니다.
결정 사항
- 2026-05-21: use case 기준 capability 선언을 기본으로 함.
- 2026-05-22: capability annotation 이름은
@UseCaseRepositoryAccess를 기본값으로 둠. → 2026-06-05 정합(사용자 결정 — as-built 채택): 실제 구현·테스트된@UseCaseCapability(TYPE target, 4-attribute)를 SSOT 로 채택. flat-enum@UseCaseRepositoryAccess원안은 superseded. 코드 재작성 대신 문서를 코드에 맞춤(§Audit F1·F2 RESOLVED). - 2026-05-22: repository capability는 사용자 권한이 아니라 application use case가 infrastructure 능력을 사용할 수 있는지에 대한 계약.
- 2026-05-22:
TRANSACTION_REQUIRED는 application-port branch의TransactionPortcontract와 연결되어야 하며 Spring@Transactional직접 import로 충족하지 않음. - 2026-05-22: SENSITIVE_READ marker = registry-managed metadata table (entity FQN + field name 단위). domain annotation 또는 JPA entity annotation 금지(domain에 framework 의존 회피). registry 표 위치는 contract-registry-governance. → 2026-06-05 깊이 결정(사용자 — 플래그만 + 메타표 defer): 본 branch 는 capability 어휘(
sensitiveReadboolean + capabilities.yaml row)만 소유. sensitive-field 메타표(entity FQN+field)와 위반 차단 enforcement 는 owner 인 raw/branch-notes/feature-contract-registry-governance 로 위임(documented-defer). scope 침범·ArchUnit static-analysis 한계 회피. - 2026-05-22: BULK_WRITE threshold = N > 100 또는 batch size > 100. 미만은 일반 WRITE_REPOSITORY로 충분.
- 2026-05-22: EXTERNAL_OUTBOUND_ALLOWED 분류 = outbox row INSERT는 in-process(불요), polling publisher의 broker publish는 outbound(필요).
- 2026-05-22: enforcement SSOT = ArchUnit annotation-based rule. compile-time annotation processor는 alternative, runtime AOP는 forbidden.
- 2026-05-22: CROSS_TENANT_ADMIN capability를 capability vocabulary에 추가 (tenant branch
feature-tenant-context-policy와 cross-link). - 2026-05-22: read repo vs write repo 분리는 강제하지 않음. 한 repository 내 메서드 단위 capability 선언으로 충분.
- 2026-05-22: capability marker 표준 = Java annotation
@UseCaseRepositoryAccess(value=Capability[])(METHOD target, flat 7-enum). → 2026-06-05 정합(사용자 — as-built 채택). 아래는 superseded 원안이며 SSOT 아님:retention:(RUNTIME 은 as-built 와 일치)RetentionPolicy.RUNTIMEtarget:→ as-builtElementType.METHOD(use case method 단위)ElementType.TYPE(use case 클래스 단위)value:→ as-built 4개 typed attributeCapability[]array→ as-built 차원별 분리(아래 정식 결정)Capabilityenum 7개 flat- consumer branches(
feature-application-port-usecase-contract,feature-business-rule-validation-contract,feature-tenant-context-policy)는 본 annotation을 consume only. (유지)
- 2026-06-05: capability marker 표준 (as-built SSOT) =
@UseCaseCapability—@Retention(RUNTIME),@Target(TYPE), use case 클래스 단위. 속성:- 구현됨(actually-implemented):
transactionMode(enumWRITE/READ_ONLY/REQUIRES_NEW),idempotency(enumIDEMPOTENT/KEYED/NOT_IDEMPOTENT),repositoryAccess(enumNONE/READ_REPOSITORY/WRITE_REPOSITORY),externalOutboundAllowed(boolean default false). - 확장 예정(planned): 누락 3종을
externalOutboundAllowed패턴의 boolean 으로 추가 —sensitiveRead/bulkWrite/crossTenantAdmin(각 default false). enum 신설이 아니라 boolean 속성 추가로 기존 코드 최소 변경. - 미명시 시 ArchUnit presence rule
inbound_port_implementations_declare_capabilityfail (owner: application-port).
- 구현됨(actually-implemented):
결정-근거 매핑
본 branch 의 각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시한다. Decision ID 는 안정적으로 유지한다. company-tech-blog 출처는
company-case-study로 표기하며 공식 best practice 로 일반화하지 않는다.
| Decision ID | Decision | Supporting Claims | Evidence Strength | Open Risk |
|---|---|---|---|---|
| D1 | use case 기준 capability 선언을 기본 | raw/official-docs/arch-clean-architecture-uncle-bob.md#CLEAN-ARCH-UB-C1 (use case 가 application layer SSOT), #CLEAN-ARCH-UB-C2 (dependency rule — inner layer 가 outer infrastructure 능력을 선언), #CLEAN-ARCH-UB-C7 (use case 단위 boundary 가 frameworks/drivers 능력 제어) |
engineering-blog + engineering-blog + engineering-blog (Uncle Bob personal blog — 공식 표준 아님) |
Uncle Bob blog 는 personal opinion. Clean Architecture 책 (Pearson) 의 ISO/IEEE 표준 인용 부재 |
| D2 | capability annotation = @UseCaseCapability (as-built SSOT, 2026-06-05 정합). flat-enum 원안 @UseCaseRepositoryAccess 는 superseded |
as-built 코드 = SSOT — application-core/.../capability/UseCaseCapability.java (actually-implemented + locally-verified) |
actually-implemented (코드 grep + UseCaseCapabilityTest 통과) |
naming 은 여전히 branch 자체 정합성 규칙이나 코드에 실재하므로 UNSUPPORTED_DECISION 해소. §Audit F1 RESOLVED |
| D3 | repository capability = application use case 의 infrastructure 능력 사용 권한 (사용자 권한 아님) | raw/official-docs/arch-hexagonal-cockburn.md#HEX-COCKBURN-ORIG-C3 (port = application 이 outside world 와 talk 하는 use case-shaped contract), #HEX-COCKBURN-ORIG-C4 (adapter 가 port 를 외부 기술로 구현 — capability 는 application 의 infrastructure 능력), #HEX-COCKBURN-ORIG-C7 (application 은 외부 기술 종류와 독립 — user auth 와 별개) |
engineering-blog + engineering-blog + engineering-blog (Cockburn personal blog — 공식 표준 아님) |
Cockburn 의 hexagonal 은 personal architectural article. user auth 와 명시 구분은 본 branch 의 해석 |
| D4 | TRANSACTION_REQUIRED = application-port branch 의 TransactionPort contract 연결 (Spring @Transactional 직접 import 로 충족 금지) |
raw/official-docs/arch-hexagonal-cockburn.md#HEX-COCKBURN-ORIG-C3 (port = application contract), #HEX-COCKBURN-ORIG-C4 (adapter 가 port 를 framework 구현), raw/official-docs/spring-tx-management-reference.md#SPRING-TX-MGR-C3 (PlatformTransactionManager API 추상화), #SPRING-TX-MGR-C5 (@Transactional 은 framework-specific annotation) |
engineering-blog + engineering-blog + official-vendor-doc + official-vendor-doc (Cockburn blog + Spring official reference) |
Cockburn port-adapter 와 Spring TX API 의 결합 (TransactionPort 추상화) 은 본 branch 해석 — official 표준은 직접 결합을 명시하지 않음 |
| D5 | SENSITIVE_READ: 본 branch 는 capability 어휘(sensitiveRead boolean + capabilities.yaml row)만 소유. metadata table(entity FQN+field; domain/JPA annotation 금지)과 위반 차단 enforcement 는 raw/branch-notes/feature-contract-registry-governance 로 위임(2026-06-05 깊이 결정 — 플래그만 + 메타표 defer) |
선택 조건: 메타표 위치·강제는 registry-governance owner / 본 branch 는 어휘만. 근거 — raw/official-docs/arch-clean-architecture-uncle-bob.md#CLEAN-ARCH-UB-C5 (entity = framework 독립), #CLEAN-ARCH-UB-C7 (entity 가 framework annotation 의존 금지), raw/official-docs/arch-hexagonal-cockburn.md#HEX-COCKBURN-ORIG-C3 (metadata 는 port 외부 registry 로 분리) |
engineering-blog + engineering-blog + engineering-blog (Uncle Bob + Cockburn personal blogs) — 분리 원칙만; 위임 경계는 본 branch 운영 결정 |
sensitiveRead 어휘 planned; 메타표·enforcement documented-defer(owner: registry-governance). scope·ArchUnit 한계 회피 |
| D6 | BULK_WRITE threshold = N > 100 또는 batch size > 100 | UNSUPPORTED_DECISION — 운영 threshold default. 외부 official 근거 없음 | none | branch 자체 운영 default |
| D7 | EXTERNAL_OUTBOUND_ALLOWED = outbox row INSERT (in-process, 불요); polling publisher broker publish (outbound, 필요) | raw/official-docs/microservices-io-transactional-outbox.md#MSIO-OUTBOX-C2 (outbox table INSERT 는 same DB transaction — in-process), #MSIO-OUTBOX-C5 (별도 message relay/polling publisher 가 outbox 를 읽어 broker 로 publish — outbound 분리), #MSIO-OUTBOX-C7 (polling publisher 가 broker 와의 외부 통신 담당) |
engineering-blog + engineering-blog + engineering-blog (Chris Richardson microservices.io — engineer 운영 가이드, 공식 표준 아님) |
microservices.io 는 Richardson 개인 사이트 — outbox pattern 의 capability 분류 명명은 본 branch 해석 |
| D8 | enforcement SSOT = ArchUnit annotation-based rule (compile-time annotation processor 는 alternative, runtime AOP 는 forbidden) | raw/official-docs/archunit-user-guide.md#ARCHUNIT-UG-C1 (ArchUnit 은 Java 아키텍처 규칙 단위 테스트 라이브러리), #ARCHUNIT-UG-C2 (JUnit test 로 실행 — compile/test time 검증), #ARCHUNIT-UG-C5 (annotation-based rule 지원 — @AnnotatedWith 등) |
official-vendor-doc + official-vendor-doc + official-vendor-doc (ArchUnit official user guide) |
AOP vs annotation processor 의 forbidden/alternative 분류는 본 branch 의 운영 정책 — ArchUnit doc 자체는 selection 권고 없음. ⚠️ presence rule 의 코드 owner 는 application-port (§Audit F5) |
| D9 | CROSS_TENANT_ADMIN capability 추가 (tenant branch cross-link) |
raw/official-docs/multitenancy-aws-saas-tenant-isolation-whitepaper.md#AWS-TENANT-C1 ~ C2 (tenant isolation fundamental + boundary breach un-recoverable), raw/official-docs/multitenancy-hibernate-user-guide.md#HBN-MT-C2 (DISCRIMINATOR strategy 공식 지원 — admin 은 filter bypass 필요) |
official-vendor-doc (AWS main page verbatim) + needs-confirmation (Hibernate body truncated) |
AWS 는 admin 이 cross-tenant 권한을 요구한다는 직접 명시는 sub-page 영역 (AWS-TENANT-C6 — needs-confirmation). Hibernate body verbatim 도 미확인 |
| D10 | read repo vs write repo 물리적 분리는 강제 안 함 — 한 repository 내 메서드 단위 capability 선언으로 충분 | raw/official-docs/cqrs-fowler-bliki.md#CQRS-FOWLER-C1 (CQRS 는 command/query 모델 분리), #CQRS-FOWLER-C3 (CQRS 는 일부 영역에 유용 — 전체 시스템에 강제 금지), #CQRS-FOWLER-C4 (Fowler 가 CQRS 의 비용 경고 — most systems 에는 부적합), #CQRS-FOWLER-C5 (단일 모델 단순화가 default — physical 분리는 큰 비용) |
engineering-blog + engineering-blog + engineering-blog + engineering-blog (Fowler bliki personal blog — 공식 표준 아님) |
Fowler bliki 는 personal opinion piece. 메서드 단위 capability 가 CQRS 의 대안이라는 해석은 본 branch 적용 |
| D11 | capability marker = @UseCaseCapability (as-built SSOT): @Retention(RUNTIME) + @Target(TYPE) (클래스 단위) + typed attributes. 구현됨: transactionMode/idempotency/repositoryAccess/externalOutboundAllowed. 확장 예정: sensitiveRead/bulkWrite/crossTenantAdmin boolean. flat 7-enum 원안 superseded(2026-06-05) |
as-built 코드 = SSOT — UseCaseCapability.java + RepositoryAccess.java/Idempotency.java/TransactionMode.java (actually-implemented); 신규 3 boolean 은 planned |
actually-implemented(4속성) + planned(3 boolean) |
구조는 코드로 확정. 신규 3 boolean 은 미구현(Phase C2). §Audit F2 RESOLVED |
| D12 | repositoryAccess 선언과 실제 repository 호출의 정합을 강제 (coherence): repositoryAccess = READ_REPOSITORY 선언 use case 가 write 메서드를 호출하면 build fail. presence(선언 유무) 강제와 별개의 관심사. |
N/A (강제 자체는 항상 적용) — 단 검출 메커니즘은 분기: ArchUnit static-analysis 로 호출 그래프 도달 가능 시 ArchUnit rule, 도달 불가(reflection/동적 호출) 시 runtime guard 또는 review fallback | raw/official-docs/archunit-user-guide.md#ARCHUNIT-UG-C1 (Java 아키텍처 규칙 단위 테스트), #ARCHUNIT-UG-C5 (@AnnotatedWith + method-call 분석 API) + governing doc wiki/projects/ca-tmpl/transaction-boundary-abstraction 의 UseCaseCapability Javadoc coherence 제약 (QueryUseCase ⇒ READ_ONLY+READ_REPOSITORY) |
official-vendor-doc (ArchUnit) + documented-only (Javadoc coherence 명세) |
구현 가이드
본 §는 as-built 명세다. 이 branch 의 결정(D1~D11)이 무엇을 할 것인가라면, 본 §는 ca-tmpl
src/에 실제로 어떻게 구현됐는지 + 아직 안 된 부분을 명세한다. 중대 주의 — 코드가 D2/D11 의 명세와 다르게 구현됨. annotation 명칭/구조/타깃이 노트 결정과 어긋난다(상세·정합 권고는 §Audit & Findings 의CONTRACT_DRIFT참조). 본 §의 anchor 는 코드(SSOT) 기준이며, D2/D11 은 사용자 결정 영역이라 자동 rewrite 하지 않고 drift 만 surface 한다.actually-implemented는src/grep 으로 확정한 것만. registry row 만 있고 코드 없는 것은planned.
1. Capability marker — as-built annotation 모양
Trace: D2/D11(as-built
@UseCaseCapability채택, 2026-06-05 정합) +#CLEAN-ARCH-UB-C7(use case 단위 boundary). 노트 D2/D11 이 as-built 로 정합됐으므로 drift 해소 — 아래는 코드 = 노트 일치 명세.
- UNSUPPORTED_IMPL_DECISION: (1) 4-attribute 구조(transactionMode/idempotency/repositoryAccess/externalOutboundAllowed)로의 분해는 외부 근거 없는 구현 trade-off — flat enum 대비 "transactional shape·idempotency·repo access·outbound surface 를 body 안 보고 읽게" 한다는 javadoc rationale(코드 주석)만 근거. (2)
@Target(TYPE)(클래스 단위) vsMETHOD(원안) 선택도 외부 근거 없는 trade-off — "use case = 1 클래스 1 책임" 가정에 기댐(클래스당 capability 1조). 다중 책임 클래스에는 부적합. 둘 다 사용자 결정(2026-06-05)으로 as-built 채택.
| 항목 | as-built (코드 = 노트 SSOT) | 원안(superseded) | status |
|---|---|---|---|
| annotation 명 | @UseCaseCapability |
@UseCaseRepositoryAccess |
actually-implemented |
| 위치(파일) | application-core/.../application/capability/UseCaseCapability.java |
— | actually-implemented |
@Target |
ElementType.TYPE (use case 클래스 단위) |
ElementType.METHOD |
actually-implemented |
@Retention |
RUNTIME (ArchUnit reflection) |
RUNTIME |
actually-implemented |
| 속성 구조 | 4개 typed attribute (아래 §2) + 확장 3 boolean(planned) | 단일 Capability[] array |
actually-implemented / planned(확장) |
2. Capability vocabulary — 구현된 enum vs registry 선언
Trace: 부록 §판정 기준 "Required capability" 7종 + capabilities.yaml 7 row(
owner_branch: feature-repository-access-permission-contract). 코드는 flat 7-enum 이 아니라 차원별 typed enum 으로 구현됐고, 7종 중 3종은 registry row 만 있고 코드 없음.
- UNSUPPORTED_IMPL_DECISION:
RepositoryAccess에NONE추가(registry/노트에 없는 값) — repo 미접근 use case 표현용 구현 trade-off.Idempotency차원 전체가 노트 capability vocabulary 에 부재(코드에는 존재).
| 노트/registry capability | 코드 구현 위치 | as-built 값 | status |
|---|---|---|---|
READ_REPOSITORY / WRITE_REPOSITORY |
capability/RepositoryAccess.java enum |
NONE, READ_REPOSITORY, WRITE_REPOSITORY |
actually-implemented |
TRANSACTION_REQUIRED |
transaction/TransactionMode.java enum (별도 차원) |
WRITE, READ_ONLY, REQUIRES_NEW |
actually-implemented |
EXTERNAL_OUTBOUND_ALLOWED |
UseCaseCapability.externalOutboundAllowed() |
boolean default false |
actually-implemented |
| (노트에 없음) idempotency | capability/Idempotency.java enum |
IDEMPOTENT, KEYED, NOT_IDEMPOTENT |
actually-implemented |
SENSITIVE_READ |
UseCaseCapability.sensitiveRead() boolean |
boolean default false |
actually-implemented (2026-06-05; 어휘+플래그만). 메타표(entity-FQN+field)·field-level enforcement 는 raw/branch-notes/feature-contract-registry-governance 위임(D5 documented-defer) |
BULK_WRITE |
UseCaseCapability.bulkWrite() boolean |
boolean default false |
actually-implemented (2026-06-05; D6). threshold 100 은 human 가이드(runtime 미강제). bulkWrite=true ⇒ repositoryAccess=WRITE_REPOSITORY coherence 강제됨 |
CROSS_TENANT_ADMIN |
UseCaseCapability.crossTenantAdmin() boolean |
boolean default false |
actually-implemented (2026-06-05; D9 어휘 owner 본 branch). cross-tenant runtime 정책은 raw/branch-notes/feature-tenant-context-policy 위임 |
3. Enforcement — ArchUnit fitness function (presence 만 강제, coherence 미강제)
Trace: D8(enforcement SSOT = ArchUnit annotation-based rule),
#ARCHUNIT-UG-C5(@AnnotatedWith지원). 구현된 rule 의 owner attribution 은 raw/branch-notes/feature-application-port-usecase-contract (코드.as()메시지) — D8 이 본 branch 를 SSOT 라 한 것과 ownership drift(§Audit).
- UNSUPPORTED_IMPL_DECISION: "repositoryAccess 선언과 실제 repository 호출의 정합(read-only 가 write 메서드 호출 시 fail)" 강제는 코드에 없음. annotation 은 선언적 문서일 뿐 — 정합 검출은 method-level call 분석 필요(ArchUnit static-analysis 한계). 이 branch 테스트 계약의 핵심 주장(아래 §4)이 대부분
planned인 이유.
| ArchUnit rule (실명) | 위치 | 무엇을 강제 | owner | status |
|---|---|---|---|---|
inbound_port_implementations_declare_capability |
CleanArchitectureTest.java:187 |
모든 CommandUseCase/QueryUseCase 구현체가 @UseCaseCapability 보유 (presence) |
feature-application-port-usecase-contract | locally-verified (negative fixture: MissingCapabilityUseCase) |
inbound_port_implementations_do_not_declare_keyed_idempotency |
(없음 — CleanArchitectureTest.java:217 에 제거 NOTE) |
idempotency = KEYED freeze 였으나 rate-limit-idempotency branch 머지로 freeze 해제 → 룰 + KeyedIdempotencyUseCase fixture 삭제됨(코드 확인). UseCaseCapabilityTest 가 이제 KEYED 를 valid 로 단언. |
raw/branch-notes/feature-application-port-usecase-contract D14 (freeze 트리거) | locally-verified |
application_does_not_use_spring_transactional_annotation |
CleanArchitectureTest.java |
..application.. 의 org.springframework.transaction.annotation.Transactional FQN 의존 금지 — D4 의 "Spring @Transactional 직접 import 금지" 충족 |
raw/branch-notes/feature-application-port-usecase-contract D3 (본 D4 와 정합) | locally-verified (fixture: TransactionalAnnotatedFixture) |
read_only_use_cases_do_not_call_repository_write_methods |
CleanArchitectureTest.java (D12/D6 섹션) |
repositoryAccess != WRITE_REPOSITORY use case 가 *Repository 의 write 메서드(save/delete*/update/insert/persist/merge/…) 직접 호출 시 build fail — 선언 vs 실제 호출 정합 |
feature-repository-access-permission-contract (D12) | locally-verified (2026-06-05; fixture ReadOnlyRepositoryWriteUseCase+FixtureRepository). static-analysis 한계 유지: helper/mapper 경유 write 는 미검출 → code-review 보완 |
bulk_write_capability_requires_write_repository_access |
CleanArchitectureTest.java (D12/D6 섹션) |
bulkWrite=true ⇒ repositoryAccess=WRITE_REPOSITORY 강제 (registry bound_to_capability) |
feature-repository-access-permission-contract (D6) | locally-verified (2026-06-05; fixture BulkWriteWithoutWriteAccessUseCase) |
external_outbound_calls_require_external_outbound_allowed_capability |
CleanArchitectureTest.java (D7 섹션) |
externalOutboundAllowed=false use case 가 outbound port(..adapter.outbound.. 구현 인터페이스) 직접 호출 시 build fail. outbound-port 집합은 adapter 바인딩으로 precompute(application-side 마커 불요) |
feature-repository-access-permission-contract (D7) | locally-verified (2026-06-05; fixture OutboundWithoutPermissionUseCase, RepoStatsPort←RepoStatsPortClient 식별). static-analysis 직접 호출 한정 |
| capabilities.yaml ↔ as-built model 1:1 drift 검출 | RepositoryAccessCapabilityRegistryTest.java (bootstrap.contract) |
registry 7 name: ↔ RepositoryAccess enum + @UseCaseCapability typed attribute 1:1 매칭. attribute rename/누락·registry 추가/삭제 시 fail. /docs gitignore → skip-on-absence(Assumptions) |
feature-repository-access-permission-contract | locally-verified (2026-06-05; 로컬 yaml 존재 시 7:7 일치 확인, skipped=0) |
4. 테스트 계약 realization — 선언 노출 test 만 존재, 위반 차단 test 는 미구현
Trace: 부록 §테스트 계약 5개 주장 + §Decisionized Work Items 의
Required test열. 현재 코드는 capability 선언이 reflection 으로 읽히는지(UseCaseCapabilityTest)와 annotation 누락 차단만 검증. capability 위반(read-only 가 write, sensitive 무선언 등) 차단 test 는 미작성.
| 테스트 계약 주장 | 대응 test (실명/위치) | status |
|---|---|---|
| capability 선언이 RUNTIME reflection 으로 노출 | UseCaseCapabilityTest.exposes_declared_transaction_mode_idempotency_and_repository_access |
actually-implemented |
| externalOutbound default=false / 명시 시 true | UseCaseCapabilityTest.external_outbound_defaults_to_false… / …readable_when_explicitly_enabled |
actually-implemented |
| 미선언 use case build fail | inbound_port_implementations_declare_capability + MissingCapabilityUseCase |
locally-verified |
| read-only use case 가 write repository 사용 시 fail | read_only_use_cases_do_not_call_repository_write_methods (D12) + fixture ReadOnlyRepositoryWriteUseCase → ArchitectureViolationFixtureTest.read_only_use_cases_do_not_call_repository_write_methods_catches_read_to_write_upgrade |
locally-verified (2026-06-05; 직접 호출 한정 — static-analysis 한계) |
| bulkWrite 선언이 WRITE_REPOSITORY 없이 사용 시 fail | bulk_write_capability_requires_write_repository_access (D6) + fixture BulkWriteWithoutWriteAccessUseCase → ArchitectureViolationFixtureTest.bulk_write_capability_requires_write_repository_access_catches_read_access_bulk |
locally-verified (2026-06-05) |
| sensitive/bulk/cross-tenant 플래그 default false / 명시 시 true | UseCaseCapabilityTest.sensitive_bulk_and_cross_tenant_flags_default_to_false_when_unspecified / …are_readable_when_explicitly_enabled |
actually-implemented (2026-06-05) |
| capabilities.yaml ↔ enum 1:1 매칭 강제 | RepositoryAccessCapabilityRegistryTest (registry/enum drift guard) |
locally-verified (2026-06-05) |
| sensitive read 무선언 use case 의 sensitive op 차단 | (위임 — 메타표·enforcement 는 raw/branch-notes/feature-contract-registry-governance, D5 documented-defer) |
delegated |
| transaction required op 이 boundary 없이 실행 시 fail | (미구현 — TransactionBoundaryContractTest 부재, application-port 의존) |
planned |
| outbound 금지 use case 의 external adapter 호출 차단 | external_outbound_calls_require_external_outbound_allowed_capability (D7) + fixture OutboundWithoutPermissionUseCase → ArchitectureViolationFixtureTest.external_outbound_calls_require_external_outbound_allowed_capability_catches_unpermitted_call |
locally-verified (2026-06-05; outbound-port = ..adapter.outbound.. 구현 인터페이스로 식별 — RepoStatsPort←RepoStatsPortClient. 직접 호출 한정) |
엣지·실패·의존
R4 캡처. 정상 경로(use case 가 capability 선언 → ArchUnit presence 통과) 외의 실패/엣지/의존.
- 실패·엣지 경로:
- 선언 vs 실제 호출 불일치:
repositoryAccess = READ_REPOSITORY인 use case 가 실제로 write 메서드를 호출 — 현재 검출 안 됨(coherence rule 미구현). 선언은 통과하나 의미상 위반. 기대 동작: build fail 이어야 하나 현재 silent pass →plannedClaim (D12). - member/anonymous class: presence rule 은
areNotInterfaces/areNotAnonymousClasses/areNotMemberClasses로 제외 — inner static use case 는 강제 대상 아님(UseCaseCapabilityTest의static final classexample 도 직접 평가 대상 아님). 신규 use case 를 inner class 로 작성 시 capability 누락이 통과되는 엣지. → 정책 결론: 신규 use case 는 top-level class 로만 작성(inner static use case 금지)해야 presence rule 이 의미를 가짐. - registry row 만 있고 enum 없음: SENSITIVE_READ/BULK_WRITE/CROSS_TENANT_ADMIN 을 코드에서 사용하려 하면 컴파일 불가(enum 부재). registry 를 SSOT 로 믿고 작성하면 좌초 — drift 명시 필요(§Audit F3).
- KEYED idempotency freeze ❌ 해제됨(2026-06-09): 과거
Idempotency.KEYED선언 시 build fail 하던 freeze 룰은 rate-limit-idempotency branch 머지로 제거(룰+fixture 삭제,KEYED이제 valid). 본 항목은 history 로만 보존.
- 선언 vs 실제 호출 불일치:
- 다른 계약 의존:
- raw/branch-notes/feature-application-port-usecase-contract —
@UseCaseCapability의 presence 강제 ArchUnit rule 의 owner(코드 attribution). 본 branch 는 capability vocabulary 를 정의하고, 모든 use case 가 선언하게 하는 강제는 application-port branch 소유. 그 rule 이 사라지면 본 vocabulary 가 무의미해짐. - raw/branch-notes/feature-application-port-usecase-contract 의
TransactionPort/TransactionMode— D4 의TRANSACTION_REQUIRED↔TransactionModeenum +TransactionPort.inRead/inWrite결합.TransactionModeenum 은application/transaction/에 구현됨(application-port slice 소유 가능). enum 이동 시UseCaseCapabilityannotation 컴파일 break. - raw/branch-notes/feature-tenant-context-policy 의 cross-tenant 정책 — D9
CROSS_TENANT_ADMIN이 consume. 미구현이므로 현재는 documented dependency. - raw/branch-notes/feature-application-port-usecase-contract D14 —
KEYEDfreeze(merge 전 금지)의 owner. 해제 트리거 merge: raw/branch-notes/feature-rate-limit-idempotency-contract (2026-06-09 머지 완료 → freeze 룰 제거, KEYED 선언 허용).
- raw/branch-notes/feature-application-port-usecase-contract —
검증해야 할 주장
공식 문서나 사례는 근거지만, 내 프로젝트에서의 동작을 자동으로 보장하지 않는다. 구현 전/중/후에 실제로 검증해야 하는 주장을 분리한다.
| Claim | Why uncertain | How to verify | Status |
|---|---|---|---|
| ArchUnit annotation-based rule 이 모든 use case method 의 capability 선언 강제를 검출 | ArchUnit AbsentCapabilityArchitectureTest 미구현 |
ArchUnit rule 작성 + use case method 에 annotation 누락 시 build fail verify | planned |
| AWS whitepaper sub-page "Authentication is not isolation" + "resource layer enforcement" verbatim 정확성 | 2026-05-27 sub-page WebFetch truncated | archive.org snapshot 또는 manual browser 재확인 | needs-confirmation |
Hibernate DISCRIMINATOR strategy 하에서 CROSS_TENANT_ADMIN 구현 메커니즘 (CurrentTenantIdentifierResolver override vs Hibernate Filter disable) |
Hibernate 6 @TenantId 와 CROSS_TENANT_ADMIN 의 통합 패턴 미검증 |
Hibernate 6 reference + Spring Security 통합 contract test 구현 | needs-confirmation |
| read-only use case 가 write repository capability 사용 시 build fail | ArchUnit 또는 annotation processor 미구현 | WriteCapabilityViolationTest ArchUnit rule 구현 + 위반 시 build fail verify |
planned |
| SENSITIVE_READ capability 가 없는 use case 의 sensitive repository operation 차단 | registry-managed metadata table 미구현 | sensitive-fields registry yaml + ArchUnit rule 통합 + 위반 시 build fail verify | planned |
| transaction required operation 이 transaction boundary 없이 실행되면 fail | TransactionPort contract 미구현 (application-port branch 의존) |
TransactionBoundaryContractTest 구현 + boundary 없이 실행 시 fail verify |
planned |
| outbound 금지 use case 의 external adapter 호출 차단 | ArchUnit rule 미구현 | OutboundCapabilityViolationTest ArchUnit rule + external adapter 호출 시 build fail verify |
planned |
TRANSACTION_REQUIRED 가 Spring @Transactional 직접 import 로만 충족하면 fail |
annotation processor 또는 ArchUnit rule 미구현 | TransactionPort 사용 강제 ArchUnit rule + Spring annotation 직접 import 시 fail verify |
planned |
| BULK_WRITE threshold 100 의 운영 합리성 | threshold 의 정량 근거 없음 | actual workload 측정 + threshold 조정 (Phase C2 이후) | needs-confirmation |
| 7개 Capability enum 이 모든 ca-tmpl use case 패턴 cover | 운영 패턴 미완 | use case 패턴 카탈로그 작성 + 누락 capability 식별 | needs-confirmation |
capabilities.yaml SSOT 와 Capability enum 1:1 매칭 강제 |
registry scan 미구현 | enum vs yaml drift 검출 ArchUnit rule 또는 Gradle task 구현 | planned |
관심사 커버리지
/coverage가 채우는 생성물 — 손유지 금지. 기준:rules/coverage-gate.md. governing_docs:clean-architecture-package-layout+transaction-boundary-abstraction. 마지막 감사: 2026-06-05 → Covered (Blocking 0 / Should-fix 0 / Advisory 0, coverage-auditor 재감사).
| 관심사 | 상태 | owner | 심각도 | 근거 |
|---|---|---|---|---|
| use case 가 repository access 능력을 명시 선언 | covered-here | — | — | D1, D11 / RepositoryAccess enum |
| 모든 inbound port 구현체가 capability 선언 강제 (presence rule) | delegated | raw/branch-notes/feature-application-port-usecase-contract | OK | inbound_port_implementations_declare_capability (코드 .as() attribution = application-port). 본 branch 는 capability 어휘 SSOT, presence 강제 는 위임 (§Audit F5) |
transaction boundary 추상화 (TransactionPort / TransactionMode / @Transactional 금지) |
delegated | raw/branch-notes/feature-application-port-usecase-contract | OK | D4 / application_does_not_use_spring_transactional_annotation + TransactionMode enum (application/transaction/) |
Idempotency 차원 (IDEMPOTENT/KEYED/NOT_IDEMPOTENT) capability ownership |
delegated | raw/branch-notes/feature-application-port-usecase-contract | OK | Idempotency enum + KEYED-freeze rule (application-port D14). 본 branch capability 어휘 범위 밖 (§Audit F6) |
| read/write repository 분리 강제 안 함 (메서드 단위 capability) | covered-here | — | — | D10 |
| outbound 호출 능력 명시 + 강제 | covered-here | — | — | D7 / externalOutboundAllowed + external_outbound_calls_require_external_outbound_allowed_capability locally-verified (2026-06-05). outbound-port = adapter 바인딩 식별. 직접 호출 한정 |
| cross-tenant admin 능력 | delegated | raw/branch-notes/feature-tenant-context-policy | OK (미구현) | D9 — vocabulary owner 는 본 branch, cross-tenant 정책 의존은 tenant branch |
| SENSITIVE_READ 메타표(entity FQN+field) + 위반 차단 enforcement | delegated | raw/branch-notes/feature-contract-registry-governance | OK (documented-defer) | D5 — 본 branch 는 sensitiveRead 어휘만 소유, 메타표·강제는 registry-governance |
| repositoryAccess 선언 vs 실제 호출 정합 강제 (coherence) | covered-here | — | — | D12 — read_only_use_cases_do_not_call_repository_write_methods + bulk_write_capability_requires_write_repository_access locally-verified (2026-06-05). 직접 호출 한정 — helper/mapper 경유는 review 보완 |
Audit & Findings (2026-06-05 — ca-tmpl 코드 대조)
ca-tmpl
src/ground truth 와 본 노트/registry 대조 결과. 사용자 작성 결정 영역(D2/D11/registry)은 자동 rewrite 하지 않고 정합 권고만 기록(/branch-spec규칙 §2). 코드가 SSOT.
| Finding | 유형 | 노트/registry | 코드 (SSOT) | 권고 |
|---|---|---|---|---|
| F1 | CONTRACT_DRIFT → RESOLVED (2026-06-05) |
annotation 명 @UseCaseRepositoryAccess (D2/D11 원안) |
@UseCaseCapability (application-core/.../capability/UseCaseCapability.java) |
D2/D11 노트는 as-built 로 정합 완료. 남은 follow-up (ca-tmpl 레포): capabilities.yaml 6 row 의 annotation: "@UseCaseRepositoryAccess(...)" 와 scope: use_case_method 가 stale — as-built @UseCaseCapability + use_case_class 로 registry-governance owner 가 갱신해야 함. |
| F2 | CONTRACT_DRIFT → RESOLVED (2026-06-05) |
target ElementType.METHOD, value Capability[] flat 7-enum (D11 원안) |
@Target(TYPE) + 4 typed attribute (transactionMode/idempotency/repositoryAccess/externalOutboundAllowed) |
D11 as-built 구조로 갱신 완료. flat-enum 모델 superseded. |
| F3 | MISSING_IMPL → RESOLVED (2026-06-05) |
SENSITIVE_READ(D5)/BULK_WRITE(D6)/CROSS_TENANT_ADMIN(D9) — capabilities.yaml row 존재 | @UseCaseCapability 의 boolean 속성 sensitiveRead/bulkWrite/crossTenantAdmin (default false) 으로 구현 + UseCaseCapabilityTest reflection 검증. SENSITIVE_READ 메타표(entity-FQN+field)·field-level enforcement 만 raw/branch-notes/feature-contract-registry-governance 위임(D5 documented-defer). |
3종 어휘 as-built 완료. RepositoryAccessCapabilityRegistryTest 가 registry 7 row ↔ as-built model 1:1 강제. |
| F4 | MISSING_CONCERN → RESOLVED (2026-06-05) |
§테스트 계약: "read-only 가 write 사용 시 fail" 등 (capability 위반 차단) | read_only_use_cases_do_not_call_repository_write_methods(D12) + bulk_write_capability_requires_write_repository_access(D6) ArchUnit 룰 + negative fixtures(ReadOnlyRepositoryWriteUseCase/BulkWriteWithoutWriteAccessUseCase) |
coherence 강제 구현 완료(locally-verified). 잔여 한계: ArchUnit static-analysis 는 직접 호출만 — helper/mapper 경유 write 미검출은 code-review 보완(D12 §엣지). transaction-boundary 강제는 여전히 application-port TransactionPort 의존. |
| F5 | OWNERSHIP_DRIFT |
D8: enforcement SSOT = 본 branch | presence rule .as() attribution = raw/branch-notes/feature-application-port-usecase-contract |
D8 을 "vocabulary SSOT = 본 branch / presence 강제 = application-port" 로 분리 명시. 본 branch 는 capability 어휘, application-port 가 선언 강제 owner. |
| F6 | IMPL_NEW_DIMENSION |
idempotency 차원 노트 capability vocabulary 에 부재 | Idempotency {IDEMPOTENT,KEYED,NOT_IDEMPOTENT} 구현 + KEYED-freeze rule |
idempotency 는 별도 contract(rate-limit-idempotency) 소유 가능 — 본 branch capability vocabulary 와의 경계 확인 권고. |
구현 로그
2026-06-05 — Phase C2 as-built (@UseCaseCapability 확장 + coherence/drift 강제)
사용자 결정(2026-06-05 /AskUserQuestion): as-built 확장(flat-enum 재작성 아님) + SENSITIVE_READ 어휘+플래그만(메타표 defer).
- 변경 파일 (ca-tmpl
src/):application-core/.../capability/UseCaseCapability.java— booleansensitiveRead()/bulkWrite()/crossTenantAdmin()(default false) + coherence/매핑 javadoc.application-core/.../capability/UseCaseCapabilityTest.java— 신규 플래그 default/explicit reflection 검증 2 test +ExampleAdminBulkUseCasefixture.app-bootstrap/.../architecture/CleanArchitectureTest.java— D12read_only_use_cases_do_not_call_repository_write_methods+ D6bulk_write_capability_requires_write_repository_access+ D7external_outbound_calls_require_external_outbound_allowed_capability룰 + 3 customArchCondition+ outbound-port precompute(OUTBOUND_PORT_NAMES, adapter 바인딩 식별) +JavaMethodCall/JavaClasses/ClassFileImporterimport.app-bootstrap/.../architecture/violations/application/{FixtureRepository,ReadOnlyRepositoryWriteUseCase,BulkWriteWithoutWriteAccessUseCase,OutboundWithoutPermissionUseCase}.java— negative fixtures (public —.classisolation corpus 가시성).app-bootstrap/.../architecture/ArchitectureViolationFixtureTest.java— isolated corpus 3 + negative assertion 3.app-bootstrap/.../contract/RepositoryAccessCapabilityRegistryTest.java— registry 7 row ↔ as-built model 1:1 drift guard (snakeyaml, skip-on-absence).sample-portfolio/.../application/worklog/BatchCreateWorkLogsUseCase.java—bulkWrite = true(canonical bulk write 데모, production-side 룰 positive case).docs/registries/capabilities.yaml(gitignored — 커밋 미포함) — 7 rowannotation:필드 + 헤더를 as-built@UseCaseCapability(...)표기로 F1/F2 정합.
- 검증 (
cd src):./gradlew :application-core:test --tests '*UseCaseCapabilityTest'PASS./gradlew :app-bootstrap:test --tests '*CleanArchitectureTest'/'*ArchitectureViolationFixtureTest'/'*RepositoryAccessCapabilityRegistryTest'PASS, skipped 0. D12/D6/D7 negative test 3종 JUnit XML 확인:tests=3 skipped=0 failures=0 errors=0. 드리프트 테스트 로컬 yaml 7:7 일치../gradlew verifyCleanArchitectureDependenciesPASS./gradlew test(full) PASS, 회귀 0
- 본 branch 소유·정적강제 가능 항목 전부 구현: read/write coherence(D12), bulk coherence(D6), outbound coherence(D7), registry↔model drift, 3 플래그 어휘.
- 잔여 (cross-branch 위임 — 본 branch 미소유): SENSITIVE_READ entity-FQN+field 메타표·field-level 강제 → raw/branch-notes/feature-contract-registry-governance. transaction-boundary 실행 강제 → raw/branch-notes/feature-application-port-usecase-contract
TransactionPort. cross-tenant runtime 정책 → raw/branch-notes/feature-tenant-context-policy. - 공통 한계: coherence 룰 3종 모두 ArchUnit static-analysis 직접 호출만 검출 — helper/mapper 경유는 code-review 보완(문서 D12 §엣지 명시).
마주친 문제
- IDE stale-index false positive:
@UseCaseCapability에 속성 추가 직후 IDE diagnostics 가bulkWrite is undefined for the annotation type를 보고. application-core 가 IDE 증분 컴파일러에서 아직 재컴파일되지 않은 stale classpath 문제 — Gradle 빌드가 application-core 를 먼저 재컴파일하여 해소. 코드 오류 아님. .classisolation corpus 가시성:ArchitectureViolationFixtureTest가.importClasses(X.class)로 fixture 를 isolated corpus 로 로드하려면 fixture 가 public 이어야 함(다른 패키지). package-private 로 두면is not visible컴파일 오류.importPackages(string)만 쓰는 기존 fixture 는 package-private 가능 — 참조 방식에 따라 가시성 요건이 다름.
묶음
- raw/company-tech-blogs/multitenancy-atlassian-tenant-context
- raw/company-tech-blogs/multitenancy-auth0-tenant-resolution
- raw/company-tech-blogs/multitenancy-hybrid-pooled-siloed-mix
- raw/company-tech-blogs/multitenancy-stripe-citus-schema-per-tenant
- raw/company-tech-blogs/multitenancy-subdomain-resolution-patterns
- raw/official-docs/arch-clean-architecture-uncle-bob
- raw/official-docs/arch-hexagonal-cockburn
- raw/official-docs/archunit-user-guide
- raw/official-docs/cqrs-fowler-bliki
- raw/official-docs/microservices-io-transactional-outbox
- raw/official-docs/multitenancy-aws-saas-tenant-isolation-whitepaper
- raw/official-docs/multitenancy-azure-architecture-patterns
- raw/official-docs/multitenancy-hibernate-user-guide
- raw/official-docs/multitenancy-microservices-io-pattern
- raw/official-docs/security-opa-policy-engine-official
- raw/official-docs/spring-tx-management-reference
본 feature branch 는 leaf. 2026-06-05 Phase C2 구현으로 아래 파생 자료 후보 발생.
오류 기록 (본 feature 작업 중 발생)
- 위 §마주친 문제 2건(IDE stale-index, isolation-corpus 가시성) — 둘 다 경미·즉시 해소. 독립
raw/errors/노트로 승급할 만큼 재발/심각도 높지 않음 → branch-note 내 기록으로 충분(별도 노트 불요).
면접 준비 (이 작업에서 나올 수 있는 면접 질문)
- "선언적 capability annotation 의 선언 vs 실제 호출 정합을 어떻게 강제하나? ArchUnit static-analysis 의 한계(helper 경유 호출 미검출)는?" — 본 작업의 D12 coherence 룰이 정직한 답변 소재. 다만 단일 질문 — 독립 interview 노트 승급은 보류, Phase 누적 시 그룹화.
Blog topics
- "Clean Architecture 에서 repository 접근 권한을 annotation+ArchUnit fitness function 으로 계약화하기 (presence vs coherence vs registry-drift 3층)" — 독립 글감 가능성. 현재는 branch-note 로 충분, canonical 추출 요청 시 분리.
관련 일일 노트
이 브랜치를 작업한 날짜들. 양방향 nav 유지.
- 2026-06-05 — Phase C2 as-built 구현(위 §구현 로그). (daily note 파일 미생성 — 본 branch-note 가 1차 기록.)
부록 — pre-template 결정 보조 섹션 (registry 참조 보존)
이 노트가 현 템플릿 이전(2026-05-21)에 작성되며 가졌던 섹션들. 내용은 위 Decision Evidence Map / 구현 가이드 / Claims To Verify 로 흡수됐으나,
capabilities.yaml주석이 "판정 기준 / Decisionized Work Items" 를 이름으로 참조하므로 삭제하지 않고 보존한다. 갱신 시 위 정식 섹션이 SSOT — 본 부록은 registry 역참조용 스냅샷.
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 승급 위치 |
판정 기준
| 구분 | 기준 |
|---|---|
| Decision | use case가 사용할 수 있는 repository capability를 명시 선언 |
| Allowed | AOP 대신 ArchUnit/compile-time checker 사용 가능 |
| Forbidden | read-only use case의 write/bulk/sensitive repository 접근 |
| Required capability | READ_REPOSITORY, WRITE_REPOSITORY, SENSITIVE_READ, BULK_WRITE, TRANSACTION_REQUIRED, EXTERNAL_OUTBOUND_ALLOWED, CROSS_TENANT_ADMIN |
| Failure condition | 선언되지 않은 repository/outbound capability 사용이 감지되지 않으면 실패 |
Decisionized Work Items
| item | Decision | Allowed | Forbidden | Required test |
|---|---|---|---|---|
| annotation | @UseCaseRepositoryAccess default |
compile-time checker alternative | undocumented repo access | annotation/rule test |
| capability enum | registry-owned capabilities | additive capability with registry row | ad hoc string capability | registry scan |
| transaction | TRANSACTION_REQUIRED maps to TransactionPort |
infra Spring implementation | direct Spring annotation as proof | transaction capability test |
| sensitive read | explicit capability | pseudonymized data read without sensitive flag if documented | PII read by default | sensitive access test |
| outbound | EXTERNAL_OUTBOUND_ALLOWED required |
domain event without transport | hidden HTTP/message call | outbound access test |
테스트 계약
- read-only use case가 write repository capability를 사용하면 실패.
- sensitive read capability가 없는 use case가 sensitive repository operation을 사용하면 실패.
- transaction required operation이 transaction boundary 없이 실행되면 실패.
- outbound 금지 use case가 external adapter를 호출하면 실패.
TRANSACTION_REQUIRED를 Spring annotation 직접 import로만 충족하면 실패.
완료 후 정리
- PR 링크:
- 리뷰 메모:
- 머지 결과 / 배포 환경:
- wiki 추출 대상 (verified만,
wiki/projects/로만 추출):actually-implemented항목:locally-verified항목:prod-verified항목:
- 추출하지 않을 항목 (planned / documented-only / abandoned):