458 lines
65 KiB
Markdown
458 lines
65 KiB
Markdown
---
|
||
title: branch / feature-capability-provider-selection-contract
|
||
source_type: branch-note
|
||
status: raw
|
||
branch: feature-capability-provider-selection-contract
|
||
parent_branch:
|
||
related_projects: [ca-skeleton]
|
||
governing_docs: [raw/project-notes/ca-skeleton-operational-contract]
|
||
tags: [branch, ca-skeleton, capability-platform, provider-selection, readiness]
|
||
created: 2026-07-28
|
||
target_merge:
|
||
status_label: in-progress
|
||
id: BR-CA-SKELETON-OPERATIONAL-CONTRACT-060
|
||
kind: project-work-item
|
||
project: ca-skeleton-operational-contract
|
||
work_item: WI-CA-SKELETON-OPERATIONAL-CONTRACT-060
|
||
inherits: [DEC-CA-SKELETON-OPERATIONAL-CONTRACT-CAPABILITY-SELECTION-001@1, DEC-CA-SKELETON-OPERATIONAL-CONTRACT-CAPABILITY-READINESS-001@1]
|
||
refines: []
|
||
overrides: []
|
||
depends_on: []
|
||
imports: []
|
||
delegates: []
|
||
accepts_delegations: []
|
||
contract_packet: 1
|
||
---
|
||
|
||
# branch: feature-capability-provider-selection-contract
|
||
|
||
> Layer: `raw/branch-notes/` — 단일 브랜치의 **TODO·결정·진행 기록**. 머지/종료 후 verified 결과는 `/ingest`로 `wiki/projects/`에 추출. 원본은 raw에 영구 보관.
|
||
> `status_label`: `in-progress` | `review` | `merged` | `abandoned`
|
||
> **스캐폴딩 상태** — 결정(D-row)·구현 가이드는 비어 있다. `/branch-spec feature-capability-provider-selection-contract` 로 채운다.
|
||
|
||
<!-- section-id: branch-parent -->
|
||
## 부모 (필수)
|
||
|
||
- **Parent project (canonical SSOT)**: [[raw/project-notes/ca-skeleton-operational-contract]]
|
||
|
||
> 분해 근거: `docs/superpowers/specs/2026-07-28-ca-skeleton-production-capability-feature-decomposition-design.md` §4.2 — 플랫폼 (Tier P). 본 branch 는 project §8.0 `WI-CA-SKELETON-OPERATIONAL-CONTRACT-060` 의 실행 단위다.
|
||
|
||
형제 branch (같은 부모의 다른 자식 — 인접 영역):
|
||
|
||
- [[raw/branch-notes/feature-env-driven-runtime-configuration]]
|
||
- [[raw/branch-notes/feature-integration-adapter-templates]]
|
||
- [[raw/branch-notes/feature-runtime-health-lifecycle-contract]]
|
||
|
||
<!-- section-id: branch-contract-packet -->
|
||
## 브랜치 계약 패킷
|
||
|
||
> project Work Item 에서 내려온 실행 계약의 snapshot. 여기에는 **pinned pointer + 1줄 요약 + branch 적용점**만 쓰고 상세를 복제하지 않는다.
|
||
|
||
- **생성 시 프로젝트 개정**: `1`
|
||
- **패킷 스키마**: `contract_packet: 1`
|
||
- **완료 조건**: capability descriptor·provider 선택·readiness 등급 검증 test 가 통과하고 bean 이름 기반 판정이 제거된다
|
||
|
||
<!-- section-id: inherited-project-decisions -->
|
||
### 상속한 프로젝트 결정
|
||
|
||
| Decision Ref | Project Summary | Branch Application | Source |
|
||
|---|---|---|---|
|
||
| `DEC-CA-SKELETON-OPERATIONAL-CONTRACT-CAPABILITY-SELECTION-001@1` | production capability는 available by default이고 inactive by default이며 활성화는 typed descriptor 기반 provider 선택으로만 이뤄진다 | `/branch-spec` 에서 적용 경계 작성 | [[raw/project-notes/ca-skeleton-operational-contract]] |
|
||
| `DEC-CA-SKELETON-OPERATIONAL-CONTRACT-CAPABILITY-READINESS-001@1` | 모든 capability는 R0~R3 readiness 등급과 capability card를 가지며 R0 seam을 R2 provider로 표기하지 않는다 | `/branch-spec` 에서 적용 경계 작성 | [[raw/project-notes/ca-skeleton-operational-contract]] |
|
||
|
||
<!-- section-id: branch-local-decisions -->
|
||
### 브랜치 지역 결정
|
||
|
||
> 상세 근거와 선택 조건은 아래 `## 결정-근거 매핑`의 동일 D-row 가 소유한다. 여기에는 요약과 관계만 둔다.
|
||
|
||
| Decision ID | Decision | Relation | Supporting Claims | Status |
|
||
|---|---|---|---|---|
|
||
| D1 | 런타임 능력 어휘를 use-case capability 와 분리 | `local` | `internal-code-fact` | `proposed` |
|
||
| D2 | 활성화 SSOT 는 provider/mode/boolean 중 한 축만 | `local` | `spring-boot-condition-annotations.md#SB-COND-C1` | `proposed` |
|
||
| D3 | 활성화 property 는 단일 prefix + `APP_*` env registry 주입 | `refines DEC-CA-SKELETON-OPERATIONAL-CONTRACT-CAPABILITY-SELECTION-001@1` | `config-spring-boot-externalized-configuration.md#SPRING-EXTCONFIG-C7` | `proposed` |
|
||
| D4 | production capability 에 `matchIfMissing = true` 금지 (프레임워크 기본값 유지) | `local` | `spring-boot-conditional-on-property-javadoc.md#SB-COND-JD-C1` | `proposed` |
|
||
| D5 | 활성화·안전 판정은 typed descriptor 로 (bean 이름 아님) | `local` | `spring-boot-condition-annotations.md#SB-COND-C3` | `proposed` |
|
||
| D6 | 토폴로지 검증은 refresh 완료 전, 실패 시 기동 거부 | `local` | `spring-boot-application-availability-startup.md#SB-AVAIL-C4` | `proposed` |
|
||
| D7 | readiness 반영은 descriptor 의 `readinessImpact` 로 판정 | `local` | `runtime-health-spring-actuator-groups.md#SB-HEALTH-C12` | `proposed` |
|
||
| D8 | 모든 capability 에 R0~R3 등급 + 등급별 증거 | `refines DEC-CA-SKELETON-OPERATIONAL-CONTRACT-CAPABILITY-READINESS-001@1` | `UNSUPPORTED_DECISION` | `proposed` |
|
||
| D9 | capability card 에 보증·비보증 + unsafe recipe ↔ 대체 + 경합 test 이름 | `refines DEC-CA-SKELETON-OPERATIONAL-CONTRACT-CAPABILITY-READINESS-001@1` | `UNSUPPORTED_DECISION` | `proposed` |
|
||
| D10 | startup 실패는 기존 registry code 재사용, 신규 code 금지 | `local` | `internal-code-fact` | `proposed` |
|
||
| D11 | descriptor registry 는 governance 스키마 준수 + 별도 파일 | `local` | sibling [[raw/branch-notes/feature-contract-registry-governance]] D4 | `proposed` |
|
||
| D12 | project note §11 실패 정책 매트릭스 채택 + `failurePolicy` 맵 파생 | `local` | project note §11 (owner 선언) | `proposed` |
|
||
| D13 | 비활성 capability 는 연결·워커·스키마·health contributor 미생성 | `local` | `spring-boot-condition-annotations.md#SB-COND-C1` | `proposed` |
|
||
| D14 | legacy 키 병행 시 SSOT 불일치는 startup 오류 | `local` | project note §36.3 규범 3 | `proposed` |
|
||
| D15 | capability card 필수 항목은 §36.2 원문 상속, narrow 금지 | `refines DEC-CA-SKELETON-OPERATIONAL-CONTRACT-CAPABILITY-READINESS-001@1` | project note §36.2 | `proposed` |
|
||
|
||
<!-- section-id: declared-overrides -->
|
||
### 선언한 예외
|
||
|
||
| Override ID | Overrides | Reason | Approval | Status |
|
||
|---|---|---|---|---|
|
||
|
||
<!-- GENERATED: project-contract-imports:start -->
|
||
## 가져온 프로젝트 계약
|
||
|
||
| Ref | Owner | 요약 | Branch 적용 |
|
||
|---|---|---|---|
|
||
<!-- GENERATED: project-contract-imports:end -->
|
||
|
||
<!-- section-id: branch-goal -->
|
||
## 목표
|
||
|
||
- `WI-CA-SKELETON-OPERATIONAL-CONTRACT-060` 의 완료 조건을 구현한다: capability descriptor·provider 선택·readiness 등급 검증 test 가 통과하고 bean 이름 기반 판정이 제거된다
|
||
|
||
- 이슈:
|
||
- PR:
|
||
|
||
<!-- section-id: branch-scope -->
|
||
## 범위
|
||
|
||
### 포함 범위
|
||
|
||
- capability descriptor 필수 필드 (capabilityId·providerId·readiness·guarantee·failurePolicy·multiInstance·requiredRole·readinessImpact·implVersion)
|
||
- 활성화 SSOT — `provider: disabled|<id>` / `dispatch-mode` / leaf `enabled` 의 적용 조건
|
||
- readiness 등급 R0~R3 정의와 승급 증거
|
||
- capability card 필수 항목 (unsafe recipe ↔ 안전한 대체 포함)
|
||
- fail-fast 토폴로지 검증 규칙
|
||
- 능력별 실패 정책 매트릭스의 owner
|
||
|
||
### 제외 범위
|
||
|
||
> 의도적으로 제외한 것. 면접 등에서 "이건 범위에 없었습니다"라고 답할 근거.
|
||
|
||
- 개별 capability 의 provider 구현 — 각 Tier T/C branch 소유
|
||
- capability 별 임계값·용량 수치
|
||
- 심층 명세 — 본 노트는 스캐폴딩. D-row 와 §구현 가이드는 `/branch-spec` 이 채운다.
|
||
- project decision registry 변경 — owner 는 project-note
|
||
|
||
## 근거 (필수, 최소 1개+)
|
||
|
||
> `/branch-spec feature-capability-provider-selection-contract` 단계에서 verbatim 인용과 함께 `raw/official-docs/` 로 수집한 뒤 여기서 링크한다.
|
||
|
||
| Source | 정당화하는 결정 |
|
||
|---|---|
|
||
| [[raw/official-docs/spring-boot-application-availability-startup]] | capability 토폴로지 검증은 startup 에서 fail-fast 로 수행하고 검증 실패는 컨텍스트 기동을 거부한다 / availability state(readiness·liveness)는 런타임 신호이지 startup 검증의 대체물이 아니다 — `SB-AVAIL-C1`~`C5` |
|
||
| [[raw/official-docs/spring-boot-condition-annotations]] | production capability 활성화를 `@ConditionalOnProperty` 로 게이트할 때 `matchIfMissing=true` 를 금지하는 결정(SB-COND-C1/C2), 조건 평가 시점·순서 의존성의 한계(SB-COND-C3/C4) |
|
||
| [[raw/official-docs/config-spring-boot-externalized-configuration]] | capability 활성화 property 를 단일 prefix 로 통일 — relaxed binding(SPRING-EXTCONFIG-C6/C7)·canonical form 권장(C8)·`@ConfigurationProperties` prefix→bean 명명 규칙(C9)·type-safe binding 이점(C10)·PropertySource 우선순위(C11, OS 환경변수가 config data 파일보다 우선)를 근거로, `ca-skeleton.*`/`app.*` 이중 prefix 를 `APP_*` 환경변수 registry 와 정합되는 단일 prefix 로 통일 |
|
||
| [[raw/official-docs/runtime-health-spring-actuator-groups]] | 활성화된 provider 만 readiness 에 반영 — optional cache 실패는 unready 미유발, session/idempotency/필수 lock 실패는 unready 유발. `StatusAggregator` 기반 aggregation(`SB-HEALTH-C10`,`C11`) + health group include/exclude 메커니즘(`SB-HEALTH-C9`) + "essential 하지 않은 외부 시스템은 포함하지 말 것" 판단 기준(`SB-HEALTH-C12`)이 근거. 단 "capability descriptor 로 판정 주체를 통일한다"는 것 자체는 본 자료가 증명하지 않는 ca-skeleton 자체 설계 결정 |
|
||
| [[raw/official-docs/spring-boot-conditional-on-property-javadoc]] | D4("`matchIfMissing=true` 금지")의 메커니즘 축 승급 근거 — `matchIfMissing` 의 선언된 기본값은 `false` 이고, property 가 `Environment` 에 전혀 없을 때 기본적으로 조건은 match 되지 않는다는 것이 annotation 공식 Javadoc 에 명시됨(`SB-COND-JD-C1`/`C2`). 단 "그래서 명시적으로 켜지 않는다"는 정책 자체는 여전히 internal |
|
||
| (추가 수집 대상) | 1차 수집 대상: 설계 §6 / §9 · ca-tmpl platform 설계 §6·§7 |
|
||
|
||
**프로젝트 내부 설계 참조 (등급 `internal-design-doc` — 공식 문서 아님, best practice 로 격상 금지):**
|
||
|
||
- ca-tmpl `docs/superpowers/specs/2026-07-26-production-capability-platform-design.md`
|
||
- llm-wiki `docs/superpowers/specs/2026-07-28-ca-skeleton-production-capability-feature-decomposition-design.md`
|
||
|
||
## TODO
|
||
|
||
각 항목 옆에 증거 등급 표기: `actually-implemented` | `locally-verified` | `prod-verified` | `documented-only` | `planned` | `needs-confirmation`
|
||
|
||
- [x] `/branch-spec` 로 D-row 11개·§구현 가이드 6절·§Audit & Findings 작성 — 등급: `documented-only`
|
||
- [ ] **미결 3건 사용자 확정** — (1) 런타임 능력 어휘, (2) 통일할 property prefix, (3) 등급 승급 자동 검사 방법 — 등급: `planned`
|
||
- [ ] A4 registry 미등록 키 4종 등록 — 등급: `planned`
|
||
- [ ] A3 notification dead config 를 notification-provider-spi 와 협의 — 등급: `planned`
|
||
- [ ] A5 `matchIfMissing=true` 6곳 개별 판정 — 등급: `planned`
|
||
- [ ] descriptor 도입 + `StartupSafetyValidator` 판정 입력 교체 — 등급: `planned`
|
||
- [ ] capability descriptor·provider 선택·readiness 등급 검증 test 가 통과하고 bean 이름 기반 판정이 제거된다 — 등급: `planned`
|
||
|
||
## 진행 중 메모
|
||
|
||
- **게이트 이력 (2026-07-28)**: depth 3회 · coverage 2회 실행. **coverage 최종 = Covered**(Blocking 0 / Should-fix 0 / Advisory 2, 2회차). **depth 최종 = Not ready**(Blocking 1 / Should-fix 4 / Advisory 3, 3회차) — `/branch-spec` 루프 천장 2회 도달로 종료. 잔여 finding 은 다음 세션 재개 대상.
|
||
- ⚠️ **최종 게이트 이후 적용된 편집 2건 (게이트 미검증)**: (1) §구현 가이드 3-1 의 대조축 시점 배정 — depth 3회차 Blocking #1 이 지적한 자기모순(§11 이 런타임 로딩되지 않는데 "startup 거부"라 씀)의 해소, (2) `failureMode`→`failurePolicy` 잔존 4곳 정정. **둘 다 게이트 재판정을 거치지 않았다.**
|
||
- 2026-07-28 `/branch-spec` 1회차. 외부 공식 문서 **5건** 수집(전량 self-grep 검증), sibling 3건 대조, ca-tmpl 코드 7개 영역 확인.
|
||
- depth/coverage 게이트 1회차가 Blocking 4건(depth 3 + coverage 1)을 잡아 보강 루프 1회 수행 — D12·D13·D14 신설, descriptor 기여·수집 메커니즘 확정, readiness group 정적 목록 현행(A8) 기록.
|
||
- 5번째 수집(`ConditionalOnProperty` Javadoc)은 depth Blocking #1 해소용 후속 dispatch. **`matchIfMissing` 기본값 `false` 확인** → D4 를 통째 `UNSUPPORTED_DECISION` 에서 "메커니즘 official / 정책 internal" 2축으로 승급.
|
||
- 수집 중 확인된 **근거 부재 2건**을 결정에 반영: (1) `matchIfMissing` 금지의 벤더 권고 부재 → D4 `UNSUPPORTED_DECISION`, (2) default readiness group 의 외부 의존성 정책 verbatim 부재(`SB-HEALTH-C8`) → §Claims To Verify.
|
||
- 같은 URL 이 이미 보관돼 있던 2건(external config / actuator health)은 새 파일을 만들지 않고 **다중 부모로 확장**했다 (`rules/linking-rules.md` §3).
|
||
|
||
## 결정 사항
|
||
|
||
- 2026-07-28: 런타임 능력 어휘를 use-case capability 와 분리 (D1) / 이유: `capabilities.yaml` 7 row 와 `@UseCaseCapability` 가 이미 그 낱말을 점유 / 검토한 대안: 기존 어휘 재정의(7 row + ArchUnit rule cascade) / 근거: `internal-code-fact`
|
||
- 2026-07-28: 활성화 판정을 bean 이름 presence 에서 typed descriptor 로 이전 (D5) / 이유: 같은 이름의 아무 bean 이나 있으면 안전으로 보고됨 + 조건 평가가 bean definition 순서에 의존 / 검토한 대안: bean 타입 기반 lookup(순서 의존은 동일) / 근거: `raw/official-docs/spring-boot-condition-annotations.md#SB-COND-C3`
|
||
- 2026-07-28: `matchIfMissing = true` 금지는 **외부 권고가 아니라 내부 정책**임을 명시 (D4) / 이유: Spring 공식 문서가 이 attribute 의 부재 시 의미를 설명하지 않음 / 근거: 코드 사실 — gRPC 서버와 filesystem object storage 가 설정 없이 활성
|
||
|
||
<!-- section-id: decision-evidence -->
|
||
## 결정-근거 매핑
|
||
|
||
> `Supporting Claims` 의 `raw/...#Cn` 은 verbatim 인용 + self-grep 검증을 마친 claim. `internal-code-fact` 는 ca-tmpl 코드를 직접 읽어 확인한 사실(경로·행 명시). 외부 공식 근거가 없는 결정은 `UNSUPPORTED_DECISION` 으로 라벨한다 — 추측으로 채우지 않는다.
|
||
>
|
||
> **근거의 두 축을 섞지 않는다.** 공식 문서가 증명하는 것은 *메커니즘*(Spring 이 어떻게 동작하는가)이고, *정책*(그래서 우리는 무엇을 금지하는가)은 대부분 내부 결정이다. 각 행의 Evidence Strength 가 이 경계를 표시한다.
|
||
|
||
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|
||
|---|---|---|---|---|---|
|
||
| D1 | 런타임 능력에는 `capability` 라는 낱말을 단독으로 쓰지 않는다 — 그 어휘는 이미 use-case repository access 개념이 점유하고 있으므로 런타임 축은 별도 어휘로 분리한다 | 기존 `@UseCaseCapability` + `capabilities.yaml` 을 유지 → 런타임 축에 신규 어휘. 기존 어휘를 런타임 의미로 재정의 → 7개 capability row 와 ArchUnit rule 전체 cascade (선택 안 함) | `internal-code-fact`: `src/application-core/src/main/java/dev/caskeleton/application/capability/UseCaseCapability.java`(+`RepositoryAccess`·`Idempotency`), `docs/registries/capabilities.yaml`(owner_branch `feature-repository-access-permission-contract`, 7 row) | `internal-code-fact` | 어휘 **명칭 자체**는 근거 없음 → §구현 가이드 1 에서 `UNSUPPORTED_IMPL_DECISION`. 명칭 확정 전 다른 branch 가 `capability` 를 런타임 의미로 쓰기 시작하면 되돌리기 비용 증가 |
|
||
| D2 | 활성화 SSOT 는 축 하나로 고정한다 — provider 축(`provider: disabled\|<id>`), mode 축(`dispatch-mode: ...`), 또는 축이 없는 leaf 의 boolean 중 **정확히 하나**만 한 capability 에 적용한다 | provider 대안이 둘 이상 → provider 축. 동작 방식만 갈리고 provider 는 하나 → mode 축. 켜고 끄는 것 외에 선택지가 없음 → boolean. 두 축을 동시에 두는 구성은 금지 | `SB-COND-C1`(`raw/official-docs/spring-boot-condition-annotations.md#SB-COND-C1` — property 존재·값 기준 매칭 메커니즘). 현행 4종 혼용: `internal-code-fact` — boolean(`ca-skeleton.tracing.enabled`·`ca-skeleton.grpc.enabled`·`ca-skeleton.fileserver.enabled`·`ca-skeleton.persistence-mongo.enabled`·`app.cache.redis.enabled`), provider id(`ca-skeleton.objectstorage.backend`·`app.messaging.broker`), mode(`app.cache.redis.client-mode`) | **메커니즘 `official-vendor-doc` / 정책 `internal-policy`** | 축 통일은 기존 5개 이상 config 클래스의 property 이름 변경 → breaking. 이행 창(legacy key 병행) 설계가 없으면 배포 중단 위험 |
|
||
| D3 | capability 활성화 property 는 단일 prefix 로 통일하고, 그 값은 `env-keys.yaml` 에 등록된 `APP_*` 환경변수에서만 주입한다 | 이미 `APP_*` 로 통일된 env registry 를 SSOT 로 유지 → property prefix 를 그에 맞춤. registry 를 포기하고 property 를 SSOT 로 삼는 대안은 sibling D2·D7 과 충돌하므로 선택 안 함 | `SPRING-EXTCONFIG-C7`·`C8`·`C9`(`raw/official-docs/config-spring-boot-externalized-configuration.md` — `_` 치환 relaxed binding, canonical kebab-case 권장, `@ConfigurationProperties` prefix→bean 명명). sibling: [[raw/branch-notes/feature-env-driven-runtime-configuration]] D2(`APP_` 전면 통일, registry=SSOT)·D7(`verifyEnvKeys` 3-way drift 검증) | **메커니즘 `official-vendor-doc` / 정책 `internal-policy` (sibling 결정 상속)** | 현행 `ca-skeleton.*` 4종(grpc·fileserver·persistence-mongo·objectstorage)은 `env-keys.yaml` 에 row 가 **0개** — 통일 전에 registry 등록이 선행돼야 하며 그 사이 `verifyEnvKeys` 가 실패할 수 있다 |
|
||
| D4 | production capability 는 **명시적 선택으로만** 활성화된다 — `matchIfMissing = true`·classpath 존재·local provider 기본값 **셋 다** 활성화 근거가 될 수 없다 (project note §36.3 규범 1 의 3 메커니즘 전부) | 운영 표면·보안 표면·외부 연결을 만드는 capability 의 **활성화 게이트** → 금지. 순수 관측(로깅 포맷 등) 또는 안전한 no-op 기본값 → 예외 허용하되 사유를 registry row 에 남김. **mode 축의 기본값 선택은 활성화 조건이 아니므로 D4 적용 밖** — 예: `app.cache.redis.client-mode=managed`(`matchIfMissing=true`)는 이미 활성화된 capability 안에서 구현 방식을 고르는 것이고, 실제 활성화 게이트는 `app.cache.redis.enabled`(`matchIfMissing=false`)다 | **메커니즘**: `SB-COND-JD-C2`(`raw/official-docs/spring-boot-conditional-on-property-javadoc.md#SB-COND-JD-C2` — "If the property is not contained in the `Environment` at all, the `matchIfMissing()` attribute is consulted. **By default missing attributes do not match.**"), `SB-COND-JD-C1`(`#SB-COND-JD-C1` — `matchIfMissing` **Defaults to false**). 즉 프레임워크 기본값이 이미 fail-closed 이고, `= true` 는 그 기본값을 **명시적으로 뒤집는 행위**다. `SB-COND-C2`(reference 페이지 — attribute 존재만 확인, L0)는 보조. 금지 대상 근거는 `internal-code-fact`: `adapter/inbound/grpc/.../GrpcServerConfig.java:18-22`(gRPC 서버 기본 ON), `adapter/outbound/objectstorage/.../ObjectStorageConfig.java:32-37`(filesystem object storage 기본 ON), 그 외 `TracingConfig.java:56-60`·`OutboxConfig.java:33-36`·`RedisCacheAdapterConfig.java:30-33`·`DistributedLockPersistenceConfig.java:25-29` | **메커니즘 `official-vendor-doc`(javadoc) / 금지 정책 `internal-policy`** — Javadoc 이 기본값 `false` 와 "부재 시 미매칭"을 명시하므로 메커니즘은 확정. 다만 Javadoc 은 *언제 이 attribute 를 쓰라/쓰지 말라* 는 권고를 담지 않으므로, production capability 에 대한 **금지 자체는 내부 정책**이다 (2026-07-28 Javadoc 수집으로 통째 `UNSUPPORTED_DECISION` 에서 승급) | 6곳 중 lock local provider(`havingValue="false"` + `matchIfMissing=true`)는 **의도된 single-instance 기본값**일 수 있다 — 일괄 금지 적용 전 개별 판정 필요(§Claims To Verify 1행). 또한 Javadoc 기본값이 `false` 라는 사실은 **`= true` 를 쓴 6곳이 전부 의도적 override** 였음을 뜻한다 — 각 override 의 원래 의도를 확인하지 않고 제거하면 기본 동작이 바뀐다 |
|
||
| D5 | 활성화·안전 판정은 bean 이름 presence 가 아니라 typed descriptor 로 한다 | 판정 대상이 "그 계약을 실제로 구현했는가" → typed descriptor. 단순 "빈이 있는가" 로 충분한 내부 배선 → 기존 방식 유지 가능 | `SB-COND-C3`(`#SB-COND-C3` — 조건은 "지금까지 처리된 것" 기준 평가, bean definition **순서**에 매우 주의), `SB-COND-C4`(`#SB-COND-C4` — 그래서 bean 조건은 auto-configuration 클래스에서만 권고). `internal-code-fact`: `app-bootstrap/.../runtime/StartupSafetyValidator.java:23-30` 이 문자열 5개(`distributedLockProvider`·`cacheStampedeProtection`·`outboxLeaderElection`·`distributedRateLimiter`·`migrationStartupRunner`)의 `containsBean` 으로 판정. sibling: [[raw/branch-notes/feature-env-driven-runtime-configuration]] D8 (동일 메커니즘을 자기 노트에서 이미 `UNSUPPORTED_IMPL_DECISION` 으로 라벨) | **`official-vendor-doc`** — 순서 의존 위험은 벤더 진술 | 이름이 같은 아무 bean 이나 있으면 안전으로 판정된다. descriptor 전환 전까지 multi-instance 안전 보고는 신뢰할 수 없다 |
|
||
| D6 | 토폴로지 검증은 context refresh 완료 **전**에 실행하고 실패 시 컨텍스트 기동을 거부한다 — `ApplicationRunner`/`CommandLineRunner` 로 옮기지 않는다 | 검증이 bean 실체를 봐야 함 + 실패 시 기동 거부가 필요 → refresh 전 singleton 초기화 시점. 기동 후 1회성 작업 → Runner | `SB-AVAIL-C4`(`raw/official-docs/spring-boot-application-availability-startup.md#SB-AVAIL-C4` — Runner 는 "once SpringApplication has started" 실행), `SB-AVAIL-C3`(`#SB-AVAIL-C3` — Liveness 이벤트는 `ApplicationStartedEvent`(refresh 이후) 다음), `SB-AVAIL-C5`(`#SB-AVAIL-C5` — startup 예외는 `ApplicationFailedEvent` 별도 경로). sibling: env-driven D8 의 trade-off(`EnvironmentPostProcessor` 는 bean 정의 이전이라 presence 검사 불가) | **`official-vendor-doc` (시점) + `internal-policy` (거부 정책)** | 검증 위치는 유지되고 *판정 방식*만 D5 로 바뀐다. 두 결정을 한 PR 에서 섞으면 회귀 원인 분리가 어렵다 |
|
||
| D7 | readiness 반영 여부는 descriptor 의 `readinessImpact` 로 결정하고, health group 구성은 그 값에서 파생한다 | optional·fallback 가능 capability → readiness 제외. 실패 시 요청을 올바르게 처리할 수 없는 capability(session·strict idempotency·필수 lock·필수 producer) → readiness 포함 | `SB-AVAIL-C2`(`#SB-AVAIL-C2` — readiness = 트래픽 라우팅 게이트), `SB-HEALTH-C9`(`raw/official-docs/runtime-health-spring-actuator-groups.md#SB-HEALTH-C9` — `management.endpoint.health.group.<name>` 으로 indicator include/exclude), `SB-HEALTH-C11`(`#SB-HEALTH-C11` — group 은 `StatusAggregator` 상속, override 가능), `SB-HEALTH-C12`(`#SB-HEALTH-C12` — 비필수 외부 시스템은 포함하지 말 것, essential 판단은 개발자 책임) | **메커니즘 `official-vendor-doc` / 어떤 capability 가 essential 인지는 `internal-policy`** | `SB-HEALTH-C8` 이 `needs-confirmation` 으로 남아 있다 — "default readiness group 이 외부 의존성을 포함하지 않는다" 의 단일 문장 verbatim 미확보. C12 가 부분 해소했을 뿐이다 |
|
||
| D8 | 모든 capability 는 R0~R3 readiness 등급을 갖고, 등급 주장에는 등급별 증거가 필요하다 | 실제 provider 없이 타입·seam 만 → `R0`. 단일 로컬 프로세스에서만 검증 → `R1`. 실서비스 통합 + 동시성·실패 test + runbook 확보 → `R2`. cluster·failover·rolling upgrade 검증 → `R3`. 증거가 다음 등급에 못 미치면 **낮은 등급 유지**가 기본이고 승급 주장은 금지 | **조사 범위(Spring 공식 4건) 내 대응 근거 미확보** — production-readiness/maturity 계열은 이번 회차에서 조사하지 않았다(비존재 단정 아님). 근거는 `internal-design-doc`: ca-tmpl `docs/superpowers/specs/2026-07-26-production-capability-platform-design.md` §6 | **`UNSUPPORTED_DECISION`** (조사 범위 내 미확보 + 내부 설계 결정) | 등급 판정이 사람의 선언에 의존한다. 자동 검사가 없으면 R0 를 R2 로 적는 것을 막지 못한다 — §구현 가이드 5 의 gate 가 유일한 방어 |
|
||
| D9 | capability card 는 보증과 **비보증**을 둘 다 적고, 흔한 unsafe recipe 와 그 안전한 대체를 쌍으로 제시하며, 그 경합을 재현하는 test 이름을 명시한다 | 모든 R2 이상 capability → 필수. R0/R1 → card 없이 한계 문서화만 | **조사 범위 내 미확보** — 프로젝트 고유 요구이며 대응 외부 표준을 이번 회차에서 조사하지 않았다. 근거는 `internal-design-doc`(platform 설계 §6) + 사용자 요구("함수를 쓰다 부딪히는 문제를 개발자가 알 수 있게") | **`UNSUPPORTED_DECISION`** (내부 결정) | card 작성이 형식적으로 흐를 위험. "경합 test 이름 명시" 를 필수 칸으로 둔 것이 유일한 강제 장치다 |
|
||
| D10 | startup 실패는 기존 registry 어휘를 재사용하고 새 error code 를 만들지 않는다 | descriptor 검증 실패 → `STARTUP_VALIDATION_FAILED`. 필수 provider 부재 → `REQUIRED_ADAPTER_DISABLED`. prod 금지 토글 → `PROFILE_MISMATCH`. 비활성 adapter 런타임 호출 → `ADAPTER_DISABLED` | `internal-code-fact`: `docs/registries/error-codes.yaml` — `STARTUP_VALIDATION_FAILED`(L841)·`REQUIRED_ADAPTER_DISABLED`(L855)·`PROFILE_MISMATCH`(L889) owner_branch `feature-migration-startup-contract`, `ADAPTER_DISABLED`(L875) owner_branch `feature-integration-adapter-templates`. 코드: `app-bootstrap/.../runtime/startup/StartupFailures.java` | `internal-code-fact` | 본 branch 는 **소비자**다. 의미 확장이 필요하면 owner branch 의 registry 변경 절차를 거쳐야 하며 여기서 단독 결정할 수 없다 |
|
||
| D15 | capability card 의 필수 항목은 project note §36.2 원문을 **그대로 상속**하고 본 branch 는 그 목록을 narrow 하지 않는다. D9 는 그중 강제력이 약해 별도 규범이 필요한 3개만 추가로 못박는다 | 13개 항목 전체 → §36.2 상속. 보증·비보증 동시 기재 / unsafe recipe ↔ 안전한 대체 / 경합 test 이름 → D9 가 추가 규범. 기본 실패 정책·override → D12 가 값 집합 소유 | project note §36.2 (13항목) | `internal-policy` (project note 상속 — 재진술 아님) | 상속 선언은 §36.2 가 바뀌면 자동 따라간다. 반대로 §36.2 개정 시 본 branch 가 알아채는 장치는 없다 — `/sync` 의 역참조 전파에 의존 |
|
||
| D12 | project note §11 의 실패 정책 매트릭스를 **그대로 채택**하고, descriptor 는 단일 `failureMode` 가 아니라 **(실패 종류 → 정책 kind) 맵**을 갖는다. provider 가 배정된 정책과 다른 kind 를 선언하면 startup 을 거부한다 | 매트릭스는 *(capability × 실패 종류)* 조합이다 — cache 는 backend 실패·codec 결함 2행, outbox 는 append·dispatcher 2행, rate limit 은 strict·availability 2행, notification 은 best-effort·durable 2행, lock 은 efficiency·fenced 2행. 따라서 capability 하나가 **여러 행에 대응**하며 단일 enum 파생은 성립하지 않는다. 어느 행에도 해당하지 않는 조합 → **매트릭스에 행을 추가하는 것이 선행**(본 branch 가 §11 owner). 값이 아니라 *선택지·파라미터*인 2행(efficiency lock = "선언된 use case 정책에 따라", strict rate limit = "fail closed **또는** bounded local emergency limiter")은 정책 kind `delegate-to-use-case` 로 표기하고 실제 값은 use case 선언을 참조한다 | project note §11 "Capability 별 실패 정책 매트릭스"(owner 선언 + 12행). `internal-design-doc`: ca-tmpl platform 설계 §7 "Failure policy is capability-specific, not globally 'fail open' or 'fail closed'." | `internal-policy` (project note §11 상속 — 재진술 아님, 값 집합 파생과 검증만 본 branch 소유) | 12행이 앞으로 등장할 capability 를 전부 덮는지 미검증. 매트릭스에 없는 조합이 나오면 provider 는 startup 을 통과하지 못하고 §11 개정이 선행돼야 한다 — 이 경직성은 의도된 것이나 이행 초기에 마찰을 만든다 |
|
||
| D13 | 비활성 capability 는 연결·백그라운드 워커·스키마·health contributor 를 만들지 않으며, 이를 negative fixture 로 검증한다 | 모든 선택 가능 capability → 기본 적용. classpath 존재만으로 auto-configuration 이 도는 third-party starter → 별도 배제 설정이 필요하며 그 사실을 registry row 에 남김 | project note §36.3 규범 2 ("미사용 capability 는 연결·백그라운드 워커·스키마·암묵적 런타임 동작을 만들지 않는다"). 메커니즘: `SB-COND-C1`(조건 미충족 시 configuration 미포함) | **정책 `internal-policy` (project note 상속) / 메커니즘 `official-vendor-doc`** | `@ConditionalOnProperty` 는 **본 프로젝트가 정의한 bean** 만 막는다. third-party starter 의 auto-configuration 이나 모듈 classpath 존재로 생기는 부수 효과는 이 조건으로 막히지 않는다 — negative fixture 가 그 차이를 드러내야 한다 |
|
||
| D14 | 이행 창 동안 legacy 활성화 키를 병행할 수 있으나, legacy 값과 SSOT 값이 **불일치하면 startup 오류**(`STARTUP_VALIDATION_FAILED`)로 처리한다 | 키 rename·축 통일(D2·D3) 이행 중 → 병행 허용 + 불일치 시 거부. 이행 완료 후 → legacy 키 제거, 잔존 시 startup 오류 | project note §36.3 규범 3 ("legacy flag 를 한시적으로 유지하는 경우, SSOT 와의 불일치는 startup 오류다"). 실패 코드는 D10 재사용 | `internal-policy` (project note 상속) | 병행 **메커니즘**은 미정 — §구현 가이드 2 의 `UNSUPPORTED_IMPL_DECISION` 참조. 불일치 판정 자체는 이 결정으로 확정되나, 두 키를 어떻게 동시에 읽을지는 별개 |
|
||
| D11 | capability descriptor registry 는 registry-governance 의 공통 스키마를 따르고, 기존 `capabilities.yaml` 과 **다른 파일**로 둔다 | 신규 registry family → universal 3 column(`owner_branch`·`compatibility_impact`·`required_test`) + family identity column 필수. 기존 파일 확장은 D1 의 어휘 충돌을 registry 층까지 끌고 오므로 선택 안 함 | sibling: [[raw/branch-notes/feature-contract-registry-governance]] D1(registry 없이 추가 금지)·D4(universal 3 column)·D6(구현 artifact 는 `ca-tmpl/docs/registries/` yaml) | `internal-policy` (sibling 결정 상속) | 신규 registry 파일명은 D1 의 어휘 확정에 종속 → §구현 가이드 1 의 `UNSUPPORTED_IMPL_DECISION` 과 같은 미결 항목 |
|
||
|
||
<!-- section-id: implementation -->
|
||
## 구현 가이드
|
||
|
||
> 3-rule meta principle 적용 — R1 각 sub-section 은 Decision ID + Claim ID reference, R2 근거 없는 detail 은 `UNSUPPORTED_IMPL_DECISION` + trade-off 한 줄, R3 본 branch 결정 범위 밖은 이관.
|
||
>
|
||
> 코드 위치 표기: ca-tmpl repo 기준 상대 경로. 본 branch 는 **계약 SSOT** 이고 실제 코드 작성은 ca-tmpl plan 이 소유한다 — 아래 클래스명·경로는 `planned` 이며 코드로 확인된 것만 `actually-implemented` 로 표기했다.
|
||
|
||
### 1. Descriptor 형태와 registry row
|
||
|
||
> **Trace**: D1(어휘 분리) · D11(registry 스키마) · sibling [[raw/branch-notes/feature-contract-registry-governance]] D4
|
||
>
|
||
> - **descriptor 기여·수집 메커니즘 (근거 있음 — 라벨 불필요)**: provider 가 자기 descriptor 를 **기여**하고 `app-bootstrap` 이 **수집·검증**한다. 근거 `internal-design-doc`: ca-tmpl platform 설계 §7 Capability descriptors — "Each active provider contributes a typed descriptor with at least:" (L318) 및 "`app-bootstrap` validates descriptors and selected settings. It does not accept arbitrary beans with a magic name." (L330). 즉 판정 입력은 **런타임 객체**이지 registry yaml 이 아니다.
|
||
> - **registry row 와 런타임 descriptor 의 역할 분리**: registry row(D11)는 *governance 기록* — 어떤 capability 가 존재하고 누가 owner 이며 어떤 test 가 강제하는지. 런타임 descriptor 는 *판정 입력* — 이번 기동에서 어떤 provider 가 실제로 활성이고 무엇을 보증하는지.
|
||
> - **둘의 대조는 startup 이 아니라 build/test 단계다** (2026-07-28 정정). 근거 `internal-code-fact`: registry yaml 은 런타임 resource 로 packaging 되지 않는다(`src/build.gradle` 에 registries → resources 복사 0건). 기존 drift 검사도 전부 빌드/테스트 시점이다 — `verifyEnvKeys` 는 Gradle task 로 `${rootProject.projectDir}/../docs/registries/env-keys.yaml` 을 읽고(`src/build.gradle:808-816`), `RepositoryAccessCapabilityRegistryTest` 는 `/docs` 가 gitignore 라 파일 부재 시 `Assumptions.assumeTrue` 로 **SKIP**(`:26-27`, `:53-55`). 따라서 registry 불일치는 **test 실패**이지 `STARTUP_VALIDATION_FAILED` 가 아니다.
|
||
> - **startup 이 대조하는 것은 descriptor ↔ 선택된 설정**이다 — 활성 provider 의 descriptor 가 선언한 보증·multi-instance 지원·필요 backing role 이 현재 설정과 맞는지. 여기서의 불일치만 `STARTUP_VALIDATION_FAILED`(D10)다.
|
||
> - **UNSUPPORTED_IMPL_DECISION**: 런타임 축의 **어휘와 registry 파일명**. 후보는 `runtime-capability` / `provider-capability` / `runtime-feature` 이며 어느 것도 외부 근거가 없다. trade-off — `runtime-capability` 는 기존 `capabilities.yaml`(use-case capability)과 낱말이 겹쳐 grep 혼선을 남기고, `runtime-feature` 는 sibling [[raw/branch-notes/feature-env-driven-runtime-configuration]] D9 의 "feature flag" 와 겹친다. **사용자 확정 전까지 어느 쪽도 코드에 박지 않는다.**
|
||
|
||
descriptor 필수 필드 (`planned`):
|
||
|
||
| 필드 | 의미 | 값 출처 |
|
||
|---|---|---|
|
||
| `capabilityId` | 안정 식별자. rename 은 breaking | 신규 registry row |
|
||
| `providerId` | 선택된 구현체 식별자 | 활성화 property 값 (D2) |
|
||
| `readiness` | `R0`\|`R1`\|`R2`\|`R3` (D8) | provider 가 선언, gate 가 검증 |
|
||
| `guarantee` | 이 provider 가 보장하는 것 (문자열 enum) | capability 별 branch 가 정의 |
|
||
| `failurePolicy` | **(실패 종류 → 정책 kind) 맵**. 단일 값이 아니다 — §11 매트릭스는 capability 당 1행이 아니라 *(capability × 실패 종류)* 조합이기 때문 (D12) | project note §11 매트릭스에서 파생 |
|
||
| `multiInstanceSupported` | multi-instance 에서 유효한가 | D5 판정 입력 |
|
||
| `requiredBackingRole` | 필요한 백엔드 역할 (예: Redis coordination role) | 기술 런타임 branch 가 정의 |
|
||
| `readinessImpact` | `none`\|`degraded`\|`unready` (D7) | health group 파생 입력 |
|
||
| `implementationVersion` | 구현 버전 — rolling upgrade 판정용 | 빌드 정보 |
|
||
|
||
registry row 는 universal 3 column(`owner_branch`·`compatibility_impact`·`required_test`)을 추가로 갖는다 (sibling [[raw/branch-notes/feature-contract-registry-governance]] D4).
|
||
|
||
### 2. 활성화 property 규약
|
||
|
||
> **Trace**: D2(축 단일화) · D3(prefix·env 정합) · D4(matchIfMissing 금지) · `SB-COND-C1` · `SPRING-EXTCONFIG-C7`~`C9`
|
||
>
|
||
> - **UNSUPPORTED_IMPL_DECISION**: 통일할 **prefix 문자열**(`ca-skeleton.*` 로 모을지 `app.*` 로 모을지). trade-off — `app.*` 은 `APP_` 환경변수와 relaxed binding 이 1:1(`APP_CACHE_REDIS_ENABLED` → `app.cache.redis.enabled`)이라 registry 정합 비용이 낮고, `ca-skeleton.*` 은 프로젝트 식별성이 높지만 `CA_SKELETON_*` 환경변수가 `APP_*` 통일(sibling D2)과 충돌한다. **정합 비용만 보면 `app.*` 이 우세하나 결정 근거는 없다.**
|
||
|
||
| 축 | 형태 | 적용 조건 |
|
||
|---|---|---|
|
||
| provider | `<prefix>.<capability>.provider = disabled \| <providerId>` | 구현체 대안이 2개 이상 |
|
||
| mode | `<prefix>.<capability>.<mode-name> = disabled \| <mode>` | provider 는 하나이고 동작 방식만 갈림 |
|
||
| boolean | `<prefix>.<capability>.enabled = true \| false` | 켜고 끄는 것 외 선택지 없음 |
|
||
|
||
규범:
|
||
|
||
- 한 capability 에 두 축을 동시에 두지 않는다 (D2).
|
||
- 모든 활성화 키는 `env-keys.yaml` row 를 가지며 `APP_*` 환경변수에서 주입된다 (D3). registry row 없는 키는 `verifyEnvKeys` 가 실패시킨다 (sibling env-driven D7).
|
||
- production capability 에는 `matchIfMissing = true` 를 쓰지 않는다 (D4). 프레임워크 기본값이 이미 `false` 이므로(`SB-COND-JD-C1`) **속성 부재 = 비활성**은 별도 조치 없이 성립한다.
|
||
- 나머지 두 활성화 경로도 막는다 (D4, §36.3 규범 1): **classpath 존재**는 D2 의 축 선언이 없으면 provider 로 인정하지 않으며, **local provider 기본값**(예: filesystem object storage)은 다른 provider 와 동일하게 명시 선택을 요구한다 — 기본 provider 라는 이유로 `matchIfMissing` 을 붙이지 않는다.
|
||
- **표기 계약**: `matchIfMissing = false` 를 **명시**한다 — 생략해도 의미는 같다(기본값 `false`).
|
||
|
||
> **UNSUPPORTED_IMPL_DECISION**: 위 "명시 vs 생략" 표기 규약. **as-built 는 혼재다** — main 소스의 `@ConditionalOnProperty` 22곳 중 `matchIfMissing` 을 지정한 것은 11곳(`= true` 6 / `= false` 5)이고 나머지 11곳은 생략이다. 즉 생략이 명시-false 보다 많다. trade-off — 명시는 장황하지만 self-documenting 이고 `= true` 를 실수로 넣었을 때 diff 에서 눈에 띈다. 생략은 간결하고 기본값을 신뢰하지만, 검토자가 Javadoc 기본값을 알아야 의도를 판정할 수 있다. **as-built 다수는 생략이므로 정합만으로는 tie-break 가 안 되고, 어느 쪽도 외부 근거는 없다.**
|
||
- placeholder 참조는 canonical kebab-case 로 쓴다 (`SPRING-EXTCONFIG-C8`).
|
||
- 이행 창 동안 legacy 키를 병행하되 SSOT 값과 불일치하면 startup 을 거부한다 (D14).
|
||
|
||
> **UNSUPPORTED_IMPL_DECISION**: legacy 키 **병행 메커니즘**. 후보 — (a) `application.yml` 에 두 키를 모두 두고 새 키를 SSOT 로 읽기, (b) `EnvironmentPostProcessor` 로 legacy → 신규 alias 주입, (c) legacy 키 사용 시 deprecated 경고만 남기고 값은 신규 키에서만 읽기. trade-off — (a)는 가장 단순하나 `verifyEnvKeys` 의 3-way drift 검사(sibling env-driven D7)를 이행 기간 동안 우회해야 하고, (b)는 registry 를 건드리지 않지만 relaxed binding 과 alias 가 겹칠 때 우선순위가 불투명하며, (c)는 깔끔하나 legacy 키만 설정한 배포를 조용히 비활성으로 만들어 D14 의 "불일치 = 오류" 와 충돌한다. **(a)가 D14 와 가장 정합하나 결정 근거는 없다.**
|
||
|
||
### 3. Startup 검증 배치와 실패 매핑
|
||
|
||
> **Trace**: D5(typed descriptor) · D6(검증 시점) · D10(실패 어휘) · `SB-COND-C3`/`C4` · `SB-AVAIL-C3`/`C4`/`C5`
|
||
|
||
- 검증 위치는 현행을 유지한다 — `SmartInitializingSingleton` (refresh 완료 전, 모든 singleton 초기화 후). `ApplicationRunner` 는 "이미 시작된 후" 실행되므로 부적합하다 (`SB-AVAIL-C4`).
|
||
- 판정 입력만 교체한다: `beanFactory.containsBean("<name>")` → 등록된 descriptor 집합. `actually-implemented` 현행은 `app-bootstrap/.../runtime/StartupSafetyValidator.java:23-30`(이름 목록) + `:78-79`(`containsBean` 호출 루프) — **교체 대상은 둘 다**.
|
||
- 실패 매핑 (D10 — 전부 기존 registry code):
|
||
|
||
| 조건 | code | 기존 owner |
|
||
|---|---|---|
|
||
| descriptor 스키마·정합성 위반 | `STARTUP_VALIDATION_FAILED` | `feature-migration-startup-contract` |
|
||
| 필수 capability 의 provider 부재 또는 보증 미달 | `REQUIRED_ADAPTER_DISABLED` | `feature-migration-startup-contract` |
|
||
| prod 프로파일에서 금지 토글 활성 | `PROFILE_MISMATCH` | `feature-migration-startup-contract` |
|
||
| 비활성 capability 를 런타임에 호출 | `ADAPTER_DISABLED` | `feature-integration-adapter-templates` |
|
||
|
||
### 3-1. 실패 정책 kind 와 매핑 규칙
|
||
|
||
> **Trace**: D12 · project note §11 (재진술 아님 — 값 집합 파생과 검증만 소유)
|
||
>
|
||
> - **UNSUPPORTED_IMPL_DECISION**: 정책 kind 의 **명칭**. 아래 7개는 §11 12행의 동작을 축약한 것이며 외부 근거가 없다. trade-off — 축약하면 descriptor 가 짧아지지만 §11 문장과 1:1 대조가 어려워지고, 문장을 그대로 값으로 쓰면 대조는 쉬우나 오타·표기 흔들림이 생긴다.
|
||
|
||
| 정책 kind | §11 대응 행 | 의미 |
|
||
|---|---|---|
|
||
| `fail-open` | optional cache(backend 실패), best-effort notification·message | source 로 fallback 또는 무시하고 진행, degraded 신호 |
|
||
| `fail-closed` | cache codec 결함, session store, keyed mutation idempotency, fenced correctness lock, strict rate limit(기본) | 해당 연산을 실패시킨다 |
|
||
| `degrade-with-fallback` | availability 지향 rate limit | 제한된 local fallback 으로 계속하되 degraded 신호 필수, 조용한 무제한 통과 금지 |
|
||
| `rollback-transaction` | outbox append | 업무 트랜잭션을 롤백 |
|
||
| `accumulate-and-alert` | outbox dispatcher 장애 | write 는 계속 축적, lag·backlog 알림 |
|
||
| `durable-retry` | durable notification·message | outbox/inbox + 재시도 + 종단 실패 경로 |
|
||
| `delegate-to-use-case` | efficiency lock, strict rate limit(변형 선택 시) | 값이 아니라 선택지 — 실제 동작은 use case 선언이 정한다 |
|
||
|
||
규범: descriptor 의 `failurePolicy` 맵 키는 **실패 종류**(예: `backend-unavailable`, `codec-error`, `append`, `dispatch`)이고 값은 위 kind 다.
|
||
|
||
**대조축의 시점 배정** (§구현 가이드 1 이 세운 "governance 문서는 런타임 입력이 아니다" 원칙을 그대로 적용):
|
||
|
||
| 대조 | 시점 | 실패 표현 |
|
||
|---|---|---|
|
||
| §11 매트릭스 ↔ descriptor 선언 — 그 (capability × 실패 종류) 조합이 매트릭스에 존재하는가, 배정된 kind 와 같은가 | **build/test** (§11 은 런타임 로딩되지 않는다) | contract test 실패 |
|
||
| descriptor 선언 ↔ 선택된 설정 — 예: `degrade-with-fallback` 인데 fallback 설정이 없음 | **startup** | `STARTUP_VALIDATION_FAILED` (D10) |
|
||
|
||
이 분리로 app-bootstrap 에 §11 12행을 상수 테이블로 재진술할 필요가 없다 — D12 의 Reference-Only 를 코드 층까지 유지한다.
|
||
|
||
> **UNSUPPORTED_IMPL_DECISION**: build/test 대조의 **구현 위치**. 후보 — (a) 기존 registry drift test 패턴을 따라 `app-bootstrap` test 에 두기(단, `/docs` 가 gitignore 라 파일 부재 시 SKIP 되는 한계를 그대로 물려받는다), (b) `verifyEnvKeys` 처럼 Gradle task 로 두기(CI 에서 확실히 돌지만 IDE 피드백이 늦다). trade-off 는 "기존 패턴 일관성" vs "SKIP 되지 않는 강제력" 이며 어느 쪽도 외부 근거가 없다.
|
||
|
||
### 3-2. 비활성 capability 의 리소스 미생성 검증
|
||
|
||
> **Trace**: D13 · `SB-COND-C1`
|
||
>
|
||
> - **UNSUPPORTED_IMPL_DECISION**: third-party starter 배제 **수단**. 후보 — (a) `spring.autoconfigure.exclude` 목록, (b) 해당 starter 를 optional 모듈에만 두고 core classpath 에서 제외, (c) 배제 없이 negative fixture 로 검출만. trade-off — (a)는 즉시 적용되나 클래스명 하드코딩이 버전 업그레이드에 취약하고, (b)는 근본적이나 모듈 registry 변경(19-leaf 고정)을 건드리며, (c)는 비용이 없지만 문제를 막지 못하고 알려주기만 한다.
|
||
|
||
negative fixture 가 assert 할 4종 (D13 의 "연결·워커·스키마·health contributor"):
|
||
|
||
| 대상 | 관측 방법 |
|
||
|---|---|
|
||
| 연결 | 해당 provider 의 client/connection factory bean 부재 |
|
||
| 백그라운드 워커 | scheduler·listener·relay bean 부재 및 스레드 미생성 |
|
||
| 스키마 | 해당 capability 소유 테이블·인덱스 DDL 미실행 |
|
||
| health contributor | `/actuator/health` component 목록에 해당 이름 부재 |
|
||
|
||
registry row 의 `required_test` 에는 위 fixture 이름을 적는다 (sibling [[raw/branch-notes/feature-contract-registry-governance]] D2 — "registry 항목은 최소 1개 이상의 contract test 와 연결").
|
||
|
||
### 4. Readiness 반영 규칙
|
||
|
||
> **Trace**: D7 · `SB-AVAIL-C2` · `SB-HEALTH-C9`/`C11`/`C12` · as-built `application.yml:250-251`
|
||
>
|
||
> - **현행 (as-built)**: readiness group 은 **정적 목록**이다 — `src/app-bootstrap/src/main/resources/application.yml:250-251` 의 `readiness: include: readinessState,db`. 소유 주석은 같은 파일 L236-237 로 [[raw/branch-notes/feature-runtime-health-lifecycle-contract]] 를 가리킨다.
|
||
> - **시점 문제 (미확인 가정)**: descriptor 는 `SmartInitializingSingleton`(refresh 완료 전 후반, D6)에 수집되는 반면 health group 은 property 바인딩으로 구성된다 — 두 시점의 선후는 **수집한 자료로 확인되지 않았다**. 수집한 actuator 문서는 group 의 property 구성(`SB-HEALTH-C9`)·aggregation(`C10`/`C11`)·essential 판단(`C12`)만 담고 group 구성 시점이나 프로그래매틱 확장점을 다루지 않는다. 이 가정이 틀리면 아래 후보 (a)·(c)의 기각 근거가 무너진다 → §Claims To Verify.
|
||
> - **UNSUPPORTED_IMPL_DECISION**: 파생 메커니즘. 후보 — (a) `EnvironmentPostProcessor` 가 descriptor 선언을 읽어 include 목록을 주입, (b) 정적 yaml 을 유지하고 descriptor 와의 **일치를 test 로 강제**, (c) 커스텀 group registry 를 직접 구현. trade-off — (a)는 시점을 맞추지만 descriptor 를 bean 이전에 알아야 해서 D5 의 typed 판정과 다른 소스(설정)를 봐야 하고, (b)는 구현이 가장 작고 D6 시점과 충돌하지 않으나 **설정 누락을 배포 후에야 test 로 잡는다**, (c)는 자유도가 크지만 Spring 의 group 계약을 재구현하는 비용이 든다(**미확인 — 프로그래매틱 확장점 존재 여부를 확인하지 않았다**). **비용만 보면 (b)가 우세하나 결정 근거는 없다.**
|
||
> - **`db` 처럼 capability 모델 밖 indicator**: 현행 include 의 `db` 는 Spring Boot DataSource 가 자동 기여하는 indicator 로 본 branch 의 descriptor 모델에 대응 행이 없다. **capability 모델 밖 indicator 의 group 편입은 [[raw/branch-notes/feature-runtime-health-lifecycle-contract]] D10(Required vs Optional Dependency Matrix) 소유**이며 본 branch 는 건드리지 않는다.
|
||
|
||
- `readinessImpact: unready` 인 활성 capability 만 readiness group 에 포함한다. group 구성은 `management.endpoint.health.group.readiness` 의 include 목록으로 표현한다 (`SB-HEALTH-C9`).
|
||
- `readinessImpact: degraded` 는 component health 에는 나타나되 readiness group 에는 넣지 않는다 — 비필수 외부 시스템을 readiness 에 넣지 말라는 기준 (`SB-HEALTH-C12`).
|
||
- 비활성 capability 는 health contributor 를 등록하지 않는다. 등록되지 않은 것은 aggregation 에도 참여하지 않는다 (`SB-HEALTH-C10`).
|
||
- liveness group 에는 어떤 capability 도 넣지 않는다 — liveness 는 프로세스 자체 신호다 (`SB-AVAIL-C1`).
|
||
|
||
> **OUT_OF_BRANCH_SCOPE**: health endpoint 의 **shape·노출·인증**은 [[raw/branch-notes/feature-runtime-health-lifecycle-contract]] D2 와 [[raw/branch-notes/feature-management-actuator-security-contract]] 소유다. 본 branch 는 *어떤 capability 가 어느 group 에 들어가는지* 의 판정 입력만 정한다.
|
||
|
||
### 5. Readiness 등급과 card 게이트
|
||
|
||
> **Trace**: D8 · D9
|
||
>
|
||
> - **UNSUPPORTED_IMPL_DECISION**: 등급 승급의 **자동 검사 방법**. 후보는 (a) descriptor 의 `readiness` 값과 test 태그를 대조하는 Gradle 검사, (b) card 문서의 필수 칸 존재를 검사하는 린터, (c) 리뷰 체크리스트(수동). trade-off — (a)만이 위조를 막지만 test 태그 규약이 선행돼야 하고, (c)는 즉시 가능하나 D8 의 Open Risk 를 해소하지 못한다.
|
||
|
||
- R2 이상을 주장하려면 card 에 **보증과 비보증이 둘 다** 있어야 한다 (D9).
|
||
- card 의 "unsafe recipe ↔ 안전한 대체" 칸에는 그 경합을 재현하는 **test 이름**을 적는다. test 이름이 없으면 그 보증은 card 에 쓸 수 없다.
|
||
- 등급별 요구 증거는 project note §36.1 을 상속한다 — 본 branch 는 재진술하지 않는다.
|
||
- **card 의 나머지 항목(owner 모듈·semantic port / provider ID·readiness 등급 / 필요한 토폴로지·영속·축출 정책 / 설정 키·secret / liveness·readiness 영향 / bounded cardinality metric·trace span / 적합·부적합 지침·비용 모델·자원 한계 / 집중·통합·실패 test 명령 / rolling upgrade·복구 노트 / runbook 링크)도 project note §36.2 원문을 그대로 상속한다.** 본 branch 는 그 목록을 narrow 하지 않으며, D9 는 그중 **강제력이 약해 별도 규범이 필요한 3개**(보증·비보증 동시 기재 / unsafe recipe ↔ 안전한 대체 / 경합 test 이름)만 추가로 못박는다.
|
||
- card 의 "기본 실패 정책과 허용되는 override" 항목은 **D12** 가 정한 값 집합을 쓴다 — §11 매트릭스 12행에서 파생된 enum 외의 값은 card 에 쓸 수 없다.
|
||
|
||
### 6. 현행 → 목표 이행
|
||
|
||
> **Trace**: D2·D3·D4·D5 의 적용 대상. 각 항목의 현행 상태는 §Audit & Findings 의 코드 근거와 1:1.
|
||
|
||
| 단계 | 내용 | 선행 조건 |
|
||
|---|---|---|
|
||
| 1 | 미등록 활성화 키 4종(grpc·fileserver·persistence-mongo·objectstorage)을 `env-keys.yaml` 에 등록 | sibling env-driven D7 절차 |
|
||
| 2 | notification dead config 해소 — 조건 키와 yml 키 일치 (A3) | owner 판정 필요 (§Audit A3) |
|
||
| 3 | `matchIfMissing=true` 6곳 개별 판정 → production capability 는 제거 | D4 |
|
||
| 4 | 활성화 축 통일 + prefix 통일 (legacy key 병행 창 포함) | D2·D3, 어휘·prefix 확정 |
|
||
| 5 | descriptor 도입 후 `StartupSafetyValidator` 판정 입력 교체 | D5, 1~4 완료 |
|
||
|
||
> **OUT_OF_BRANCH_SCOPE**: 각 capability 의 **provider 구현**(Redis role, Kafka producer, session store 등)은 Tier T/C branch 소유다. 본 §은 활성화·판정·readiness 반영의 공통 계약만 다룬다.
|
||
|
||
<!-- section-id: edge-failure-dependency -->
|
||
## 엣지·실패·의존
|
||
|
||
- **실패·엣지 경로**
|
||
- descriptor 를 선언하지 않은 provider 가 활성화됨 → `STARTUP_VALIDATION_FAILED` 로 기동 거부 (D10). 조용히 통과시키면 D5 가 해결하려는 문제가 그대로 남는다.
|
||
- 같은 capability 에 두 provider 가 동시에 활성 → 기동 거부. `@ConditionalOnProperty` 는 상호 배타를 보장하지 않으므로 descriptor 수집 시점에 중복을 검사해야 한다 (D2).
|
||
- 활성화 property 는 켜져 있으나 필요한 backing role 설정이 없음 → `REQUIRED_ADAPTER_DISABLED`.
|
||
- descriptor 는 `multiInstanceSupported: false` 인데 `APP_MULTI_INSTANCE_ENABLED=true` → 기동 거부 (H8 이전 대상 계약).
|
||
- 비활성 capability 의 port 를 use case 가 호출 → `ADAPTER_DISABLED` (런타임 실패, 기동 실패 아님).
|
||
- readiness group 에 넣을 capability 가 하나도 없음 → **capability 유래 항목만** 비우고 기동은 계속. `readinessState` 는 capability 가 아니므로 항상 유지한다(제거하면 k8s readiness probe 게이트 자체를 잃는다). readiness 가 항상 UP 이 되는 것을 **정상**으로 문서화한다 (`SB-HEALTH-C12` 의 판단 기준).
|
||
- 검증 자체가 실패로 기동을 막을 때 `ApplicationFailedEvent` 경로로 빠지며 availability state 는 설정되지 않는다 (`SB-AVAIL-C5`) — 로그만이 유일한 진단 수단이다.
|
||
|
||
- **다른 계약 의존**
|
||
- [[raw/branch-notes/feature-env-driven-runtime-configuration]] D2·D7·D8 — `APP_` prefix 통일, `verifyEnvKeys` drift 검증, multi-instance flag. **D8 의 판정 메커니즘은 본 branch 로 이전(H8)**, flag 자체와 registry row 는 env-driven 유지. 그 계약이 바뀌면 D3·D5 가 직접 영향받는다.
|
||
- [[raw/branch-notes/feature-contract-registry-governance]] D1·D4·D6 — 신규 registry family 의 스키마와 저장 위치. D11 이 전적으로 의존한다.
|
||
- [[raw/branch-notes/feature-migration-startup-contract]] — `STARTUP_VALIDATION_FAILED`·`REQUIRED_ADAPTER_DISABLED`·`PROFILE_MISMATCH` 의 owner. D10 은 소비만 한다.
|
||
- [[raw/branch-notes/feature-integration-adapter-templates]] — `ADAPTER_DISABLED` owner. 본 branch 가 활성화 SSOT 를 가져가면 그 branch 는 소비자로 강등된다(설계 §5.3).
|
||
- [[raw/branch-notes/feature-runtime-health-lifecycle-contract]] D2·D9·D10 — health endpoint shape 와 required/optional dependency 구분의 owner. D7 은 그 위에 판정 입력만 얹는다.
|
||
- [[raw/branch-notes/feature-repository-access-permission-contract]] — `capabilities.yaml` 과 `@UseCaseCapability` 의 owner. D1 의 어휘 분리는 이 계약을 **건드리지 않기 위한** 결정이다.
|
||
|
||
## Audit & Findings
|
||
|
||
> 2026-07-28 `/branch-spec` 의 §2 ground truth 확인에서 ca-tmpl 코드·registry·yml 을 직접 읽어 발견한 정합 문제. **전부 `actually-implemented` 사실**(경로·행 명시)이며 추측이 아니다. 각 항목은 §구현 가이드 6 의 이행 단계와 1:1 대응한다.
|
||
>
|
||
> 본 branch 가 단독으로 고칠 수 없는 항목은 owner 를 명시했다 — 자동 수정하지 않고 정합 권고만 남긴다.
|
||
|
||
| ID | 발견 | 근거 (ca-tmpl 상대 경로) | 판정 | owner |
|
||
|---|---|---|---|---|
|
||
| A1 | **활성화 규약 4종 혼용** — boolean `enabled` / provider id `backend`·`broker` / mode `client-mode` 가 동시에 쓰인다 | `GrpcServerConfig.java:18-22`, `FileExportConfig.java:26-29`, `MongoPersistenceConfig.java:21-24`, `ObjectStorageConfig.java:32-37`, `KafkaAdapterConfig.java:23`, `RedisCacheAdapterConfig.java:30-38` | `ACTIVATION_AXIS_DRIFT` — D2 로 통일 대상 | 본 branch |
|
||
| A2 | **property prefix 이중화** — `ca-skeleton.*` 와 `app.*` 이 공존. 반면 env registry 는 `APP_*` 로 통일됨 | 위 파일들 + `docs/registries/env-keys.yaml`(108행 중 106행이 `APP_` 105 + `SPRING_PROFILES_ACTIVE` 1. 예외 2건은 third-party 소유 키 — `MANAGEMENT_SERVER_PORT`(L72)·`OTEL_EXPORTER_OTLP_ENDPOINT`(L711)) | `PREFIX_DRIFT` — D3 대상. prefix 문자열은 미확정 | 본 branch (+ env-driven 협의) |
|
||
| A3 | **notification dead config** — 어댑터는 `app.notification.google-email.enabled` / `app.notification.slack-webhook.enabled` 로 조건 평가하나, `application.yml` 과 registry 에는 `app.notification.{slack,email}.provider` 만 존재. `matchIfMissing=false` 이므로 **문서화된 설정으로는 두 어댑터가 활성화되지 않는다** | `GoogleEmailNotificationAdapterConfig.java:19`, `SlackNotificationAdapterConfig.java:19` vs `app-bootstrap/src/main/resources/application.yml:500-506`, `env-keys.yaml`(`APP_NOTIFICATION_SLACK_PROVIDER`·`APP_NOTIFICATION_EMAIL_PROVIDER` 2행) | `DEAD_CONFIG` — 활성화 불가 | [[raw/branch-notes/feature-notification-provider-spi]] 와 협의 필요 |
|
||
| A4 | **registry 미등록 활성화 키 4종** — grpc·fileserver·persistence-mongo·objectstorage 관련 키가 `env-keys.yaml` 에 **0개** | `grep -c "name: APP_{GRPC,FILESERVER,MONGO,OBJECTSTORAGE}" docs/registries/env-keys.yaml` = 0 | `REGISTRY_GAP` — sibling [[raw/branch-notes/feature-contract-registry-governance]] D1("registry 없이 추가하지 않음") 위반 상태 | 본 branch (등록) + registry-governance (절차) |
|
||
| A5 | **속성 부재로 활성화되는 운영 표면 3건** — gRPC 서버, filesystem object storage, outbox relay scheduler 가 아무 설정 없이 켜진다. 나머지 3곳(`TracingConfig`·`RedisCacheAdapterConfig` mode 축·`DistributedLockPersistenceConfig` local provider)은 관측 또는 mode 축·의도된 single-instance 기본값으로 **D4 적용 밖**(판정 근거는 §Claims To Verify 1행) | `GrpcServerConfig.java:18-22`, `ObjectStorageConfig.java:32-37`, `OutboxConfig.java:33-36`(`ca-skeleton.outbox.relay-enabled` — DB 폴링 + broker 발행이라는 외부 연결을 만든다) | `IMPLICIT_ACTIVATION` — D4 대상 | 본 branch (계약) + 각 adapter branch (적용) |
|
||
| A6 | **bean 이름 문자열 판정** — multi-instance 안전을 bean 이름 5개의 presence 로 판정. 같은 이름의 아무 bean 이나 있으면 안전으로 보고된다 | `StartupSafetyValidator.java:23-30` (`REQUIRED_MULTI_INSTANCE_BEANS` + `containsBean`) | `UNTYPED_SAFETY_CHECK` — D5 대상. 공식 문서의 순서 의존 경고(`SB-COND-C3`)와도 어긋남 | **본 branch (H8 이전 수신)** — 원 owner [[raw/branch-notes/feature-env-driven-runtime-configuration]] D8 |
|
||
| A8 | **readiness group 이 정적 목록** — `readiness: include: readinessState,db` 가 yml 에 손으로 적혀 있고, `db` 는 Spring Boot DataSource 자동 기여 indicator 라 본 branch 의 descriptor 모델에 대응 행이 없다 | `src/app-bootstrap/src/main/resources/application.yml:250-251`(소유 주석 L236-237 → [[raw/branch-notes/feature-runtime-health-lifecycle-contract]]) | `STATIC_READINESS_GROUP` — D7 의 "descriptor 에서 파생" 과 시점·형태가 어긋난다. 파생 메커니즘은 §구현 가이드 4 의 `UNSUPPORTED_IMPL_DECISION` | 본 branch (판정 입력) + [[raw/branch-notes/feature-runtime-health-lifecycle-contract]] (group shape·비-capability indicator) |
|
||
| A7 | **`capability` 어휘 충돌** — `capabilities.yaml`(7 row)과 `@UseCaseCapability` 가 use-case repository access 의미로 이미 점유 | `docs/registries/capabilities.yaml`, `application-core/.../application/capability/UseCaseCapability.java` | `TERM_COLLISION` — D1 로 분리. 명칭 미확정 | 본 branch |
|
||
|
||
**이관 기록 (H8, 2026-07-28)** — multi-instance 활성화 판정 메커니즘의 owner 가 [[raw/branch-notes/feature-env-driven-runtime-configuration]] D8 에서 본 branch 로 이동했다. `APP_MULTI_INSTANCE_ENABLED` flag 자체·`env-keys.yaml` row·`APP_` prefix 통일(D2)·drift 검증(D7)은 env-driven 이 계속 소유한다. 설계 근거: `docs/superpowers/specs/2026-07-28-ca-skeleton-production-capability-feature-decomposition-design.md` §5.1. 최초 설계는 이전 원천을 `feature-background-job-async-contract` 로 잘못 적었고 본 `/branch-spec` 에서 정정했다.
|
||
|
||
<!-- section-id: claims-to-verify -->
|
||
## 검증해야 할 주장
|
||
|
||
| Claim | Why uncertain | How to verify | Status |
|
||
|---|---|---|---|
|
||
| `matchIfMissing=true` 6곳의 D4 적용/비적용 분류가 옳다 — 특히 lock local provider 는 의도된 single-instance 기본값이고, `client-mode` 는 활성화 게이트가 아닌 mode 축이다 | D4 를 일괄 적용하면 single-instance 기본 동작이 깨질 수 있는데, 그 의도가 코드 주석에만 있고 결정으로 박혀 있지 않다 | `DistributedLockPersistenceConfig.java:25-29` 의 `havingValue="false"` 조합을 [[raw/branch-notes/feature-distributed-lock-contract]] D3 와 대조 + `RedisCacheAdapterConfig.java:30-40` 의 두 조건(`client-mode` mode 축 / `enabled` 활성화 게이트) 분리 확인 | `needs-confirmation` |
|
||
| notification 어댑터가 실제로 활성화된 적이 없다 (A3) | 조건 키가 yml·registry 어디에도 없으나, 테스트나 별도 profile 에서 주입될 가능성을 배제하지 못했다 | ca-tmpl 에서 `grep -rn "google-email.enabled\|slack-webhook.enabled" src/` 로 test·resources 포함 전수 확인 | `needs-confirmation` |
|
||
| descriptor 수집이 `SmartInitializingSingleton` 시점에 완전하다 | 모든 singleton 초기화 후이지만, lazy bean 이나 조건부로 늦게 등록되는 provider 가 있으면 누락될 수 있다 | descriptor 미등록 provider 를 심은 negative fixture 로 `STARTUP_VALIDATION_FAILED` 발생 확인 | `planned` |
|
||
| readiness group 에서 제외된 `degraded` capability 가 실제로 트래픽 차단을 유발하지 않는다 | `SB-HEALTH-C11` 상 group 이 aggregator 를 상속하므로, component health 의 DOWN 이 다른 경로로 readiness 에 전파될 여지가 남는다 | Redis 를 내린 상태에서 `/actuator/health/readiness` 가 UP 을 유지하는지 통합 test | `planned` |
|
||
| "default readiness group 은 외부 의존성을 포함하지 않는다" | 공식 문서에서 단일 문장 verbatim 을 확보하지 못했다 (`SB-HEALTH-C8`). `SB-HEALTH-C12` 가 판단 기준만 부분 제공 | Spring Boot Kubernetes probes 섹션 재수집 또는 실제 기본 group 멤버십을 런타임에서 확인 | `needs-confirmation` |
|
||
| health group 구성 시점이 descriptor 수집(`SmartInitializingSingleton`)보다 이르다 | §구현 가이드 4 의 후보 (a)·(c) 기각이 이 선후에 달려 있는데, 수집한 actuator 문서에 group 구성 시점·프로그래매틱 확장점 진술이 없다 | Spring Boot actuator health-groups 문서 재수집(프로그래매틱 확장점 포함) 또는 실제 기동 순서를 로그로 확인 | `needs-confirmation` |
|
||
| registry yaml 은 런타임에 읽히지 않는다 | `src/build.gradle` 에 registries → resources packaging 이 0건임을 grep 으로 확인했으나, 다른 경로(예: jar 외부 마운트)로 읽힐 가능성을 배제하지 못했다 | ca-tmpl 에서 `grep -rn "docs/registries" src/ --include=*.java --include=*.gradle` 전수 확인 | `needs-confirmation` |
|
||
| 본 branch 가 소유할 관심사가 sibling branch 결정과 겹치지 않는다 | D-row 11개 작성 후에도 owner 경계가 문장으로만 존재 | `/sync` 실행 — owner 중복·재진술 검출 | `needs-confirmation` |
|
||
|
||
## Coverage / 관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)
|
||
|
||
> 2026-07-28 1회차 `coverage-auditor` 판정 = **Not-covered** (Blocking 1 / Should-fix 3). 아래 표는 **판정 시점** 상태이며, 각 finding 의 보강을 같은 날 적용했다(우측 "1회차 보강" 열). **재판정 전까지 Covered 로 간주하지 않는다.**
|
||
> governing: project note §11 · §25 · §36 + 분해 설계 §6 · §9
|
||
|
||
| 관심사 | 판정 시 상태 | owner | 심각도 | 1회차 보강 |
|
||
|--------|------|-------|--------|------|
|
||
| capability descriptor 최소 필드 9개 (§36.3) | covered-here | — | — | — (§36.3 과 1:1 일치 확인됨) |
|
||
| 활성화 SSOT 축 선택 (provider/dispatch-mode/enabled) | covered-here | — | — | — |
|
||
| 활성화 규범① 암묵 활성화 금지 | covered-here | — | — | D4 선택 조건에 mode 축 경계 추가 |
|
||
| 활성화 규범④ typed descriptor 판정 | covered-here | — | — | 기여·수집 메커니즘을 §구현 가이드 1 에 명시 |
|
||
| **활성화 규범② 미사용 capability 리소스 미생성** | missing | (없음) | 🟡 Should-fix | **D13 신설** |
|
||
| **활성화 규범③ legacy flag 불일치 = startup 오류** | missing | (없음) | 🟡 Should-fix | **D14 신설** + §구현 가이드 2 에 병행 메커니즘 라벨 |
|
||
| readiness 등급 R0~R3 + 승급 증거 | covered-here | — | — | D8 선택 조건을 등급 분기로 재작성 |
|
||
| fail-fast 토폴로지 검증 시점/거부 | covered-here | — | — | — |
|
||
| **capability card 필수 항목 13개 (§36.2)** | covered-here (3/13) | — | 🟡 Should-fix | §구현 가이드 5 에 **나머지 10개 상속 선언** + D12 연결 |
|
||
| **§11 실패 정책 매트릭스 12행의 owner** | **missing** | (없음) | 🔴 Blocking | **D12 신설** — 매트릭스 채택 + `failurePolicy` 맵 파생 (2회차 지적으로 단일 enum → 맵 정정) + 불일치는 build/test 대조 |
|
||
| startup 실패 코드 매핑 | delegated | [[raw/branch-notes/feature-migration-startup-contract]], [[raw/branch-notes/feature-integration-adapter-templates]] | OK | — |
|
||
| descriptor registry 스키마·저장 위치 | delegated | [[raw/branch-notes/feature-contract-registry-governance]] | OK | — |
|
||
| readiness endpoint shape/노출/인증 | delegated | [[raw/branch-notes/feature-runtime-health-lifecycle-contract]], [[raw/branch-notes/feature-management-actuator-security-contract]] | OK | A8 로 정적 include 현행 기록 + 비-capability indicator 취급을 명시 위임 |
|
||
| 개별 capability 의 provider 구현 | delegated | 각 Tier T/C branch | OK | — |
|
||
|
||
> **위임 판정 주의**: §11 매트릭스가 `missing` 인 이유는 위임 후보 6개 sibling(cache-strategy-registry / idempotency-ownership-protocol / fenced-lock-coordination / outbox-dispatch-mode / notification-durable-delivery / redis-runtime-role-isolation)이 **전부 scaffolding 상태**(D-row 0개)라 실제로 아무도 맡고 있지 않았기 때문이다. 그 branch 들이 `/branch-spec` 을 마치면 일부 행은 delegated 로 재분류될 수 있다.
|
||
|
||
## 마주친 문제
|
||
|
||
아직 없음.
|
||
|
||
## 묶음 (이 branch에서 파생된 자료)
|
||
|
||
<!-- GENERATED: branches:start -->
|
||
<!-- GENERATED: branches:end -->
|
||
|
||
## 관련 일일 노트
|
||
|
||
해당 없음.
|
||
|
||
## 완료 후 정리
|
||
|
||
- PR 링크:
|
||
- 리뷰 메모:
|
||
- 머지 결과 / 배포 환경:
|
||
- **wiki 추출 대상** (verified만, `wiki/projects/`로만 추출):
|
||
- **추출하지 않을 항목** (planned / documented-only / abandoned):
|