444 lines
53 KiB
Markdown
444 lines
53 KiB
Markdown
---
|
|
title: branch / feature-repository-access-permission-contract
|
|
source_type: branch-note
|
|
status: raw
|
|
branch: feature-repository-access-permission-contract
|
|
parent_branch:
|
|
governing_docs: [wiki/projects/ca-tmpl/clean-architecture-package-layout, wiki/projects/ca-tmpl/transaction-boundary-abstraction]
|
|
related_projects: [ca-skeleton]
|
|
tags: [branch, ca-skeleton, repository, permission, use-case]
|
|
created: 2026-05-21
|
|
target_merge:
|
|
status_label: in-progress
|
|
id: BR-CA-SKELETON-OPERATIONAL-CONTRACT-005
|
|
kind: project-work-item
|
|
project: ca-skeleton-operational-contract
|
|
work_item: WI-CA-SKELETON-OPERATIONAL-CONTRACT-005
|
|
inherits: [DEC-CA-SKELETON-OPERATIONAL-CONTRACT-MODULE-LAYOUT-001@1, DEC-CA-SKELETON-OPERATIONAL-CONTRACT-TRANSACTION-001@1]
|
|
refines: []
|
|
overrides: []
|
|
depends_on: []
|
|
contract_packet: 1
|
|
contract_packet_sha256: 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` 주석이 이름으로 참조하므로 삭제하지 않고 말미 §부록으로 분리·보존했다.
|
|
|
|
<!-- section-id: branch-parent -->
|
|
## 부모 (필수)
|
|
|
|
- **Parent project (canonical SSOT)**: [[raw/project-notes/ca-skeleton-operational-contract]]
|
|
|
|
> ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note 의 운영 계약 중 해당 영역 (§<관련 섹션>) 의 결정/근거/금지 사항을 정제한다.
|
|
|
|
<!-- GENERATED: branch-contract:start -->
|
|
<!-- section-id: branch-contract-packet -->
|
|
## 브랜치 계약 패킷
|
|
|
|
- **생성 시 프로젝트 개정**: `1`
|
|
- **패킷 스키마**: `contract_packet: 1`
|
|
- **완료 조건**: repository access rule과 forbidden fixture가 통과한다
|
|
|
|
<!-- section-id: inherited-project-decisions -->
|
|
### 상속한 프로젝트 결정
|
|
|
|
| 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]] |
|
|
|
|
<!-- section-id: branch-local-decisions -->
|
|
### 브랜치 지역 결정
|
|
|
|
> 기존 branch-local 결정은 아래 `## Decision Evidence Map / 결정-근거 매핑`의 D-row가 소유하며 이 packet에서 복제하지 않는다.
|
|
|
|
| Decision ID | Decision | Relation | Supporting Claims | Status |
|
|
|---|---|---|---|---|
|
|
|
|
<!-- section-id: declared-overrides -->
|
|
### 선언한 예외
|
|
|
|
| Override ID | Overrides | Reason | Approval | Status |
|
|
|---|---|---|---|---|
|
|
<!-- GENERATED: branch-contract:end -->
|
|
|
|
<!-- section-id: branch-goal -->
|
|
## 목표
|
|
|
|
read/write repository 분리는 기본입니다. 추가로 어떤 use case가 어떤 repository capability를 사용할 수 있는지 annotation/policy로 제한해야 합니다. 특정 상황에서 허용되지 않은 repo 사용은 skeleton contract violation입니다.
|
|
|
|
- 이슈:
|
|
- PR:
|
|
|
|
<!-- section-id: branch-scope -->
|
|
## 범위
|
|
|
|
### 포함 범위
|
|
|
|
- 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의 결정 근거. 상세 비교는 아래 §외부 근거 / 대안 조사 참조.
|
|
|
|
| Source | 정당화하는 결정 |
|
|
|---|---|
|
|
| [[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 운영 사례 |
|
|
| [[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 대안 |
|
|
| [[raw/company-tech-blogs/multitenancy-hybrid-pooled-siloed-mix]] | hybrid 대안 |
|
|
| [[raw/official-docs/arch-clean-architecture-uncle-bob]] | D1 (use case 기준 capability), D5 (domain framework 의존 회피) |
|
|
| [[raw/official-docs/arch-hexagonal-cockburn]] | D3 (capability = use case infra power, not user auth), D4 (TransactionPort port-adapter), D5 (port-external metadata 분리) |
|
|
| [[raw/official-docs/cqrs-fowler-bliki]] | D10 (read/write repo 물리 분리 안 함, 메서드 단위 capability) |
|
|
| [[raw/official-docs/microservices-io-transactional-outbox]] | D7 (EXTERNAL_OUTBOUND_ALLOWED = polling publisher broker publish) |
|
|
| [[raw/official-docs/archunit-user-guide]] | D8 (enforcement SSOT = ArchUnit annotation-based rule), D12 (coherence rule) |
|
|
| [[raw/official-docs/spring-tx-management-reference]] | D4 (TRANSACTION_REQUIRED ↔ TransactionPort, Spring `@Transactional` 직접 import 금지) |
|
|
|
|
### 외부 근거 / 대안 조사 (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" 참조. `@UseCaseRepositoryAccess` annotation / 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의 `TransactionPort` contract와 연결되어야 하며 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 *어휘*(`sensitiveRead` boolean + 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: `RetentionPolicy.RUNTIME`~~ (RUNTIME 은 as-built 와 일치)
|
|
- ~~target: `ElementType.METHOD` (use case method 단위)~~ → as-built `ElementType.TYPE` (use case **클래스** 단위)
|
|
- ~~value: `Capability[]` array~~ → as-built 4개 typed attribute
|
|
- ~~`Capability` enum 7개 flat~~ → as-built 차원별 분리(아래 정식 결정)
|
|
- 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`(enum `WRITE`/`READ_ONLY`/`REQUIRES_NEW`), `idempotency`(enum `IDEMPOTENT`/`KEYED`/`NOT_IDEMPOTENT`), `repositoryAccess`(enum `NONE`/`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_capability` fail (owner: application-port).
|
|
|
|
## 결정-근거 매핑
|
|
|
|
> 본 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 명세) | **ArchUnit static analysis 한계** — repository write 메서드 호출이 helper/mapper 를 경유하면 호출 그래프 추적 누락 가능. coherence rule 미구현(`planned`) — presence rule 만 존재. 본 결정은 *강제 의도*를 owner 로 고정하고 구현은 Phase C2 |
|
|
|
|
## 구현 가이드
|
|
|
|
> 본 §는 **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)`(클래스 단위) vs `METHOD`(원안) 선택도 외부 근거 없는 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`~~ **❌ REMOVED (2026-06-09 정합)** | (없음 — `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`~~ → **삭제(stale 정합)**. 노트가 live 룰로 잘못 기재했던 것 정정 |
|
|
| `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 → `planned` Claim (D12).
|
|
- **member/anonymous class**: presence rule 은 `areNotInterfaces/areNotAnonymousClasses/areNotMemberClasses` 로 제외 — inner static use case 는 강제 대상 아님(`UseCaseCapabilityTest` 의 `static final class` example 도 직접 평가 대상 아님). 신규 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 로만 보존.
|
|
- **다른 계약 의존**:
|
|
- [[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` ↔ `TransactionMode` enum + `TransactionPort.inRead/inWrite` 결합. `TransactionMode` enum 은 `application/transaction/` 에 구현됨(application-port slice 소유 가능). enum 이동 시 `UseCaseCapability` annotation 컴파일 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 — `KEYED` freeze(merge 전 금지)의 owner. 해제 트리거 merge: [[raw/branch-notes/feature-rate-limit-idempotency-contract]] (**2026-06-09 머지 완료 → freeze 룰 제거, KEYED 선언 허용**).
|
|
|
|
## 검증해야 할 주장
|
|
|
|
> 공식 문서나 사례는 근거지만, 내 프로젝트에서의 동작을 자동으로 보장하지 않는다. 구현 전/중/후에 실제로 검증해야 하는 주장을 분리한다.
|
|
|
|
| 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` — boolean `sensitiveRead()`/`bulkWrite()`/`crossTenantAdmin()` (default false) + coherence/매핑 javadoc.
|
|
- `application-core/.../capability/UseCaseCapabilityTest.java` — 신규 플래그 default/explicit reflection 검증 2 test + `ExampleAdminBulkUseCase` fixture.
|
|
- `app-bootstrap/.../architecture/CleanArchitectureTest.java` — D12 `read_only_use_cases_do_not_call_repository_write_methods` + D6 `bulk_write_capability_requires_write_repository_access` + **D7 `external_outbound_calls_require_external_outbound_allowed_capability`** 룰 + 3 custom `ArchCondition` + outbound-port precompute(`OUTBOUND_PORT_NAMES`, adapter 바인딩 식별) + `JavaMethodCall`/`JavaClasses`/`ClassFileImporter` import.
|
|
- `app-bootstrap/.../architecture/violations/application/{FixtureRepository,ReadOnlyRepositoryWriteUseCase,BulkWriteWithoutWriteAccessUseCase,OutboundWithoutPermissionUseCase}.java` — negative fixtures (public — `.class` isolation 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 row `annotation:` 필드 + 헤더를 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 verifyCleanArchitectureDependencies` PASS
|
|
- `./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 를 먼저 재컴파일하여 해소. 코드 오류 아님.
|
|
- **`.class` isolation corpus 가시성**: `ArchitectureViolationFixtureTest` 가 `.importClasses(X.class)` 로 fixture 를 isolated corpus 로 로드하려면 fixture 가 **public** 이어야 함(다른 패키지). package-private 로 두면 `is not visible` 컴파일 오류. `importPackages(string)` 만 쓰는 기존 fixture 는 package-private 가능 — 참조 방식에 따라 가시성 요건이 다름.
|
|
|
|
## 묶음
|
|
|
|
<!-- GENERATED: sources:start -->
|
|
- [[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]]
|
|
<!-- GENERATED: sources:end -->
|
|
|
|
<!-- GENERATED: blog-topics:start -->
|
|
- [[raw/blog-topics/repository-capability-archunit-fitness-function-2026-07-02]]
|
|
<!-- GENERATED: blog-topics:end -->
|
|
|
|
> 본 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):
|