597 lines
103 KiB
Markdown
597 lines
103 KiB
Markdown
---
|
|
title: branch / feature-mongo-runtime-baseline-contract
|
|
source_type: branch-note
|
|
status: raw
|
|
branch: feature-mongo-runtime-baseline-contract
|
|
parent_branch:
|
|
related_projects: [ca-skeleton]
|
|
governing_docs: [raw/project-notes/ca-skeleton-operational-contract]
|
|
tags: [branch, ca-skeleton, mongodb, persistence, change-stream, index-manifest]
|
|
created: 2026-07-28
|
|
target_merge:
|
|
status_label: in-progress
|
|
id: BR-CA-SKELETON-OPERATIONAL-CONTRACT-065
|
|
kind: project-work-item
|
|
project: ca-skeleton-operational-contract
|
|
work_item: WI-CA-SKELETON-OPERATIONAL-CONTRACT-065
|
|
inherits: [DEC-CA-SKELETON-OPERATIONAL-CONTRACT-MONGO-BASELINE-001@1, DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-OPTIONAL-ADAPTER-001@1]
|
|
refines: []
|
|
overrides: []
|
|
depends_on: [WI-CA-SKELETON-OPERATIONAL-CONTRACT-060]
|
|
imports: []
|
|
delegates: []
|
|
accepts_delegations: []
|
|
contract_packet: 1
|
|
---
|
|
|
|
# branch: feature-mongo-runtime-baseline-contract
|
|
|
|
> Layer: `raw/branch-notes/` — 단일 브랜치의 **TODO·결정·진행 기록**. 머지/종료 후 verified 결과는 `/ingest`로 `wiki/projects/`에 추출. 원본은 raw에 영구 보관.
|
|
> `status_label`: `in-progress` | `review` | `merged` | `abandoned`
|
|
> **2026-07-28 `/branch-spec` 완료** — D1~D18(+D9-a) + §구현 가이드 10절 + §Audit 6건 작성. 근거는 공식 벤더 문서 13건(verbatim + self-grep 검증)과 ca-tmpl `internal-code-fact`. 착수 시점 readiness 는 **`R0` Contract**(project note §36.1).
|
|
|
|
<!-- 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 T). 본 branch 는 project §8.0 `WI-CA-SKELETON-OPERATIONAL-CONTRACT-065` 의 실행 단위다.
|
|
|
|
형제 branch (같은 부모의 다른 자식 — 인접 영역):
|
|
|
|
- [[raw/branch-notes/feature-persistence-failure-baseline]]
|
|
- [[raw/branch-notes/feature-read-consistency-query-contract]]
|
|
- [[raw/branch-notes/feature-outbox-dispatch-mode-contract]]
|
|
|
|
<!-- section-id: branch-contract-packet -->
|
|
## 브랜치 계약 패킷
|
|
|
|
> project Work Item 에서 내려온 실행 계약의 snapshot. 여기에는 **pinned pointer + 1줄 요약 + branch 적용점**만 쓰고 상세를 복제하지 않는다.
|
|
|
|
- **생성 시 프로젝트 개정**: `1`
|
|
- **패킷 스키마**: `contract_packet: 1`
|
|
- **완료 조건**: concern·index manifest·replica-set 트랜잭션·change stream checkpoint test 가 통과한다
|
|
|
|
<!-- section-id: inherited-project-decisions -->
|
|
### 상속한 프로젝트 결정
|
|
|
|
| Decision Ref | Project Summary | Branch Application | Source |
|
|
|---|---|---|---|
|
|
| `DEC-CA-SKELETON-OPERATIONAL-CONTRACT-MONGO-BASELINE-001@1` | MongoDB는 read preference·read concern·write concern을 명시하고 트랜잭션과 change stream은 replica-set 요건 검증을 통과할 때만 활성화한다 | concern 3축은 D5·D6·D7 이 명시 규칙으로, replica-set 요건 검증은 D8(transaction)·D11(change stream)과 §구현 가이드 4 의 공통 게이트로 구체화 | [[raw/project-notes/ca-skeleton-operational-contract]] |
|
|
| `DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-OPTIONAL-ADAPTER-001@1` | optional adapter stack은 축마다 구현체 하나를 고정하고 core stack과 분리된 matrix로 관리한다 | D1 이 Mongo 축을 boolean leaf 하나로 고정(provider 축 미승격). index 도구 축도 하나로 고정 — D13 이 자체 러너를 택하고 Mongock·Liquibase 를 명시적으로 기각 | [[raw/project-notes/ca-skeleton-operational-contract]] |
|
|
|
|
<!-- section-id: branch-local-decisions -->
|
|
### 브랜치 지역 결정
|
|
|
|
> 상세 근거와 선택 조건은 아래 §결정-근거 매핑의 동일 D-row 가 소유한다. 여기에는 요약과 관계만 둔다.
|
|
|
|
| Decision ID | Decision | Relation | Supporting Claims | Status |
|
|
|---|---|---|---|---|
|
|
| D1 | 활성화 축은 boolean leaf 유지 (`persistence-mongo.enabled`), provider 축으로 승격하지 않음 | `refines DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-OPTIONAL-ADAPTER-001@1` | `internal-code-fact` + sibling [[raw/branch-notes/feature-capability-provider-selection-contract]] D2 | `proposed` |
|
|
| D2 | 연결 URI 는 Spring 표준 키가 소유하되, 활성인데 URI 미설정이면 startup 거부 | `local` | `internal-code-fact` + `MONGO-CONNSTR-C7` | `proposed` |
|
|
| D3 | timeout 3종·pool 2종을 드라이버 기본값에 맡기지 않고 명시 | `local` | `MONGO-CONNSTR-C1`~`C5` | `proposed` |
|
|
| D4 | `tls` 를 연결 문자열 형식의 기본값에 맡기지 않고 명시 | `local` | `MONGO-CONNSTR-C7` | `proposed` |
|
|
| D5 | read concern 을 연산 단위로 명시, 기본 `majority` · `available` 금지 | `refines DEC-CA-SKELETON-OPERATIONAL-CONTRACT-MONGO-BASELINE-001@1` | `MONGO-READCONCERN-C1`~`C12` | `proposed` |
|
|
| D6 | write concern 을 명시하고 `wtimeout` 초과를 "write 취소"로 해석하지 않음 | `refines DEC-CA-SKELETON-OPERATIONAL-CONTRACT-MONGO-BASELINE-001@1` | `MONGO-WRITECONCERN-C2`~`C8` | `proposed` |
|
|
| D7 | read preference 기본 `primary`, secondary 는 stale 허용 선언 경로만 | `refines DEC-CA-SKELETON-OPERATIONAL-CONTRACT-MONGO-BASELINE-001@1` | `MONGO-READPREF-C1`,`C3`,`C4`,`C5` | `proposed` |
|
|
| D8 | transaction 은 토폴로지·FCV·storage engine 검증 통과 시에만 활성화 | `refines DEC-CA-SKELETON-OPERATIONAL-CONTRACT-MONGO-BASELINE-001@1` | `MONGO-TXN-C2`~`C4` · `MONGO-TXN-PROD-C1` | `proposed` |
|
|
| D9 | transaction 의 시간·크기 한계를 계약에 명시하고 무제한 사용을 전제하지 않음 | `local` | `MONGO-TXN-PROD-C2`~`C5` | `proposed` |
|
|
| D10 | 드라이버 retryable writes 1회 재시도 위에 애플리케이션 재시도를 중첩하지 않음 | `local` | `MONGO-RETRYWRITE-C3`,`C5`,`C6` | `proposed` |
|
|
| D11 | change stream 은 토폴로지 검증 통과 시에만 활성화하고 resume token 을 영속화 | `refines DEC-CA-SKELETON-OPERATIONAL-CONTRACT-MONGO-BASELINE-001@1` | `MONGO-CHANGESTREAM-C1`,`C2`,`C4`~`C8` | `proposed` |
|
|
| D12 | oplog window 를 감시해 resume token 무효화 **전에** 경보 | `local` | `MONGO-CHANGESTREAM-C7` | `proposed` |
|
|
| D13 | index 는 auto-index-creation 금지 + 선언적 manifest + drift 감지, 구현은 자체 `IndexOperations` 러너 | `local` | `SD-MONGO-INDEX-C1`~`C3`,`C5` · `MONGOCK-C2` · `LIQUIBASE-MONGO-PRO-C1` | `proposed` |
|
|
| D14 | index migration runner 는 단일 실행을 보장하는 lock 을 갖는다 | `local` | `MONGOCK-C3` + project note §25 Multi-Instance Guardrail | `proposed` |
|
|
| D15 | Mongo 실패 분류는 기존 `Category` enum 재사용, error code 는 **신규 제안** | `local` | `internal-code-fact` | `proposed` |
|
|
| D16 | mongo 모듈에 JPA/Hibernate/Flyway/PostgreSQL 의존 금지 ArchUnit rule 신설 | `local` | `internal-code-fact` (`CleanArchitectureTest.java:823-828`) | `proposed` |
|
|
| D17 | same-store Mongo outbox/inbox 는 **가능 조건**만 소유하고 행 모델·프로토콜은 위임 | `local` | `internal-code-fact` + sibling 위임 | `proposed` |
|
|
| D18 | Mongo 쿼리 filter·document 값이 로그로 새지 않게 driver logger 를 값 미노출 레벨로 고정 | `local` | `MONGO-JAVA-LOG-C1`,`C3`~`C6` | `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-065` 의 완료 조건을 구현한다: concern·index manifest·replica-set 트랜잭션·change stream checkpoint test 가 통과한다
|
|
|
|
- 이슈:
|
|
- PR:
|
|
|
|
<!-- section-id: branch-scope -->
|
|
## 범위
|
|
|
|
### 포함 범위
|
|
|
|
- URI·topology·TLS·credential·timeout·pool 설정
|
|
- read preference / read concern / write concern 명시 규칙
|
|
- 트랜잭션·change stream 의 replica-set 요건 검증
|
|
- index manifest·unique/TTL index·drift 감지·migration runner
|
|
- change stream resume token·checkpoint 저장소와 oplog window 감시
|
|
- same-store Mongo outbox/inbox 옵션
|
|
|
|
### 제외 범위
|
|
|
|
> 의도적으로 제외한 것. 면접 등에서 "이건 범위에 없었습니다"라고 답할 근거.
|
|
|
|
- 예시 업무 document — sample 이 아닌 production 모듈에 두지 않음
|
|
- PostgreSQL 계약 — [[raw/branch-notes/feature-persistence-failure-baseline]](SQLState 매핑·Hikari) · [[raw/branch-notes/feature-database-connection-pool-contract]] 소유
|
|
- `ReadConsistency` 정책과 replica 라우팅 판정 — [[raw/branch-notes/feature-read-consistency-query-contract]] 소유. 본 branch 는 Mongo 측 표현 수단만 (D7)
|
|
- outbox 행 모델·dispatch 모드·owner token 프로토콜 — [[raw/branch-notes/feature-outbox-dispatch-mode-contract]] · [[raw/branch-notes/feature-idempotency-ownership-protocol-contract]] 소유. 본 branch 는 same-store 가능 조건만 (D17)
|
|
- env key 이름·수치·bounds 등록과 활성화 property prefix 문자열 — [[raw/branch-notes/feature-env-driven-runtime-configuration]] · [[raw/branch-notes/feature-capability-provider-selection-contract]] 소유. 본 branch 는 명시 대상 키 집합만 (D3·D4)
|
|
- error code / metric 이름 확정 — [[raw/branch-notes/feature-contract-registry-governance]] 절차 소관. 본 branch 는 신규 제안만 (D12·D15)
|
|
- `TransactionPort` 의 Mongo 구현 여부 — application-core 포트 계약 영향이 있어 조율 선행 (§구현 가이드 7)
|
|
- project decision registry 변경 — owner 는 project-note
|
|
|
|
## 근거 (필수, 최소 1개+)
|
|
|
|
> 2026-07-28 수집 완료 — 13건 전부 `wiki-source-summarizer` 가 verbatim 인용 + self-grep 검증을 마친 `official-doc` 이다. 각 자료의 `Usage Boundaries` 가 **이 자료가 증명하지 않는 것**을 명시하며, 그 gap 은 §검증해야 할 주장으로 승계했다.
|
|
|
|
| Source | 정당화하는 결정 |
|
|
|---|---|
|
|
| [[raw/official-docs/mongodb-connection-string-options]] | timeout(`serverSelectionTimeoutMS`/`connectTimeoutMS`/`socketTimeoutMS`)·pool(`maxPoolSize`/`minPoolSize`/`maxIdleTimeMS`)·`tls`·`retryWrites`/`retryReads` 를 드라이버 기본값에 맡기지 않고 명시하고 env registry 로 노출한다 |
|
|
| [[raw/official-docs/read-concern-mongodb-official]] | MongoDB 조회는 read concern 을 명시하고 기본값(`local` implicit default)에 의존하지 않는다 — `local`/`available`/`majority`/`linearizable`/`snapshot` 5개 level 의 보증/비보증 차이, `majority` 의 replica-set·multi-document transaction 관계(write concern `"majority"` 커밋 조건부 보장), level별 topology/operation 제약(`linearizable` primary 전용, `snapshot` 트랜잭션 밖 find/aggregate/distinct-unsharded-only) 근거(`MONGO-READCONCERN-C1`~`C12`) |
|
|
| [[raw/official-docs/write-concern-mongodb-official]] | MongoDB write 는 write concern 을 명시하고 기본값(`w: "majority"` 여부)에 의존하지 않는다 + journaling(`j`) / `wtimeout` 을 명시한다 — `w`/`j`/`wtimeout` 의미, implicit default, `wtimeout` 초과 시 write 가 rollback 되지 않는다는 실패 모드 근거 |
|
|
| [[raw/official-docs/spring-data-mongodb-template-config-official]] | D5·D6·D7 이 "concern 을 명시한다"고 결정한 것을 **Spring Data `MongoTemplate` API 어디에 붙이는지**(적용 seam) 확인 — `WriteConcernResolver` 가 연산 단위(remove/update/insert/save) write concern 결정 수단으로 실재함(D6 근거), `ReadPreference` 는 template 의 설정 가능 property 로 존재함(D7 근거). **read concern 설정 수단은 이 문서에 부재**(self-grep 0 매치) — D5 의 Spring Data 측 seam 은 이 자료로 닫히지 않으며 `UNSUPPORTED_DECISION` 후보로 승계 |
|
|
| [[raw/official-docs/spring-data-mongodb-index-management-official]] | automatic index creation 기본값(OFF, 버전 3.0+)과 explicit/programmatic index 생성 권고(`IndexResolver`+`IndexOperations`) 확인 — "auto-index-creation 에 맡기지 않는다" 결정의 부분 근거. "manifest"/"migration runner"/"drift 감지" 자체는 이 자료가 증명하지 않음 (해당 raw 문서 Usage Boundaries 참조) |
|
|
| [[raw/official-docs/read-preference-mongodb-official]] | 조회 replica 라우팅을 read preference mode로 명시하고 secondary 읽기는 stale read 허용 경로에만 쓴다 — mode 5종 정의 + secondary 읽기 stale 경고 + maxStalenessSeconds 메커니즘 + 트랜잭션 내 read preference 제약(`primary` 고정) 근거. maxStalenessSeconds 의 수치 최소값 제약은 이 자료 범위 밖(별도 페이지 확인 필요, 해당 raw 문서 Usage Boundaries 참조) |
|
|
| [[raw/official-docs/change-streams-mongodb-official]] | change stream 은 replica set/sharded cluster 요건(WiredTiger, read concern majority 지원 여부 무관)을 검증할 때만 활성화하고, resume token 을 checkpoint 저장소에 영속화하며(resumeAfter/startAfter/startAtOperationTime 3가지 재개 경로), oplog window 부족 시 resume 실패·invalidate event 발생 시 stream 종료를 감지해 `startAfter` 로 재개하는 계약의 근거 |
|
|
| [[raw/official-docs/retryable-writes-mongodb-official]] | Mongo write 재시도 의미를 명시한다 — 드라이버의 retryable writes 가 무엇을 보장하고 무엇을 보장하지 않는지를 계약에 적고, 애플리케이션 재시도와 겹치지 않게 한다. retryable writes 기본 활성 여부(4.2+ 호환 드라이버) + 재시도 정확히 1회 + 재시도 가능(단일 문서 연산, acknowledged write concern)/불가능(`w:0`, multi-document update/delete, 트랜잭션 내부 개별 write) 연산 목록 + replica-set/sharded-cluster 배포 요건(standalone 불가) 근거 |
|
|
| [[raw/official-docs/transactions-mongodb-official]] | multi-document transaction 은 replica-set/sharded 요건 검증을 통과할 때만 활성화하고(FCV Replica Set≥4.0/Sharded Cluster≥4.2, primary WiredTiger 요건, `writeConcernMajorityJournalDefault:false` shard 배제) + transaction 은 시간·크기 한계를 가지므로 무제한 사용을 전제하지 않는다(runtime limit 이 공식 usage consideration 으로 인정됨 — 단 구체 수치는 별도 Production Considerations 페이지, 이 자료 범위 밖) 근거. 부수적으로 transaction 내 read concern(기본 `"local"`)/write concern(기본 `w:"majority"`/`w:1"`) 결정 체인 근거 |
|
|
| [[raw/official-docs/transactions-production-considerations-mongodb-official]] | (부분) transaction 은 시간·크기 한계를 가지므로 무제한 사용을 전제하지 않고, 한계 초과 시 동작을 계약에 명시한다 — runtime limit 기본값("less than one minute", `transactionLifetimeLimitSeconds` 초과 시 periodic cleanup 에 의한 abort)과 oplog entry 크기 한계(transaction 전체 아닌 entry 단위 16MB) 근거. **`TransientTransactionError`/`UnknownTransactionCommitResult` 라벨 기반 재시도 계약은 이 자료로 정당화되지 않음** — 두 라벨 모두 이 URL 에 부재(self-grep 0 매치), 별도 raw 자료 필요(`UNSUPPORTED_DECISION` 후보) |
|
|
| [[raw/official-docs/liquibase-mongodb-pro-drift-report-official]] | MongoDB index drift 감지를 Liquibase 로 얻지 않고 자체 러너로 구현한다 — Liquibase 의 MongoDB drift report 접근이 **Liquibase MongoDB Pro extension(유료)** 기능 목록에 명시된다는 근거(`LIQUIBASE-MONGO-PRO-C1`, `C2`). **주의**: 무료(OSS) extension 에 drift 가 전혀 없다는 부정 명제는 이 자료가 직접 증명하지 않음(해당 raw 문서 Usage Boundaries 참조) — "무료 티어 미충족"을 최종 결정 근거로 쓰려면 이 gap 을 D-row 에 `needs-confirmation` 으로 명시할 것 |
|
|
| [[raw/official-docs/mongock-migration-lock-maintenance-official]] | index manifest 적용 + drift 감지를 Mongock 이 아니라 Spring Data `IndexOperations` 기반 자체 러너로 구현한다 — Mongock 미채택 근거(신규 개발이 후속 프로젝트 Flamingock 으로 이전, critical bug fix/security update 만 지속)와 채택 시 이점(멀티 인스턴스 동시 실행 방지 DB 영속 pessimistic lock 내장)을 모두 인지한 trade-off 근거(`MONGOCK-C1`~`C4`) |
|
|
| [[raw/official-docs/java-driver-logging-mongodb-official]] | MongoDB 쿼리 filter·document 값이 로그로 새지 않게 억제한다(`D18` 후보, §엣지·실패·의존 표에 이미 전방 참조된 결정) — 어느 logger(`org.mongodb.driver.protocol`/하위 `org.mongodb.driver.protocol.command`)가 command 내용을 어느 레벨(DEBUG)로 남기는지, SLF4J 바인딩 설정으로 logger 이름 단위 레벨을 조정하는 방법, `maxDocumentLength()`(기본 1000자)로 로그 메시지 길이를 제한할 수 있으나 이는 truncation 이지 필드 마스킹이 아니라는 것의 근거(`MONGO-JAVA-LOG-C1`~`C6`) |
|
|
|
|
**프로젝트 내부 설계 참조 (등급 `internal-design-doc` — 공식 문서 아님, best practice 로 격상 금지):**
|
|
|
|
- llm-wiki `docs/superpowers/specs/2026-07-28-ca-skeleton-production-capability-feature-decomposition-design.md` §4.2 #6
|
|
- ⚠️ ca-tmpl `docs/superpowers/specs/2026-07-26-production-capability-platform-design.md` (§12.3) — **저장소에 존재하지 않음**(§Audit A3). 본 branch 의 D-row 는 이 문서를 인용하지 않으며, 모든 근거는 위 공식 벤더 문서 13건 또는 `internal-code-fact` 다.
|
|
|
|
## TODO
|
|
|
|
각 항목 옆에 증거 등급 표기: `actually-implemented` | `locally-verified` | `prod-verified` | `documented-only` | `planned` | `needs-confirmation`
|
|
|
|
- [x] `/branch-spec` 로 D-row(D1~D18)·§구현 가이드 10절·§Audit 6건 작성 — 등급: `actually-implemented` (문서 작업)
|
|
|
|
**선행 — 자료 수집 5건** (되돌릴 코드를 먼저 쓰지 않기 위해 착수 앞에 둔다):
|
|
|
|
- [ ] Mongock v5 기능/lock 페이지 + Liquibase 무료 OSS extension 기능 목록 — D13 기각 논리 확정용. **D13·D14 코드보다 먼저** — 등급: `planned`
|
|
- [ ] oplog window 측정 수단 공식 자료 — **D12 코드보다 먼저** — 등급: `planned`
|
|
- [ ] Spring Data / 드라이버의 **read concern 적용 API** — §구현 가이드 2-1 의 미확정 seam. **D5 코드보다 먼저** — 등급: `planned`
|
|
- [ ] MongoDB Java driver transactions 에러 처리(재시도 라벨) — D9-a 확정용. **대응 구현 TODO 없음 → 순서 제약 없음** — 등급: `planned`
|
|
- [ ] MongoDB TTL index 만료 정밀도 · unique index 기존 데이터 실패 — **§구현 가이드 6 의 lock 후보 (a)(unique+TTL 컬렉션)를 채택한다면 D14 코드보다 먼저** (TTL 만료 지연이 lock 안전성의 전제) — 등급: `planned`
|
|
|
|
**구현**:
|
|
|
|
- [ ] 연결 계약 — URI 필수화 + timeout·pool·tls 명시 (D2·D3·D4) — 등급: `planned`
|
|
- [ ] concern 3종 명시 규칙 (D5·D6·D7 · §구현 가이드 2-1) — 등급: `planned`
|
|
- [ ] 토폴로지 검증 게이트 (D8·D11 · §구현 가이드 4) — 등급: `planned`
|
|
- [ ] 쿼리·document 값 로그 유출 억제 (D18 · §구현 가이드 8) — 등급: `planned`
|
|
- [ ] ArchUnit rule 신설 (D16) — 등급: `planned`
|
|
- [ ] Mongo 실패 매핑 registry 신규 제안 (D15) — 등급: `planned`
|
|
- [ ] change stream checkpoint + oplog window 감시 (D11·D12) — 등급: `planned`
|
|
- [ ] index manifest + migration runner + drift (D13·D14) — 등급: `planned`
|
|
- [ ] 로컬 replica-set 컨테이너 + 통합 test — 완료 조건 검증 — 등급: `planned`
|
|
|
|
## 진행 중 메모
|
|
|
|
- 2026-07-28: 이 branch 착수 시점의 ca-tmpl 상태는 **`R0` Contract** 다 — 모듈은 있으나 wiring 뿐이고, 연결 URI 조차 어디에도 설정돼 있지 않다(§Audit A1). "opt-in 설정만 존재" 라는 project note §34 표기는 정확하다.
|
|
- 근거 수집은 MongoDB 공식 9건 + Spring 2건 + 도구 2건(Mongock·Liquibase) = 13건. 전부 verbatim 인용 + self-grep 검증을 거쳤다. (2회차 게이트 반영으로 Spring Data template config · MongoDB Java driver logging 2건 추가.)
|
|
- 수집 과정에서 **부재를 부재로 기록**한 항목이 여러 개 있다(`transactionLifetimeLimitSeconds` 정수값, `TransientTransactionError` 라벨, `maxStalenessSeconds` 하한). 이 값들을 추측해 채우지 않았고 §검증해야 할 주장으로 승계했다.
|
|
|
|
## 결정 사항
|
|
|
|
- 2026-07-28: **read/write concern 과 read preference 를 "명시" 로 고정** / 이유: 세 축 모두 기본값이 존재하지만(`local` read concern·`{w:"majority"}` write concern·`primary` read preference) 배포 설정과 연결 문자열 형식에 따라 조용히 달라질 수 있다 / 검토한 대안: 기본값 신뢰(기각 — `MONGO-CONNSTR-C7` 처럼 형식만 바꿔도 뒤집히는 축이 실재) / 근거: [[raw/official-docs/read-concern-mongodb-official]] · [[raw/official-docs/write-concern-mongodb-official]] · [[raw/official-docs/read-preference-mongodb-official]]
|
|
- 2026-07-28: **index 관리는 자체 `IndexOperations` 러너** / 이유: drift 감지가 이 branch 의 1급 요구사항인데 Mongock 은 EOL 공지 + drift 확인 불가, Liquibase 는 drift 가 Pro 전용 / 검토한 대안: Mongock · Liquibase-mongodb · 앱 밖 배포 파이프라인 Job / 근거: [[raw/official-docs/spring-data-mongodb-index-management-official]] · [[raw/official-docs/mongock-migration-lock-maintenance-official]] · [[raw/official-docs/liquibase-mongodb-pro-drift-report-official]] — 단 기각 논리 2건은 `needs-confirmation`(§검증해야 할 주장 — *Mongock drift 부재* · *Liquibase 무료 티어 drift 부재*)
|
|
- 2026-07-28: **드라이버 재시도 위에 애플리케이션 재시도를 겹치지 않음** / 이유: `retryWrites` 가 공식 드라이버에서 이미 기본 `true` 이고 재시도는 정확히 1회라, 이를 모르고 재시도를 추가하면 시도 횟수가 2배가 된다 / 근거: [[raw/official-docs/retryable-writes-mongodb-official]] · [[raw/official-docs/mongodb-connection-string-options]]
|
|
|
|
<!-- section-id: decision-evidence -->
|
|
## Decision Evidence Map / 결정-근거 매핑
|
|
|
|
> `Supporting Claims` 의 `raw/...#Cn` 은 verbatim 인용 + self-grep 검증을 마친 claim. `internal-code-fact` 는 ca-tmpl 코드를 직접 읽어 확인한 사실(경로·행 명시). 외부 공식 근거가 없는 결정은 `UNSUPPORTED_DECISION` 으로 라벨한다 — 추측으로 채우지 않는다.
|
|
>
|
|
> **근거의 두 축을 섞지 않는다.** MongoDB 공식 문서가 증명하는 것은 *메커니즘·기본값*(드라이버·서버가 어떻게 동작하는가)이고, *정책*(그래서 우리는 무엇을 금지·강제하는가)은 대부분 내부 결정이다. 각 행의 Evidence Strength 가 이 경계를 표시한다.
|
|
|
|
| Decision ID | Decision | 선택 조건 (언제 이 결정 / 언제 대안) | Supporting Claims | Evidence Strength | Open Risk |
|
|
|---|---|---|---|---|---|
|
|
| D1 | Mongo capability 의 활성화 축은 **boolean leaf** 로 유지한다 — `ca-skeleton.persistence-mongo.enabled`. provider 축(`provider: disabled\|<id>`)으로 승격하지 않는다 | 구현체 대안이 없고 켜고 끄는 것 외 선택지가 없음 → boolean (현행 유지). 관리형 Mongo/DocumentDB 등 **provider 대안이 2개 이상** 생기면 → provider 축으로 이관하고 그때 breaking rename 을 감수. 두 축 동시 보유는 금지 | `internal-code-fact`: `adapter/outbound/persistence-mongo/.../MongoPersistenceConfig.java:33-36` (`@ConditionalOnProperty(prefix="ca-skeleton.persistence-mongo", name="enabled", havingValue="true")`, `matchIfMissing` 미지정), `MongoPersistenceSettings.java:23` (`enabled=false`), `app-bootstrap/.../application.yml:336-338`. sibling: [[raw/branch-notes/feature-capability-provider-selection-contract]] D2(축 단일화)·D4(`matchIfMissing=true` 금지 — 본 모듈은 미지정이므로 이미 준수) | **정책 `internal-policy` (sibling 결정 상속) / 현행 `internal-code-fact`** | prefix 통일(sibling D3)이 `app.*` 로 확정되면 `ca-skeleton.persistence-mongo.*` 는 rename 대상이 된다. 본 branch 는 축만 고정하고 **prefix 문자열은 sibling 이 소유** — 두 결정이 다른 PR 로 나뉘면 이행 창에 키가 둘로 갈린다 |
|
|
| D2 | 연결 URI 는 Spring 표준 `spring.data.mongodb.uri` 가 계속 소유한다(모듈 Settings 로 옮기지 않음). 단 **capability 가 활성인데 URI 가 미설정이면 startup 을 거부**한다 | URI 는 운영자가 이미 아는 표준 키 → Spring 소유 유지. 모듈이 URI 를 자체 property 로 재정의 → 키가 둘이 되어 `verifyEnvKeys` 3-way drift 검사(sibling env-driven D7)를 통과할 수 없으므로 선택 안 함. **미설정 시 조용한 기본값 사용은 어느 조건에서도 선택하지 않는다** | `internal-code-fact`: `MongoPersistenceSettings.java:10-13` 이 URI 미모델링을 **의도적 결정**으로 주석에 명시. 그러나 `spring.data.mongodb.*` 는 `application.yml`·`src/.env`(126행) 어디에도 **존재하지 않음**(grep 0건). → 플래그를 켰을 때 **드라이버/Spring 기본 URI 로 연결을 시도하는지, 아니면 Spring 이 먼저 실패시키는지는 `needs-confirmation`** — 수집한 13건 중 Spring Boot `MongoProperties` 기본값을 다루는 자료가 없다(§검증해야 할 주장 — *플래그를 켰을 때 기본 URI 로 붙는지*). **어느 쪽이든 "설정하지 않은 채 활성화" 가 유효한 상태가 아니라는 점은 같으므로 D2 의 거부 정책은 성립한다.** `MONGO-CONNSTR-C7`: `tls` 기본값이 연결 문자열 **형식마다 다르므로**(SRV=true / Standard=false) URI 문자열 자체가 보안 결정을 담는다. 실패 코드는 기존 `STARTUP_VALIDATION_FAILED`(`error-codes.yaml:841`, owner `feature-migration-startup-contract`) 재사용 | **`internal-code-fact` + 메커니즘 `official-vendor-doc`(C7) / 거부 정책 `internal-policy`** | 이 gap 은 **현재 실재한다** — §Audit A1 참조. env-keys.yaml 에 mongo row 가 0개라 `verifyEnvKeys` 도 이 부재를 잡지 못한다. registry 등록이 선행돼야 startup 검증이 성립한다 |
|
|
| D3 | timeout 3종(`serverSelectionTimeoutMS`·`connectTimeoutMS`·`socketTimeoutMS`)과 pool 2종(`maxPoolSize`·`minPoolSize`)을 **드라이버 기본값에 맡기지 않고 명시**한다 | 운영 환경(dev/staging/prod) → 5개 전부 명시 필수. 로컬 단일 개발자 실행 → 기본값 허용하되 그 사실을 프로파일에 명시. **`socketTimeoutMS` 만은 어느 환경에서도 명시**한다 — 기본값이 무제한이라 미명시 = 무한 대기 | `MONGO-CONNSTR-C3`(`raw/official-docs/mongodb-connection-string-options.md#MONGO-CONNSTR-C3` — "The time in milliseconds to attempt a send or receive on a socket before the attempt times out. **The default is no timeout**, though different drivers might vary."), `C1`(serverSelection 기본 30,000ms), `C2`(connect 기본 10,000ms, 드라이버별 상이), `C4`(maxPoolSize 기본 100), `C5`(minPoolSize 기본 0) | **기본값 `official-vendor-doc` / 명시 정책 `internal-policy`** | 값 자체는 본 branch 소유가 아니다 — sibling [[raw/branch-notes/feature-database-connection-pool-contract]] 가 Hikari 값을 `feature-env-driven-runtime-configuration` 에 위임한 선례와 동일하게, **본 branch 는 "명시 대상 키 집합"만 고정하고 수치는 env-driven 이 소유**한다. `maxIdleTimeMS` 는 원문에 숫자 기본값이 없어(`C6`) 명시 대상에서 제외 — 필요해지면 별도 결정 |
|
|
| D4 | `tls` 를 연결 문자열 형식의 암묵적 기본값에 맡기지 않고 **항상 명시**한다 | prod/staging → `tls=true` 명시. 로컬 docker → `tls=false` 를 **명시**(생략 금지). SRV 형식이라 "어차피 true" 라는 이유로 생략하는 것은 선택하지 않는다 | `MONGO-CONNSTR-C7`(`#MONGO-CONNSTR-C7` — `tls` 기본값이 SRV 형식은 `true`, Standard 형식은 `false`). 즉 **연결 문자열 형식을 바꾸면 TLS 여부가 조용히 뒤집힌다** | **메커니즘 `official-vendor-doc` / 명시 정책 `internal-policy`** | URI 는 secret 분류 대상(자격증명 포함)이라 `secrets-classification.yaml` row 가 필요한데 현재 0건 — owner 는 [[raw/branch-notes/feature-secrets-config-source-contract]]. 본 branch 는 **신규 제안**만 하고 등록은 그 branch 절차를 따른다 |
|
|
| D5 | 조회는 read concern 을 **연산 단위로 명시**하고 기본은 `majority` 로 한다. `available` 은 금지, `linearizable`·`snapshot` 은 제약 충족 시에만 | 정확성이 필요한 업무 조회 → `majority`. 오래된 데이터를 감수해도 되는 대량 분석/근사 조회 → `local` 을 **명시적 opt-in** 으로만. 단일 document 를 고유 식별하는 강한 읽기 → `linearizable`(primary 전용). 트랜잭션 또는 `find`/`aggregate`/`distinct`(unsharded) 의 시점 고정 조회 → `snapshot`. **sharded collection 조회 → `available` 금지** | `MONGO-READCONCERN-C2`(`raw/official-docs/read-concern-mongodb-official.md#MONGO-READCONCERN-C2` — `local` 이 "Default for reads against the primary and secondaries"), `C1`(local 은 과반수 기록 보장 없음 · "Data may be rolled back"), `C4`(majority 는 "Returned documents are durable, even if a failure occurs"), `C5`(majority 는 WiredTiger 요건), `C3`(available 은 sharded 조회에서 orphaned document 반환), `C7`/`C8`(linearizable 은 primary + 단일 document 식별 필터일 때만), `C9`~`C11`(snapshot 의 범위·연산 제한), `C6`(트랜잭션 안의 majority 는 write concern `"majority"` 커밋일 때만 보장) | **`official-vendor-doc`** — level 별 보증/제약은 전부 벤더 진술. "기본을 majority 로" 라는 **선택 자체는 `internal-policy`** | `C6`/`C10` 때문에 read concern 단독으로는 트랜잭션 안에서 보증이 성립하지 않는다 — D6 의 write concern 결정과 **쌍으로만** 유효하다. 두 결정을 따로 적용하면 트랜잭션 경로에서 보증이 조용히 사라진다 |
|
|
| D6 | write 는 write concern 을 명시하고, **`wtimeout` 초과를 "write 취소"로 해석하지 않는다**. 트랜잭션 내부 개별 write 에는 write concern 을 설정하지 않는다 | 업무 write → `w:"majority"` 명시. 유실 감수 가능한 보조 write → 낮은 `w` 를 **명시적 opt-in**. `j` 는 배포의 journaling 정책에 따라 선언. `wtimeout` 은 명시하되 **초과 시 재시도는 멱등성이 보장된 연산에만** 허용 | `MONGO-WRITECONCERN-C7`(`raw/official-docs/write-concern-mongodb-official.md#MONGO-WRITECONCERN-C7` — "When these write operations return, MongoDB does not undo successful data modifications performed before the write concern exceeded the `wtimeout` time limit"), `C6`(한계 초과 시 "even if the required write concern will eventually succeed" write concern error 반환), `C5`(wtimeout 은 primary 성공 **후** 전파 시간 제한), `C3`(majority 의미), `C4`(j 의미), `C8`(implicit default 가 `{w:"majority"}`), `MONGO-TXN-C7`(트랜잭션 내부 개별 write 에 write concern 설정 시 에러) | **`official-vendor-doc`** — 특히 C7 은 실패 해석의 핵심 벤더 진술 | C8 이 "implicit default 가 이미 majority" 라고 말하므로 명시가 무의미해 보일 수 있으나, 배포가 CWWC(cluster-wide write concern)를 바꾸면 기본값이 달라진다 — 이 branch 가 명시를 요구하는 이유이나 **CWWC override 메커니즘 자체는 이번 회차에서 조사하지 않았다**(비존재 단정 아님) |
|
|
| D7 | 조회의 replica 라우팅은 read preference 로 명시하고 **기본은 `primary`**, secondary 읽기는 stale 을 허용한다고 **선언한 경로에만** 허용한다 | 기본/미지정 조회 → `primary`. stale 감수를 선언한 조회 → `secondaryPreferred` + `maxStalenessSeconds` 명시. read 를 포함한 트랜잭션 → **`primary` 강제**(선택지 없음). `nearest` 는 지연 최적화가 정확성보다 중요한 경로에만 | `MONGO-READPREF-C1`(`raw/official-docs/read-preference-mongodb-official.md#MONGO-READPREF-C1` — primary 가 default), `C3`("All read preference modes except `primary` may return stale data ... Ensure that your application can tolerate stale data"), `C4`(maxStalenessSeconds 로 지연 상한 지정), `C5`("Transactions that contain read operations must use read preference `primary`"), `C2`/`C6`/`C7`/`C8`(mode 별 fallback 동작) | **`official-vendor-doc`(메커니즘·제약) / "언제 stale 을 허용하는가" 는 `internal-policy`** | **`ReadConsistency` 정책 → 라우팅 판정 매핑은 본 branch 소유가 아니다** — [[raw/branch-notes/feature-read-consistency-query-contract]] 가 `DEC-…-READ-CONSISTENCY-001@1` 의 owner 다. 본 branch 는 *Mongo 측 표현 수단*(mode·maxStalenessSeconds)만 고정한다. 또한 `maxStalenessSeconds` 의 **수치 하한은 미확보**(해당 raw 의 Usage Boundaries — 별도 페이지) |
|
|
| D8 | multi-document transaction 은 **토폴로지·FCV·storage engine 검증을 통과할 때만** 활성화한다. standalone 배포에서는 활성화 자체를 거부한다 | 여러 document/collection 에 걸친 원자성이 실제로 필요 → transaction 활성화 + 기동 시 요건 검증. **단일 document 로 모델링 가능** → transaction 을 쓰지 않는다(이미 원자적). 요건 미충족 배포 → `STARTUP_VALIDATION_FAILED` 로 기동 거부(조용한 비활성 아님) | `MONGO-TXN-PROD-C1`(`raw/official-docs/transactions-production-considerations-mongodb-official.md#MONGO-TXN-PROD-C1` — "MongoDB standalone deployments do not support transactions. To use transactions, your deployment must be a multiple node replica set."), `MONGO-TXN-C2`(FCV — Replica Set ≥`4.0` / Sharded Cluster ≥`4.2`), `C3`(primary WiredTiger, secondary WiredTiger 또는 in-memory), `C4`(`writeConcernMajorityJournalDefault:false` shard 가 있는 sharded cluster 에서 실행 불가), `C1`("In MongoDB, an operation on a single document is atomic" — transaction 이 많은 경우 불필요) | **`official-vendor-doc`(요건) / 기동 거부 정책 `internal-policy`** | 검증 시점은 sibling [[raw/branch-notes/feature-capability-provider-selection-contract]] D6(refresh 완료 **전**, `SmartInitializingSingleton`)을 상속해야 한다 — 본 branch 가 별도 시점을 정의하면 startup 검증이 두 곳으로 갈린다. **FCV·storage engine 을 런타임에 어떤 명령으로 질의할지는 미정** — §구현 가이드 4 의 `UNSUPPORTED_IMPL_DECISION` |
|
|
| D9 | transaction 의 **시간·크기 한계를 계약에 명시**하고 무제한 사용을 전제하지 않는다 | 기본 → 1분 미만 runtime 을 전제로 트랜잭션 범위를 설계. 1분을 넘길 수밖에 없는 배치성 작업 → transaction 이 아니라 **분할 + 멱등 재시도**로 설계(파라미터 상향은 마지막 수단). 대량 write → oplog entry 당 16MB 한계를 설계 제약으로 취급 | `MONGO-TXN-PROD-C2`(`#MONGO-TXN-PROD-C2` — "By default, a transaction must have a runtime of less than one minute. You can modify this limit using transactionLifetimeLimitSeconds" + "Transactions that exceeds this limit are considered expired and will be aborted by a periodic cleanup process."), `C3`(전체 16MB 한계는 제거됐으나 **개별 oplog entry 는 여전히 16MB BSON 한계**), `C4`(`TransactionTooLargeForCache` 에러와 함께 abort), `C5`("When you encounter an error during individual operation in the transaction, abort and retry the transaction.") | **`official-vendor-doc`** — 단 **정확한 기본 정수 초는 원문에 없음**("less than one minute" 서술만). "60초" 라고 적지 않는다 | `TransientTransactionError` / `UnknownTransactionCommitResult` 라벨 기반 **재시도 계약은 근거 미확보** — 두 라벨 모두 해당 URL 에 self-grep 0 매치. 아래 D9-a 로 분리해 `UNSUPPORTED_DECISION` 라벨 유지 |
|
|
| D9-a | 트랜잭션 재시도 책임(어떤 에러 라벨에서 트랜잭션 전체를 재시도할 것인가)을 애플리케이션 계약으로 못박는다 | (선택 조건 미확정 — 근거 확보 후 작성) | **조사 범위 내 미확보** — `MONGO-TXN-PROD-C5` 는 "에러 시 abort 후 retry" 라는 **사후 대응 권고**만 제공하고 라벨 이름을 주지 않는다. `TransientTransactionError`·`UnknownTransactionCommitResult` 는 해당 URL 에 부재(grep 0 매치, 비존재 단정 아님 — 드라이버별 에러 처리 페이지 미조사) | **`UNSUPPORTED_DECISION`** | 라벨 없이 재시도 조건을 코드에 박으면 "어떤 에러에서 재시도해야 하는가" 를 구현자가 임의로 정하게 된다. **착수 전 별도 dispatch 1건**(MongoDB Java driver transactions 에러 처리 페이지)으로 닫는 것이 권고 |
|
|
| D10 | 드라이버의 retryable writes(기본 1회 재시도) 위에 **애플리케이션 재시도를 중첩하지 않는다**. 재시도가 필요한 경로는 멱등성을 스스로 보장한다 | 단일 document write → 드라이버 재시도에 위임(추가 재시도 없음). `updateMany`/`deleteMany` 등 multi-document write → 드라이버가 재시도하지 않으므로 **멱등 설계 + 명시적 재시도**가 필요. `w:0` → 재시도 대상이 아니므로 업무 write 에 사용 금지. 트랜잭션 내부 write → 개별 재시도가 없으므로 **트랜잭션 전체 재시도**(D9-a 소관) | `MONGO-RETRYWRITE-C3`(`raw/official-docs/retryable-writes-mongodb-official.md#MONGO-RETRYWRITE-C3` — "**By default, MongoDB retries writes once**. One retry attempts to address transient network errors and replica set elections, but not persistent network errors."), `C5`(`w:0` 및 `updateMany`/`deleteMany` 는 재시도 불가), `C6`(트랜잭션 내부 write 는 개별 재시도 없음, commit/abort 는 `retryWrites` 와 무관하게 1회 재시도), `C4`(재시도 대상 = acknowledged write concern + 단일 document 연산), `C1`(standalone 미지원), `C2`(4.2+ 드라이버 기본 활성), `C7`(6.1+ `NoWritesPerformed` 라벨) | **`official-vendor-doc`(재시도 의미) / 중첩 금지 정책 `internal-policy`** | 기본이 `true`(`MONGO-CONNSTR-C8`)라 **아무도 결정하지 않아도 재시도가 이미 일어나고 있다** — 이 사실을 모르는 상태에서 애플리케이션 재시도를 추가하면 실제 시도 횟수가 2배가 된다. D3 의 명시 대상 키에 `retryWrites` 를 포함할지는 §구현 가이드 2 에서 다룬다 |
|
|
| D11 | change stream 은 **토폴로지 검증을 통과할 때만** 활성화하고, resume token 을 checkpoint 저장소에 **영속화**한다. invalidate 이후 재개는 `startAfter` 로 한다 | replica set / sharded cluster + WiredTiger → 활성화 허용. standalone → 활성화 거부. 정상 재개 → `resumeAfter`(저장된 token). **invalidate 발생 후 재개 → `startAfter`**(`resumeAfter` 는 invalidate 이후 재개 불가). token 이 없는 최초 기동 → `startAtOperationTime` | `MONGO-CHANGESTREAM-C1`(`raw/official-docs/change-streams-mongodb-official.md#MONGO-CHANGESTREAM-C1` — "Change streams are available for replica sets and sharded clusters"), `C2`(WiredTiger 요건), `C4`(resumeAfter), `C5`("Unlike resumeAfter , startAfter can resume notifications after an invalidate event"), `C6`(startAtOperationTime 은 oplog 시간 범위 안이어야 함), `C8`(cursor 종료 4조건 — 명시적 close / invalidate / 연결 종료·timeout / shard 제거), `C3`(read concern majority 지원 여부와 **무관**하게 사용 가능) | **`official-vendor-doc`** | checkpoint **저장소 선택**(same-store Mongo 컬렉션 vs 별도 store)은 근거 없음 → §구현 가이드 5 의 `UNSUPPORTED_IMPL_DECISION`. 또한 checkpoint 저장이 이벤트 처리와 원자적이지 않으면 **at-least-once** 가 되며, 이 보증 등급은 project note §11 의 durable 경로 정책과 정합돼야 한다 |
|
|
| D12 | oplog window 를 감시해 **resume token 이 무효화되기 전에** 경보한다 | change stream 을 활성화한 배포 → 감시 필수. 비활성 배포 → 해당 없음. 경보 임계는 "남은 oplog 시간 < 소비자 최대 다운타임 허용치" 로 정의 | `MONGO-CHANGESTREAM-C7`(`#MONGO-CHANGESTREAM-C7` — "The oplog must have enough history to locate the operation associated with the token or the timestamp, if the timestamp is in the past.") — 즉 oplog 가 롤오버되면 **저장해 둔 token 으로 재개할 수 없다** | **위험 근거 `official-vendor-doc` / 임계값·metric 이름은 `internal-policy`** | metric 이름·임계값은 **신규 제안**이다 — `metrics.yaml` 에 mongo row 가 0건이고 registry owner 절차(sibling [[raw/branch-notes/feature-contract-registry-governance]])를 거쳐야 한다. 본 branch 단독으로 확정 불가 |
|
|
| D13 | index 는 **automatic index creation 에 맡기지 않고** 선언적 manifest + 적용 러너 + drift 감지로 관리하며, 구현은 **자체 `IndexOperations` 러너**로 한다(Mongock·Liquibase 미채택) | drift 감지가 1급 요구사항이고 Spring Boot 4 호환 리스크를 없애야 함 → 자체 러너(기본). changelog 이력·rollback 추적이 drift 보다 중요 → Mongock 을 **시한부**로 채택하되 EOL 을 계약에 명시. 조직이 이미 Liquibase Pro 라이선스 보유 → Liquibase-mongodb Pro | `SD-MONGO-INDEX-C1`(`raw/official-docs/spring-data-mongodb-index-management-official.md#SD-MONGO-INDEX-C1` — "Automatic index creation is turned OFF by default as of version 3.0"), `C2`("index creation must be explicitly enabled since version 3.0 to prevent undesired effects on collection lifecycle and performance"), `C3`(programmatic 생성이 "(Recommended)" 로 표기, `IndexResolver`+`IndexOperations`), `C5`("Explicit index creation provides better control than automatic creation"). **대안 기각 근거** — `MONGOCK-C2`(`raw/official-docs/mongock-migration-lock-maintenance-official.md#MONGOCK-C2` — "Mongock will continue receiving critical bug fixes and security updates only. All innovation is happening in Flamingock."), `LIQUIBASE-MONGO-PRO-C1`(`raw/official-docs/liquibase-mongodb-pro-drift-report-official.md#LIQUIBASE-MONGO-PRO-C1` — drift report 접근이 **Pro** 기능 목록에 포함) | **채택 근거 `official-vendor-doc` / 기각 근거 `official-vendor-doc`(EOL·Pro gating) + `needs-confirmation`** | **기각 논리에 gap 이 있다** — Liquibase 자료는 "Pro 에 drift 가 있다" 를 증명할 뿐 "**무료 OSS extension 에 drift 가 없다**" 는 부정 명제를 직접 증명하지 않는다(해당 raw 의 Usage Boundaries). Mongock 역시 "drift 기능이 없다" 가 아니라 "이 페이지에서 확인되지 않음" 이다. 따라서 **기각은 `needs-confirmation`** 이며 §검증해야 할 주장 2행으로 승계한다. manifest 포맷·diff 알고리즘은 §구현 가이드 6 의 `UNSUPPORTED_IMPL_DECISION` |
|
|
| D14 | index migration runner 는 **단일 실행을 보장하는 lock** 을 갖는다 | multi-instance 배포 → lock 필수. 단일 인스턴스 로컬 → lock 없이 실행 허용하되 그 사실을 명시. lock 획득 실패 → 기동 거부가 아니라 **대기 후 실패**(다른 인스턴스가 적용 중일 수 있음) | `MONGOCK-C3`(`#MONGOCK-C3` — "As more than one instance of the client-service may be running simultaneusly in the environment [...] Mongock uses a pesimistic lock that is persisted in database") — 기성 도구도 이 문제를 DB 영속 lock 으로 푼다는 **벤더 확인**. project note §25 Multi-Instance Guardrail("migration runner: one app startup runner / multi-instance 활성화 시 migration lock 검증 필요"). sibling: [[raw/branch-notes/feature-distributed-lock-contract]] 가 migration runner lock 을 **자기 범위 밖**으로 선언 → 범용 `DistributedLockPort` 재사용 아님 | **문제 실재 `official-vendor-doc` / lock 메커니즘 선택 `internal-policy`** | lock 구현 수단(unique index + TTL 컬렉션)은 근거 없음 → §구현 가이드 6 의 `UNSUPPORTED_IMPL_DECISION`. 특히 **TTL index 의 만료 정밀도**가 lock 안전성에 미치는 영향은 미조사 → §검증해야 할 주장 — *lock 컬렉션 TTL 만료 정밀도* |
|
|
| D15 | Mongo 실패 분류는 **기존 `Category` enum 10종을 재사용**하고, error code 는 기존 `DB_*` 를 재사용하지 않고 **신규 제안**한다 | 실패가 기존 category 의미에 들어맞음 → 재사용(`TRANSIENT_DEPENDENCY`/`CONFLICT`/`DATA_INTEGRITY`/`INTERNAL`). code 는 기존 `DB_*` 가 **SQLState 기반**이라 Mongo 에 매핑되지 않음 → 신규 code 제안. **기존 code 를 의미 확장해 재사용하는 것은 선택하지 않는다**(owner 가 다른 branch) | `internal-code-fact`: `shared-contract/.../shared/error/Category.java:11-20`(enum 10종), `docs/registries/error-codes.yaml` — `DB_UNAVAILABLE`(L231)~`DB_QUERY_CANCELED`(L343) 9종이 전부 `owner_branch: feature-persistence-failure-baseline` 이고 주석이 **SQLState 매트릭스**(`08*`, `40001`, `40P01` …) 출처를 명시. mongo grep 0건. 선례: sibling [[raw/branch-notes/feature-integration-adapter-templates]] 가 `ADAPTER_DISABLED` 를 **신규 code** 로 만든 근거(startup lifecycle ≠ runtime lifecycle 혼동 방지) | `internal-code-fact` | 본 branch 는 registry **소비자이자 신규 제안자**다. code 이름·retryable·http_status 확정은 `feature-contract-registry-governance` 의 변경 절차를 거쳐야 하며 여기서 단독 확정할 수 없다 — §구현 가이드 3 은 **제안 표**로만 둔다 |
|
|
| D16 | mongo 모듈이 JPA/Hibernate/Flyway/PostgreSQL 타입에 의존하지 못하게 하는 **ArchUnit rule 을 신설**한다 | NoSQL adapter 모듈이 존재하는 한 → rule 필수. 모듈이 제거되면 → rule 도 함께 제거 | `internal-code-fact`: `app-bootstrap/src/test/java/.../architecture/CleanArchitectureTest.java:823-828` — "Future NoSQL adapter modules (for example adapter-persistence-mongodb) must be added as sibling modules ... **When such a module exists, add an ArchUnit rule forbidding** `jakarta.persistence..`, `org.hibernate..`, `org.springframework.data.jpa..`, `org.flywaydb..`, and `dev.caskeleton.adapter.outbound.persistence.postgresql..` **dependencies from that module.**" 그 모듈은 **이미 존재**하나(`modules.yaml:202-212`) rule 은 미작성(mongo grep = 이 주석 1건뿐) | `internal-code-fact` — 코드 주석이 조건과 대상 패키지를 **명시적으로 지정**함 | rule 이 없는 동안 mongo 모듈이 JPA 타입을 import 해도 빌드가 막지 않는다. 단 `src/build.gradle` 의 `allowedProjectDependencies` 가 프로젝트 의존만 통제하므로(`modules.yaml:207` = `[application-core, shared-contract]`) **외부 라이브러리 import** 는 현재 무방비다 |
|
|
| D17 | same-store Mongo outbox/inbox 는 본 branch 가 **가능 조건**(어떤 요건이 충족돼야 Mongo 에 둘 수 있는가)만 소유하고, 행 모델·상태 머신·프로토콜은 소유하지 않는다 | Mongo 가 업무 store 이고 D8 트랜잭션 게이트를 통과 → same-store outbox 가능. 트랜잭션 요건 미충족 → **불가**(append 원자성이 성립하지 않음). 행 모델·dispatch 모드 → [[raw/branch-notes/feature-outbox-dispatch-mode-contract]]. owner token 프로토콜 → [[raw/branch-notes/feature-idempotency-ownership-protocol-contract]] | `internal-code-fact`: 현행 `adapter/outbound/persistence-mongo/CLAUDE.md` 가 "does **not** reimplement idempotency / outbox / lock on Mongo (those stay JPA-only)" 로 **명시 금지**. project note §11 "outbox append → 업무 트랜잭션 롤백"(append 는 업무 write 와 원자) → Mongo same-store 는 D8 게이트에 종속. `MONGO-TXN-C1`(단일 document 는 이미 원자적) | **`internal-code-fact` + `internal-policy`(project note §11 상속)** | 현행 모듈 CLAUDE.md 의 금지를 **해제**하려면 그 파일을 고쳐야 하는데, 이는 ca-tmpl 측 변경이다. 본 branch 는 해제 **조건**만 정의하고 실제 해제는 outbox branch 착수 시점에 이뤄진다 — 두 문서가 어긋난 채 방치되면 다음 작업자가 "금지인가 허용인가" 를 되묻게 된다 |
|
|
| D18 | Mongo 쿼리 filter·document 값이 로그로 새지 않게 **driver logger 를 값 미노출 레벨로 고정**한다 — 관계형 계약의 "SQL/parameter 로그 금지" 에 대응하는 Mongo 측 규칙 | 운영 프로파일 → `org.mongodb.driver.protocol.command` 를 DEBUG 미만으로 고정(값 노출 차단). 로컬 디버깅 → DEBUG 허용하되 **운영 데이터가 없는 환경에서만**. 진단 목적으로 운영에서 DEBUG 가 필요 → `LoggerSettings.maxDocumentLength` 축소 + 한시적 활성화 + 종료 시각 명시 | `MONGO-JAVA-LOG-C3`(`raw/official-docs/java-driver-logging-mongodb-official.md#MONGO-JAVA-LOG-C3` — `org.mongodb.driver.protocol.command` 가 **DEBUG** 레벨로 command 시작/성공 기록), `C4`(그 DEBUG 라인의 `Command:` 필드에 **command document 전체가 그대로** 남으며 예시에 `filter` 키 포함), `C1`(`org.mongodb.driver.protocol` = "Commands sent to and replies received"), `C2`(`org.mongodb.driver.connection` 은 별개 관심사 — 연결/pool 은 값을 담지 않으므로 함께 끄지 않아도 됨), `C5`(`LoggerSettings.maxDocumentLength()` 기본 `1000`자), `C6`(logger 이름 단위 레벨 조정). governing doc §11 Persistence "SQL/parameter 로그 금지" 의 Mongo 대응 | **`official-vendor-doc`(무엇이 어느 레벨에 남는가) / 금지 정책 `internal-policy`(project note §11 상속)** | `maxDocumentLength` 기본값이 **1000자**라 축소해도 **값의 앞부분은 남는다** — 길이 제한은 유출 완화이지 차단이 아니다. 차단은 레벨 고정으로만 성립한다. 또한 [[raw/branch-notes/feature-log-management-contract]] D1 의 masking 계층은 **구현돼 있으나**(`internal-code-fact`: `logback-spring.xml:45-46`,`:81` + `SecretMaskingMessageConverter`) `LogMaskingPatterns` **카탈로그 기반 고정 패턴**이라 **부분만 덮는다** — 카탈로그 키(`password`/`passwd`/`pwd`/`secret`/`token`/`api_key`/`access_token`/`refresh_token`/`client_secret` + `authorization` 헤더 + 독립 `bearer`)에 해당하는 filter 필드 값은 실제로 마스킹되지만, **그 외 임의 업무 필드 값은 덮이지 않는다**. 부분 커버리지는 "마스킹되니 안전하다" 는 오해를 만들기 때문에 오히려 위험하다 — **값 차단은 레벨 고정으로만 성립한다**. 잔여 미검증 채널은 §구현 가이드 8 의 "덮지 않는 채널" 표 참조 |
|
|
|
|
<!-- section-id: implementation -->
|
|
## 구현 가이드
|
|
|
|
> 3-rule meta principle 적용 — R1 각 sub-section 은 Decision ID + Supporting Claim ID reference, R2 근거 없는 detail 은 `UNSUPPORTED_IMPL_DECISION` + trade-off 한 줄, R3 본 branch 결정 범위 밖은 이관.
|
|
>
|
|
> 코드 위치 표기: ca-tmpl repo 기준 상대 경로. 본 branch 는 **계약 SSOT** 이고 실제 코드 작성은 ca-tmpl plan 이 소유한다 — 아래 클래스명·경로는 코드로 확인된 것만 `actually-implemented` 로 표기했고 나머지는 `planned` 다.
|
|
|
|
### 1. 현행 as-built 와 이 branch 가 바꾸는 것
|
|
|
|
> **Trace**: D1(활성화 축) · D2(URI 소유) · D16(ArchUnit) — 전부 `internal-code-fact`
|
|
|
|
| 항목 | 현행 상태 | 등급 | 본 branch 후 |
|
|
|---|---|---|---|
|
|
| 모듈 존재 | `adapter/outbound/persistence-mongo` (소스 2개: `MongoPersistenceConfig`·`MongoPersistenceSettings`) | `actually-implemented` | 유지 |
|
|
| opt-in gate | `MongoPersistenceConfig.java:33-36` — `matchIfMissing` 미지정(프레임워크 기본 `false`) | `actually-implemented` | 유지 (D1) |
|
|
| classpath 무력화 | `app-bootstrap/.../application.yml:19-21` `spring.autoconfigure.exclude` 3건 ↔ `MongoPersistenceConfig.java:38-42` `@ImportAutoConfiguration` 3건 | `actually-implemented` | 유지 — **두 half 는 함께 바뀌어야 함** |
|
|
| repository 스캔 범위 | `:43` `@EnableMongoRepositories(basePackageClasses = MongoPersistenceConfig.class)` | `actually-implemented` | 유지 |
|
|
| 연결 URI | `spring.data.mongodb.*` **어디에도 없음** (`application.yml`·`src/.env` 126행 grep 0건) | — | D2 로 신설 + 미설정 시 startup 거부 |
|
|
| timeout·pool·tls | 미설정 (드라이버 기본값) | — | D3·D4 로 명시 |
|
|
| concern 3종 | 미설정 | — | D5·D6·D7 로 명시 |
|
|
| transaction | `TransactionPort` 구현체는 `persistence-jpa/.../SpringTransactionPort` **1개뿐** | `actually-implemented`(JPA만) | D8 게이트. Mongo 측 구현 여부는 §7 |
|
|
| change stream | 없음 | — | D11·D12 로 신설 |
|
|
| index manifest | 없음 | — | D13·D14 로 신설 |
|
|
| ArchUnit rule | `CleanArchitectureTest.java:823-828` 에 **TODO 주석만**, rule 미작성 | `documented-only` | D16 으로 신설 |
|
|
| registry row | `env-keys`·`metrics`·`error-codes`·`secrets-classification` 전부 **mongo row 0건** | — | D2·D3·D12·D15 가 신규 제안 |
|
|
| 로컬 실행 자산 | `docker-compose*.yml` 에 Mongo 서비스 없음, Mongo Testcontainers test 없음 | — | R1 승급의 선행 조건(§8) |
|
|
|
|
**Readiness 등급 판정 (project note §36.1)**: 현행은 **`R0` Contract** — 타입·seam·placeholder 만 존재. 근거: 문서/document/repository 0개, 로컬 서비스 없음, 통합 test 없음. **`R0` 를 `R2` 로 표기하는 것은 §36.1 금지 항목**이므로, 본 branch 완료 시점의 목표 등급은 `R1` Local(로컬 replica-set 컨테이너 + 집중 통합 test)이며 `R2` 주장은 실서비스 통합·runbook 확보 후에만 한다.
|
|
|
|
### 2. 연결 설정 계약 (명시 대상 키 집합)
|
|
|
|
> **Trace**: D2(URI) · D3(timeout·pool) · D4(tls) · D10(retryWrites) — `MONGO-CONNSTR-C1`~`C8`
|
|
>
|
|
> - **본 § 는 "어떤 키를 명시할 것인가" 만 고정한다.** 수치 값은 sibling [[raw/branch-notes/feature-env-driven-runtime-configuration]] 이 소유 — [[raw/branch-notes/feature-database-connection-pool-contract]] 가 Hikari 값을 그 branch 에 위임한 선례와 동일.
|
|
|
|
| 키 | 드라이버 기본값 (근거) | 명시 필수? | 이유 |
|
|
|---|---|---|---|
|
|
| `serverSelectionTimeoutMS` | 30,000ms (`MONGO-CONNSTR-C1`) | 운영 필수 | 30s 는 요청 타임아웃보다 길어 상류에서 먼저 끊긴다 |
|
|
| `connectTimeoutMS` | 10,000ms, 드라이버별 상이 (`C2`) | 운영 필수 | "드라이버별로 다를 수 있다" 는 원문 단서 자체가 명시 사유 |
|
|
| `socketTimeoutMS` | **no timeout** (`C3`) | **항상 필수** | 미명시 = 무한 대기. 유일하게 로컬에서도 명시 |
|
|
| `maxPoolSize` | 100 (`C4`) | 운영 필수 | 100 은 대부분 배포에서 과다 |
|
|
| `minPoolSize` | 0 (`C5`) | 운영 필수 | 0 이면 첫 요청이 연결 비용을 부담 |
|
|
| `maxIdleTimeMS` | **원문에 숫자 기본값 없음** (`C6`) | 제외 | 기본값을 모르는 키를 "기본값 대비 명시" 로 규정할 수 없음. **단 이 제외는 문서 부재를 근거로 한 것이고 드라이버의 실제 동작은 미확인** |
|
|
| `timeoutMS` (CSOT 통합 timeout) | (미조사) | **제외 — 채택 시 D10 재도출 필요** | `MONGO-RETRYWRITE-C3` 의 적용 범위가 "`timeoutMS` 를 별도 설정하지 않은 **기본 동작**" 이고, 같은 자료가 "`timeoutMS` 설정 시 몇 회까지 재시도되는지의 상한은 명시하지 않음" 을 Does-not-prove 로 남긴다. 즉 **`timeoutMS` 를 켜면 D10 의 "정확히 1회 재시도" 전제가 무효**가 된다 |
|
|
| `tls` | SRV=`true` / Standard=`false` (`C7`) | **항상 필수** | 연결 문자열 형식을 바꾸면 TLS 가 조용히 뒤집힘 (D4) |
|
|
| `retryWrites` / `retryReads` | 공식 드라이버 `true` (`C8`) | 명시 권고 | 기본이 켜져 있다는 사실을 계약에 드러내야 D10 의 "중첩 금지" 가 성립 |
|
|
|
|
> **UNSUPPORTED_IMPL_DECISION**: 이 키들을 **URI 쿼리스트링에 담을지, Spring `spring.data.mongodb.*` 개별 property 로 분리할지**. trade-off — 쿼리스트링은 드라이버 문서와 1:1 이라 대조가 쉽지만 URI 전체가 secret 분류가 되어 개별 값의 registry 검증(`verifyEnvKeys`)이 불가능해진다. 개별 property 는 registry 검증이 되지만 Spring 이 노출하는 property 집합이 드라이버 옵션 전체를 덮는지 확인되지 않았다. **registry 정합만 보면 개별 property 가 우세하나 결정 근거는 없다.**
|
|
|
|
### 2-1. concern·read preference 적용 seam (D5·D6·D7 이 *어디에* 붙는가)
|
|
|
|
> **Trace**: D5(read concern) · D6(write concern) · D7(read preference) — `SD-MONGO-TEMPLATE-C1`~`C6` · `MONGO-READPREF-C5` · `MONGO-TXN-C7`
|
|
>
|
|
> D5·D6·D7 은 *무엇을 명시할 것인가*를 정했다. 본 절은 Spring Data MongoDB 에서 **그 값이 붙는 지점**을 고정한다 — 이것이 없으면 구현자가 "repository 인가 template 인가 client 설정인가" 를 되묻는다.
|
|
|
|
| 축 | 적용 seam | 근거 | 상태 |
|
|
|---|---|---|---|
|
|
| write concern (기본값) | `MongoTemplate` 의 기본 `WriteConcern` 속성. 미설정 시 드라이버(`MongoClient`)의 DB/Collection 설정으로 폴백 | `SD-MONGO-TEMPLATE-C3` — "If it has not yet been specified through the driver at a higher level (such as `com.mongodb.client.MongoClient`), you can set the `com.mongodb.WriteConcern` property that the `MongoTemplate` uses ... If the `WriteConcern` property is not set, it defaults to the one set in the MongoDB driver's DB or Collection setting." | 확정 |
|
|
| **write concern (연산별)** | **`WriteConcernResolver`** 를 `MongoTemplate` 에 구성 — `remove`/`update`/`insert`/`save` 별로 다른 값 결정 | `SD-MONGO-TEMPLATE-C4`(per-operation 전략 인터페이스) · `C5`(`WriteConcern resolve(MongoAction action)`) · `C6`(`MongoAction` 이 collection 명·POJO 클래스·변환된 Document·연산 종류(`REMOVE`/`UPDATE`/`INSERT`/`INSERT_LIST`/`SAVE`)를 제공) | 확정 — **D6 의 "연산별 명시" 를 실현하는 수단** |
|
|
| read preference (기본값) | `MongoTemplate` 의 선택적 속성 중 하나. **`Query` 수준 지정이 template 기본값보다 우선** | `SD-MONGO-TEMPLATE-C1`(설정 가능 속성 목록에 `ReadPreference` 포함) · `C2`("The default read preference applied to read operations **if no other preference was defined via the Query**") | 확정 |
|
|
| read preference (트랜잭션 내) | 선택 불가 — `primary` 강제 | `MONGO-READPREF-C5` | 확정 |
|
|
| write concern (트랜잭션 내 개별 write) | **설정 금지** — 설정 시 에러. transaction/session/client 레벨에서만 지정 | `MONGO-TXN-C7` | 확정 |
|
|
| **read concern** | **미확정** | 해당 페이지에 "read concern"/"ReadConcern" 이 **0회 등장**(self-grep 0 매치) — `SD-MONGO-TEMPLATE` Does-not-prove | **`UNSUPPORTED_IMPL_DECISION`** |
|
|
|
|
> **UNSUPPORTED_IMPL_DECISION — read concern 적용 seam.** 수집한 자료 중 Spring Data MongoDB 가 read concern 을 어떤 API 로 노출하는지 다루는 것이 없다(위 페이지는 `WriteConcern`·`ReadPreference` 만 다룬다). 후보 — (a) `MongoClientSettings` 수준에서 전역 설정(연산별 분기 불가), (b) `Query`/`AggregationOptions` 수준 옵션이 존재한다면 그것, (c) `MongoTemplate` 을 감싼 자체 wrapper 에서 명시. trade-off — (a)는 확실히 존재하지만 **D5 의 "연산 단위로 명시" 를 충족하지 못한다**(정확성 조회와 근사 조회가 같은 값을 쓰게 됨). (b)가 있으면 D5 를 그대로 실현하지만 **존재 여부 자체가 미확인**이다. (c)는 항상 가능하나 프레임워크가 이미 제공하는 것을 재발명할 위험이 있다. **(b)의 존재 확인이 선행돼야 한다** — §검증해야 할 주장 참조.
|
|
>
|
|
> **범위 한정**: 위 seam 은 전부 `MongoTemplate` 경로 기준이다. 모듈 wiring 은 `@EnableMongoRepositories`(§1)이므로 **repository 파생 쿼리에 concern/preference 가 어떻게 붙는지는 본 표가 다루지 않는다**. 현재 repository·document 가 0개라 착수를 막지는 않으나, 첫 `@Document`/`MongoRepository` 를 추가하는 시점에 재확인해야 한다.
|
|
>
|
|
> 대비: write concern 은 `WriteConcernResolver`(`C4`~`C6`)라는 **연산별 결정 수단이 확인**된 반면, read concern 은 그 대응물이 확인되지 않았다. 따라서 D5 와 D6 은 같은 문장 구조("연산 단위로 명시")를 갖지만 **실현 확실성이 다르다**.
|
|
|
|
### 3. 실패 분류 — 기존 재사용 vs 신규 제안
|
|
|
|
> **Trace**: D15 · `internal-code-fact`(`Category.java:11-20`, `error-codes.yaml`)
|
|
>
|
|
> - **본 § 는 제안이다.** code 확정은 `feature-contract-registry-governance` 의 registry 변경 절차 소관이며 본 branch 가 단독으로 확정하지 않는다.
|
|
|
|
기존 재사용 (변경 없음):
|
|
|
|
| 대상 | 재사용 | owner |
|
|
|---|---|---|
|
|
| 실패 category 어휘 | `Category` enum 10종 (`VALIDATION`·`AUTH`·`AUTHZ`·`NOT_FOUND`·`CONFLICT`·`RATE_LIMIT`·`TRANSIENT_DEPENDENCY`·`PERMANENT_DEPENDENCY`·`DATA_INTEGRITY`·`INTERNAL`) | `shared-contract` |
|
|
| 토폴로지 검증 실패 (D2·D8·D11) | `STARTUP_VALIDATION_FAILED` | `feature-migration-startup-contract` |
|
|
| index migration 실패 (D13·D14) | `MIGRATION_FAILED` | `feature-migration-startup-contract` |
|
|
| 비활성 capability 런타임 호출 | `ADAPTER_DISABLED` | `feature-integration-adapter-templates` |
|
|
|
|
신규 제안 (**아직 registry 에 없음** — 이름은 확정 아님):
|
|
|
|
**가장 흔한 3종 먼저** — 아래 3행은 정상 운영에서 가장 자주 만나는 실패다. `DB_*` 가 SQLState 기반이라 재사용할 수 없으므로(D15) Mongo 측 대응이 필요하다:
|
|
|
|
| 실패 | 제안 category | 근거 claim |
|
|
|---|---|---|
|
|
| 서버 선택 실패 (`serverSelectionTimeoutMS` 초과 — 서버 미기동·전원 unreachable·replica set 미구성) | `TRANSIENT_DEPENDENCY` | `MONGO-CONNSTR-C1`(기본 30,000ms 후 예외). PostgreSQL 대응은 `DB_UNAVAILABLE`(503·retryable, owner `feature-persistence-failure-baseline`)이나 **SQLState 기반이라 재사용 불가** |
|
|
| 인증·권한 실패 (자격증명 오류, 최소권한 계정의 명령 거부) | `AUTH` / `INTERNAL` / `PERMANENT_DEPENDENCY` — **미정** (아래 `UNSUPPORTED_IMPL_DECISION` 참조) | 근거 claim 없음. 기존 `AUTH_*` 9종은 전부 **JWT 사용자 인증**용(owner `feature-authentication-authorization-contract`)이라 *서버-대-DB* 인증에 의미가 맞지 않는다. §구현 가이드 4 의 최소권한 검증(§검증해야 할 주장 — *토폴로지·FCV 질의를 최소권한 계정이 실행 가능한가*)과 쌍 |
|
|
| connection pool 고갈 (`maxPoolSize` 소진 후 대기) | `TRANSIENT_DEPENDENCY` | `MONGO-CONNSTR-C4`(기본 100)·`C5`(min 0). Hikari 대응(`DB_UNAVAILABLE` + `hikaricp.connections.acquire{outcome=TIMEOUT}`)은 [[raw/branch-notes/feature-persistence-failure-baseline]]·[[raw/branch-notes/feature-database-connection-pool-contract]] 소유이며 **JDBC 전용** |
|
|
|
|
나머지 (드물지만 계약이 필요한 경로):
|
|
|
|
| 실패 | 제안 category | 근거 claim |
|
|
|---|---|---|
|
|
| write concern 미달·`wtimeout` 초과 | `TRANSIENT_DEPENDENCY` | `MONGO-WRITECONCERN-C6`·`C7` — write 는 취소되지 않으므로 **재시도는 멱등 연산에만** |
|
|
| transaction 시간 한계 초과 abort | `CONFLICT` | `MONGO-TXN-PROD-C2` |
|
|
| `TransactionTooLargeForCache` | `INTERNAL` | `MONGO-TXN-PROD-C4` |
|
|
| change stream resume 불가 (oplog 롤오버) | `INTERNAL` | `MONGO-CHANGESTREAM-C7` |
|
|
| unique index 위반 (duplicate key) | `CONFLICT` | **선례 정합** — `internal-code-fact`: `error-codes.yaml:301-306` `DB_UNIQUE_VIOLATION` = category `CONFLICT` · `http_status: 409` · `retryable: false`. Mongo duplicate key 도 **같은 의미**이므로 category 를 일치시킨다(code 이름만 신규). sibling [[raw/branch-notes/feature-transaction-concurrency-contract]] D5 의 "optimistic 충돌 → 409 non-retryable" 계열과도 정합 |
|
|
|
|
> **UNSUPPORTED_IMPL_DECISION — 인증 실패의 category**: 위 표의 "인증·권한 실패" 행은 **category 자체가 미정**이다. 후보 — (a) `AUTH`, (b) `INTERNAL`, (c) `PERMANENT_DEPENDENCY`. trade-off — 기존 `AUTH_*` 9종은 전부 *클라이언트가 제시한 JWT* 를 다루므로 (a)를 쓰면 "누구의 인증인가" 가 로그·대시보드에서 뒤섞인다. DB 자격증명 오류는 **클라이언트가 고칠 수 없고 배포 설정으로만 고쳐지므로** (b)/(c)가 의미상 가깝다. **어느 쪽도 외부 근거가 없고**, 기존 registry 에도 *서버-대-의존성 인증 실패* 선례가 없다.
|
|
>
|
|
> **UNSUPPORTED_IMPL_DECISION**: 위 신규 code 의 **이름·`retryable`·`http_status`**. trade-off — 기존 `DB_*` 접두사를 이어 쓰면(`DB_WRITE_CONCERN_TIMEOUT` 등) grep 일관성은 좋지만 그 접두사는 SQLState 매트릭스가 owner 인 `feature-persistence-failure-baseline` 소유라 **의미 확장 = 남의 registry 침범**이다. `MONGO_*` 접두사는 소유가 깨끗하지만 vendor 이름을 error code 에 박는 첫 사례가 된다. **소유 경계만 보면 `MONGO_*` 가 우세하나 결정 근거는 없다.**
|
|
|
|
### 4. 토폴로지 검증 게이트 (transaction · change stream 공통)
|
|
|
|
> **Trace**: D8(transaction) · D11(change stream) · D2(URI) — `MONGO-TXN-C2`~`C4` · `MONGO-TXN-PROD-C1` · `MONGO-CHANGESTREAM-C1`·`C2`
|
|
|
|
검증 항목과 대상 기능:
|
|
|
|
| 검증 | transaction (D8) | change stream (D11) | 근거 |
|
|
|---|---|---|---|
|
|
| standalone 아님 (replica set / sharded) | 필수 | 필수 | `MONGO-TXN-PROD-C1` · `MONGO-CHANGESTREAM-C1` |
|
|
| primary WiredTiger | 필수 | 필수 | `MONGO-TXN-C3` · `MONGO-CHANGESTREAM-C2` |
|
|
| FCV ≥ RS `4.0` / SC `4.2` | 필수 | (해당 없음) | `MONGO-TXN-C2` |
|
|
| `writeConcernMajorityJournalDefault≠false` shard | 필수 (sharded) | (해당 없음) | `MONGO-TXN-C4` |
|
|
| read concern majority 지원 여부 | (해당 없음) | **무관** | `MONGO-CHANGESTREAM-C3` |
|
|
|
|
- 검증 시점은 sibling [[raw/branch-notes/feature-capability-provider-selection-contract]] D6 을 **상속**한다 — context refresh 완료 전(`SmartInitializingSingleton`), `ApplicationRunner` 로 옮기지 않는다. 본 branch 는 시점을 재정의하지 않는다.
|
|
- 검증 실패 시 `STARTUP_VALIDATION_FAILED` 로 기동 거부한다. **조용한 비활성 전환은 하지 않는다** — 활성화를 선언했는데 요건이 없으면 그것은 설정 오류다.
|
|
|
|
> **UNSUPPORTED_IMPL_DECISION**: 토폴로지·FCV·storage engine 을 **어떤 명령으로 질의할지**(`hello`/`buildInfo`/`getParameter` 등)와 **자격증명이 그 명령을 실행할 권한을 갖는지**. trade-off — 관리 명령은 권한이 없으면 실패하므로, 검증이 오히려 최소권한 계정에서 기동을 막을 수 있다. 근거 자료(위 4건) 중 어느 것도 *클라이언트가 이 요건을 런타임에 확인하는 방법*을 다루지 않는다. **검증 방법이 확정될 때까지 게이트는 "실패 시 거부" 정책만 확정이고 판정 수단은 미정이다.**
|
|
|
|
### 5. change stream checkpoint 와 재개 경로
|
|
|
|
> **Trace**: D11 · D12 — `MONGO-CHANGESTREAM-C4`~`C8`
|
|
|
|
재개 경로 결정표:
|
|
|
|
| 상황 | 사용 옵션 | 근거 |
|
|
|---|---|---|
|
|
| 저장된 resume token 있음 · invalidate 없었음 | `resumeAfter` | `C4` |
|
|
| **invalidate event 이후 재개** | `startAfter` | `C5` — `resumeAfter` 는 invalidate 이후 재개 불가 |
|
|
| token 없음 (최초 기동) | `startAtOperationTime` | `C6` — 과거 시점이면 oplog 시간 범위 안이어야 함 |
|
|
| oplog 가 token 을 넘겨 롤오버됨 | **재개 불가** — 소비를 중단하고 경보. 복구 방법은 아래 참조 | `C7` |
|
|
|
|
> **OUT_OF_BRANCH_SCOPE — "재개 불가 이후의 복구"**: 저장된 token 이 무효가 된 뒤 *무엇을 해야 소비자가 정합 상태로 돌아오는가*(컬렉션 전량 재스캔 / 보상 이벤트 재발행 / 수동 개입)는 **소비자가 무엇을 하는 소비자인지에 종속**되며 skeleton 범위 밖의 도메인 결정이다. 본 branch 는 여기까지만 소유한다 — (1) 이 상태를 **감지**하고(`C7`), (2) 소비를 **중단**하고, (3) **경보**한다. 조용히 최신 시점부터 재개해 이벤트 구간을 건너뛰는 동작은 **금지**한다(유실을 은폐하므로). 실제 복구 절차의 **문서 산출물 owner 는 [[raw/branch-notes/feature-operational-runbook-contract]] D9**(실 runbook 본문 작성)다. change stream 소비자를 도입하는 branch 가 도메인 절차를 정의하겠지만 **그런 branch 는 현재 저장소에 없다**(소비자 도입 시 신설) — 지금 시점에 이 절차를 소유한 문서는 없으며, 그 사실 자체를 여기 명시해 다음 작업자가 "이미 있는데 못 찾는 것" 으로 오해하지 않게 한다.
|
|
|
|
cursor 종료 4조건(`C8`)은 전부 **정상 처리 경로를 가져야 한다**: 명시적 close(정상 종료) / invalidate(→ `startAfter` 재개) / 연결 종료·timeout(→ `resumeAfter` 재개) / sharded cluster 의 shard 제거(→ `resumeAfter` 재개 + 경보).
|
|
|
|
> **UNSUPPORTED_IMPL_DECISION**: checkpoint **저장 위치와 저장 시점**. 후보 — (a) 같은 Mongo 의 전용 컬렉션, (b) PostgreSQL(업무 store), (c) 별도 store. trade-off — (a)는 change stream 대상과 저장소가 같아 운영 단순하고 D8 트랜잭션 게이트를 통과하면 처리와 checkpoint 를 한 트랜잭션에 넣을 여지가 있으나, Mongo 가 죽으면 checkpoint 도 함께 잃는다. (b)는 이미 트랜잭션이 검증된 store 지만 두 DB 에 걸친 원자성은 불가능해 **at-least-once 가 확정**된다. **근거 자료 어느 것도 checkpoint 저장 전략을 다루지 않는다.** 저장 시점(처리 전 vs 후)에 따라 at-least-once ↔ at-most-once 가 갈리므로, 이 선택은 project note §11 의 durable 경로 정책과 함께 결정돼야 한다.
|
|
|
|
### 6. index manifest · migration runner · drift
|
|
|
|
> **Trace**: D13(자체 러너) · D14(lock) — `SD-MONGO-INDEX-C1`~`C3`,`C5` · `MONGOCK-C2`,`C3` · `LIQUIBASE-MONGO-PRO-C1`
|
|
|
|
확정된 것:
|
|
|
|
- `spring.data.mongodb.auto-index-creation` 을 켜지 않는다. 근거 `SD-MONGO-INDEX-C1`(3.0 부터 기본 OFF) + `C2`("undesired effects on collection lifecycle and performance"). **기본값이 이미 OFF 이므로 "켜지 않는다" 는 별도 조치 없이 성립**하며, 계약의 역할은 누군가 켜는 것을 막는 것이다.
|
|
- 적용은 programmatic 경로를 쓴다 — `IndexResolver` + `IndexOperations` (`C3` 가 "(Recommended)" 로 표기, `C5` 가 "more control than annotations").
|
|
- drift 감지는 실제 인덱스 목록과 manifest 를 대조한다.
|
|
|
|
대안 기각 (D13 선택 조건의 근거):
|
|
|
|
| 대안 | 기각 사유 | 근거 | 확신도 |
|
|
|---|---|---|---|
|
|
| Mongock | 신규 개발이 Flamingock 으로 이전, critical fix 만 유지 | `MONGOCK-C2` | 확정 (벤더 공지) |
|
|
| Mongock (drift) | drift 기능이 **이 페이지에서 확인되지 않음** | `MONGOCK-C4` Does-not-prove | **`needs-confirmation`** — 부재의 증거 아님 |
|
|
| Liquibase-mongodb | drift report 가 **Pro** 기능 | `LIQUIBASE-MONGO-PRO-C1`·`C2` | 확정 |
|
|
| Liquibase (무료 티어) | 무료 extension 에 drift 가 **없다**는 부정 명제 | (직접 근거 없음) | **`needs-confirmation`** |
|
|
|
|
> **착수 순서 제약**: 위 표의 `needs-confirmation` 2행은 **본 절(§6)의 코드를 쓰기 전에** 닫아야 한다. 자체 러너는 §구현 가이드 최대 분량 절이고 전부 이 기각 논리에 종속되므로, 기각이 뒤집히면 작성한 코드를 되돌리게 된다. §검증해야 할 주장의 Mongock·Liquibase 자료 2건(Mongock v5 기능/lock 페이지 · Liquibase 무료 OSS extension 기능 목록)을 먼저 수집한다. 채택 근거(`SD-MONGO-INDEX-C1`~`C3`,`C5`)는 이미 충분하므로 **"auto-index-creation 을 쓰지 않는다" 부분은 지금 착수해도 안전**하다 — 되돌릴 위험이 있는 것은 *도구 대신 자체 러너를 만든다* 는 부분뿐이다.
|
|
|
|
> **UNSUPPORTED_IMPL_DECISION 1 — manifest 포맷과 drift 판정 범위.** 후보 — (a) `@Document` 애노테이션을 `IndexResolver` 로 읽어 manifest 를 코드에서 도출, (b) 별도 YAML manifest 파일, (c) 둘 병행. trade-off — (a)는 SSOT 가 하나지만 "인덱스 목록" 을 코드 밖에서 리뷰할 수 없고, (b)는 리뷰 가능하나 엔티티와 manifest 가 어긋날 수 있다. 또한 **drift 를 어디까지 볼 것인가**(키 순서만 vs `unique`/`partialFilterExpression`/`collation`/TTL `expireAfterSeconds` 까지)가 미정이며, `SD-MONGO-INDEX-C3` 의 Does-not-prove 가 "manifest·migration 이력·drift 비교 로직은 이 문서 범위 밖" 임을 명시한다. **어느 쪽도 외부 근거가 없다.**
|
|
>
|
|
> **UNSUPPORTED_IMPL_DECISION 2 — drift 발견 시 동작.** 후보 — (a) 기동 거부(`MIGRATION_FAILED`), (b) 경보만 남기고 기동, (c) 자동 교정. trade-off — (a)는 배포 중단 비용이 크고 운영자가 인덱스를 손으로 추가한 정당한 경우에도 막힌다. (c)는 프로덕션에서 예상치 못한 인덱스 빌드를 유발해 `SD-MONGO-INDEX-C2` 가 경고한 바로 그 "undesired effects" 를 재현한다. **(b)가 가장 보수적이나 결정 근거는 없다.**
|
|
>
|
|
> **UNSUPPORTED_IMPL_DECISION 3 — runner lock 메커니즘.** D14 는 lock 이 **필요하다**까지만 근거가 있다(`MONGOCK-C3` = 기성 도구도 DB 영속 pessimistic lock 을 씀). 후보 — (a) unique index 를 건 lock 컬렉션 + TTL 로 만료, (b) 배포 파이프라인 단일 Job 으로 분리(앱 밖), (c) 기존 `MigrationStartupRunner` 와 같은 프로세스에 묶기. trade-off — (a)는 D13 이 어차피 다뤄야 하는 unique/TTL index 를 재료로 쓰므로 신규 개념이 없으나 **TTL 만료 정밀도가 lock 안전성에 미치는 영향이 미조사**(§검증해야 할 주장 — *lock 컬렉션 TTL 만료 정밀도*)다. (b)는 sibling `feature-migration-startup-contract` 가 이미 `K8S-JOB-C1`/`C2` 로 근거를 확보한 패턴이지만 앱 readiness 게이트와 분리된다. **(a)가 기존 아키텍처와 가장 정합하나 결정 근거는 없다.**
|
|
|
|
### 6-1. oplog window 측정 (D12)
|
|
|
|
> **Trace**: D12 — `MONGO-CHANGESTREAM-C7`
|
|
>
|
|
> - **UNSUPPORTED_IMPL_DECISION — 측정 수단**: `C7` 은 "oplog 에 충분한 history 가 있어야 resume 이 된다" 는 **위험**만 증명하고, *남은 window 를 어떻게 재는가*는 다루지 않는다. 후보 — (a) `replSetGetStatus` 의 oplog 타임스탬프 범위, (b) `local.oplog.rs` 의 first/last entry 타임스탬프 차이, (c) `db.getReplicationInfo()` 의 `timeDiff`. trade-off — (a)·(c)는 관리 명령이라 §구현 가이드 4 와 **같은 최소권한 문제**를 공유하고(§검증해야 할 주장 — *토폴로지·FCV 질의를 최소권한 계정이 실행 가능한가*), (b)는 `local` 데이터베이스 직접 조회라 권한 요구가 다르고 `MONGO-READCONCERN-C12`(local DB 는 read concern 을 조용히 무시)가 적용되는 특수 영역이다. **어느 쪽도 외부 근거가 없다.**
|
|
> - **UNSUPPORTED_IMPL_DECISION — 경보 임계**: D12 는 임계를 "남은 oplog 시간 < 소비자 최대 다운타임 허용치" 로 표현했으나 그 허용치를 정하는 주체가 본 branch 가 아니다. [[raw/branch-notes/feature-metrics-alerting-contract]] **D3** 가 "alert threshold 는 SLO/error budget 또는 documented operational default 에 연결 — **임의 수치 금지**" 를 강제하므로, 본 branch 는 수치를 제안하지 않고 **임계의 형태**(비율 기반 vs 절대 시간)만 남긴다.
|
|
|
|
| 항목 | 본 branch 가 고정하는 것 | 위임 |
|
|
|---|---|---|
|
|
| 무엇을 재는가 | 남은 oplog window(시간) 와 마지막 저장 checkpoint 의 시간 격차 | — |
|
|
| 어떻게 재는가 | (미정 — 위 후보 3종) | — |
|
|
| metric 이름 | (제안하지 않음) | [[raw/branch-notes/feature-metrics-alerting-contract]] D2 (Micrometer dot.case + unit suffix) |
|
|
| 경보 임계 수치 | (제안하지 않음 — 임의 수치 금지) | [[raw/branch-notes/feature-metrics-alerting-contract]] D3 |
|
|
| registry 등록 | 신규 제안만 | [[raw/branch-notes/feature-contract-registry-governance]] |
|
|
|
|
### 7. 위임 — 본 branch 가 정의하지 않는 것
|
|
|
|
> **Trace**: D7(read preference) · D17(outbox/inbox) — R3 정제 결과
|
|
|
|
| 관심사 | owner | 본 branch 가 남기는 것 |
|
|
|---|---|---|
|
|
| `ReadConsistency` 정책 → replica 라우팅 판정 | [[raw/branch-notes/feature-read-consistency-query-contract]] (`DEC-…-READ-CONSISTENCY-001@1`) | Mongo 측 **표현 수단**만 (mode 5종 · `maxStalenessSeconds`) — D7 |
|
|
| outbox 행 모델 · dispatch 모드 · 순서 | [[raw/branch-notes/feature-outbox-dispatch-mode-contract]] | Mongo same-store **가능 조건**(D8 게이트 통과)만 — D17 |
|
|
| idempotency owner token 프로토콜 | [[raw/branch-notes/feature-idempotency-ownership-protocol-contract]] | 동일 — D17 |
|
|
| 활성화 property prefix 문자열 · descriptor 스키마 | [[raw/branch-notes/feature-capability-provider-selection-contract]] (D2·D3·D5·D6) | 축이 boolean 이라는 것만 — D1 |
|
|
| env key 이름·수치·bounds 등록 | [[raw/branch-notes/feature-env-driven-runtime-configuration]] (D2·D7) | **명시 대상 키 집합**만 — D3·D4 |
|
|
| secret 분류·회전 정책 | [[raw/branch-notes/feature-secrets-config-source-contract]] | URI 가 secret 이라는 신규 제안만 — D4 |
|
|
| 운영 runbook 문서 | [[raw/branch-notes/feature-operational-runbook-contract]] | Mongo runbook 부재를 §Audit A4 로 신고만 — 작성은 그 branch 절차 |
|
|
| error code 이름 확정 | [[raw/branch-notes/feature-contract-registry-governance]] | category 매핑 제안만 — D15 |
|
|
| PostgreSQL/JPA 실패 매핑 · Hikari pool | [[raw/branch-notes/feature-persistence-failure-baseline]] · [[raw/branch-notes/feature-database-connection-pool-contract]] | 없음 (본 branch 범위 밖) |
|
|
|
|
`TransactionPort` 를 Mongo 로 구현할 것인지(현재 구현체는 JPA 1개)는 **본 branch 에서 확정하지 않는다** — 두 `PlatformTransactionManager` 공존은 application-core 의 포트 계약에 영향을 주므로 `feature-application-port-usecase-contract` 와의 조율이 선행돼야 한다. D8 은 *Mongo 트랜잭션을 언제 켜도 되는가*만 정의한다.
|
|
|
|
### 8. 쿼리·document 값 로그 유출 억제 (D18)
|
|
|
|
> **Trace**: D18 — `MONGO-JAVA-LOG-C1`~`C6` · governing doc §11 Persistence("SQL/parameter 로그 금지")
|
|
>
|
|
> 관계형 쪽 대응은 [[raw/branch-notes/feature-persistence-failure-baseline]] 이 소유하나 그 구현은 Hibernate `show-sql`(JDBC 전용)에 한정된다. Mongo 는 **다른 logger 계층**을 쓰므로 같은 금지를 별도로 집행해야 한다.
|
|
|
|
| logger | 무엇이 남는가 | 값 노출? | 운영 정책 |
|
|
|---|---|---|---|
|
|
| `org.mongodb.driver.protocol.command` | command 시작/성공 이벤트. `Command:` 필드에 **command document 전체**(예시에 `filter` 포함) | **예 — 차단 대상** | DEBUG 미만으로 고정 |
|
|
| `org.mongodb.driver.protocol` (상위) | "Commands sent to and replies received" | 예 | 하위와 함께 고정 |
|
|
| `org.mongodb.driver.connection` | 연결·connection pool | 아니오 (값 미포함) | **끄지 않는다** — pool 고갈 진단(§구현 가이드 3)에 필요 |
|
|
|
|
규범:
|
|
|
|
- 운영 프로파일에서 command logger 를 DEBUG 이상으로 올리는 설정은 **금지**한다. 이 금지가 없으면 모든 쿼리 filter 값이 평문으로 남는다(`C4`).
|
|
- `LoggerSettings.maxDocumentLength` 는 **완화 수단이지 차단 수단이 아니다** — 기본 `1000`자(`C5`)를 줄여도 값의 앞부분은 남는다. 차단은 레벨로만 한다.
|
|
- connection logger 는 값을 담지 않으므로(`C2`) 일괄 차단 대상에서 **분리**한다 — pool 고갈·연결 실패 진단 능력을 잃지 않기 위해.
|
|
- 이 규칙은 Mongo 가 **비활성일 때도** 설정에 남아 있어야 한다(활성화 시점에 잊지 않도록).
|
|
|
|
**이 규칙이 덮지 않는 채널** (근거 자료가 Does-not-prove 로 명시한 범위 — "레벨만 내리면 다 막힌다" 고 주장하지 않는다):
|
|
|
|
| 채널 | 왜 안 덮이는가 |
|
|
|---|---|
|
|
| Command Monitoring API | logger 와 **별개 채널**이다. 애플리케이션이 command listener 를 등록하면 로그 레벨과 무관하게 command 내용을 받는다 — 등록 시 그쪽에서 별도 억제 필요 |
|
|
| `org.mongodb.driver.operation` 등 나머지 driver logger | 수집 자료가 이름 목록만 제공하고 각각이 값을 담는지 **실측을 주지 않는다** |
|
|
| `protocol` ↔ `protocol.command` 상속 관계 | 공식 목록에 `protocol.command` 가 `protocol` 의 자식으로 **명시돼 있지 않다**(추론일 뿐). 상위만 끄면 하위가 따라 꺼지는지는 미검증 → **두 이름을 모두 명시적으로 고정**한다 |
|
|
| Spring Data `org.springframework.data.mongodb.core` logger | Spring Data 자체 로깅은 driver logger 와 별개다. 수집 자료가 다루지 않아 취급 미정 |
|
|
|
|
> **UNSUPPORTED_IMPL_DECISION — 집행 수단**. 후보 — (a) `logback-spring.xml` 등 로깅 설정에 logger 레벨을 못박기, (b) 코드에서 `LoggerSettings` 를 빌드해 주입, (c) 운영 프로파일 startup 검증에서 해당 logger 의 유효 레벨을 확인하고 위반 시 거부. trade-off — (a)는 가장 단순하나 설정 파일을 고치면 조용히 뚫린다. (c)는 D2·D8 의 startup 게이트와 같은 모양이라 정합적이지만 로깅 프레임워크의 유효 레벨을 런타임에 질의하는 방식이 프레임워크마다 다르다. **(a)+(c) 조합이 가장 견고하나 결정 근거는 없다.** 로깅 설정 파일의 소유자는 [[raw/branch-notes/feature-log-management-contract]] 이므로 (a) 채택 시 그 branch 와 조율이 필요하다.
|
|
|
|
## Audit & Findings
|
|
|
|
> `/branch-spec` 실행 중 ca-tmpl 코드·registry 대조에서 발견한 정합 문제. **본 branch 가 자동 수정하지 않는다** — 사용자·owner branch 의 결정 영역이므로 정합 권고만 남긴다.
|
|
|
|
| ID | 유형 | 발견 | 근거 | 권고 |
|
|
|---|---|---|---|---|
|
|
| A1 | `MONGO_URI_UNCONFIGURED` | `spring.data.mongodb.*` 가 `application.yml` 과 `src/.env`(126행) 어디에도 없다(grep 0건). 즉 `ca-skeleton.persistence-mongo.enabled=true` 로 바꿔도 **연결 대상이 정의되지 않은 상태**다. (플래그를 켰을 때 기본 URI 로 붙는지 Spring 이 먼저 실패시키는지는 `needs-confirmation` — §검증해야 할 주장 — *플래그를 켰을 때 기본 URI 로 붙는지*) | `internal-code-fact`(부재는 grep 으로 확정) | D2 로 해소. env-keys.yaml 등록이 선행돼야 `verifyEnvKeys` 가 이 부재를 잡는다 |
|
|
| A2 | `ARCHUNIT_RULE_ABSENT` | `CleanArchitectureTest.java:823-828` 이 "그런 모듈이 생기면 rule 을 추가하라" 고 적었으나 모듈은 이미 존재(`modules.yaml:202-212`)하고 rule 은 미작성 | `internal-code-fact` | D16 으로 해소 |
|
|
| A3 | `MISSING_INTERNAL_DESIGN_DOC` | 프로젝트 결정 `DEC-…-MONGO-BASELINE-001` 의 근거란이 "ca-tmpl platform 설계 §12.3" 을 가리키나, ca-tmpl `docs/superpowers/specs/` 에 tracked 된 파일은 `2026-07-20-harness-policy-engine-design.md` **하나뿐**이다. `docs/` 는 gitignore 가 아니라 정상 tracked(54 파일)이므로 부재가 확실하다 | `internal-code-fact` (`git ls-files docs/superpowers/`) | **본 branch 는 §12.3 내용을 인용하지 않았다** — 모든 D-row 는 공식 벤더 문서 또는 코드 사실에 근거한다. project note §6.1 의 근거란 정정은 project-note owner 소관 |
|
|
| A4 | `REGISTRY_ROW_ABSENT` | `env-keys`·`metrics`·`error-codes`·`secrets-classification` 4개 registry 전부 mongo row 0건. `docs/runbooks/` 에도 mongo runbook 없음(`db-*.md` 5건은 전부 SQL) | `internal-code-fact` | D2·D3·D12·D15 가 신규 제안. 등록은 각 owner branch 절차 |
|
|
| A5 | `README_SCOPE_DRIFT` | ca-tmpl `README.md:25` 는 `adapter:outbound:persistence-*` 가 "JPA/PostgreSQL·MongoDB 영속 구현과 매핑·**migration**" 을 담당한다고 적었으나, mongo 모듈의 `CLAUDE.md`·`README.md` 는 wiring-only 이며 migration 을 명시적으로 다루지 않는다 | `internal-code-fact` | D13·D14 착수 시 README 문구를 실제 범위에 맞춘다 (낮은 심각도) |
|
|
| A7 | `OWNER_DOC_STALE` | [[raw/branch-notes/feature-log-management-contract]] 본문이 masking Layer 1 을 **미구현**으로 서술한다(`:190` "Layer 1~3 모두 미구현", `:274`, `:278`, `:334` "Layer 1 미구현(DRIFT-2)"). 그러나 코드는 구현돼 있고(`logback-spring.xml:45-46`,`:80-81` + `SecretMaskingMessageConverter`·`SecretMaskingJsonGeneratorDecorator`·`LogMaskingPatterns`) 같은 문서의 frontmatter `:22` `last_pass: 2026-06-14` 도 "Phase C2 전면 구현 완료" 로 갱신돼 있다 — **본문과 frontmatter 가 서로 어긋난 상태** | `internal-code-fact` | **본 branch 는 고치지 않는다**(Single-Owner). owner branch 가 본문을 정정해야 하며 `/sync` 로 그 branch 의 frontmatter↔본문 drift 를 별도 처리 권고. 본 branch 의 D18·§구현 가이드 8 은 **코드 사실**을 기준으로 작성했다 |
|
|
| A6 | `MODULE_METADATA_ODD` | `modules.yaml:211` 의 mongo 모듈 `mutation_import` 가 `dev.caskeleton.adapter.inbound.web.controller.HealthcheckController` — 본 모듈 타입이 아니다. 모듈에 자체 public 타입이 사실상 없어서일 수 있다 | `internal-code-fact` | 모듈에 실제 타입이 생기면 재검토 (정보성) |
|
|
|
|
<!-- section-id: edge-failure-dependency -->
|
|
## 엣지·실패·의존
|
|
|
|
**실패·엣지 경로** (각 경로의 기대 동작):
|
|
|
|
| 경로 | 기대 동작 | 근거 / 결정 |
|
|
|---|---|---|
|
|
| capability 활성인데 URI 미설정 | `STARTUP_VALIDATION_FAILED` 로 기동 거부. **기본 URI 로 조용히 연결하지 않는다** | D2 · §Audit A1 |
|
|
| standalone 배포에서 transaction/change stream 활성 선언 | `STARTUP_VALIDATION_FAILED` 로 기동 거부. 조용한 비활성 전환 금지 | D8 · D11 · `MONGO-TXN-PROD-C1` · `MONGO-CHANGESTREAM-C1` |
|
|
| `wtimeout` 초과 | write concern error 반환. **이미 적용된 write 는 되돌아가지 않는다** → 재시도는 멱등 연산에만 | D6 · `MONGO-WRITECONCERN-C6`·`C7` |
|
|
| transaction 이 1분 초과 | periodic cleanup 이 abort. 배치성 작업은 분할 + 멱등 재시도로 설계 | D9 · `MONGO-TXN-PROD-C2` |
|
|
| transaction 이 WiredTiger cache 초과 | `TransactionTooLargeForCache` 로 abort | D9 · `MONGO-TXN-PROD-C4` |
|
|
| 단일 oplog entry 16MB 초과 | 설계 제약으로 취급 — 트랜잭션 범위를 줄인다 | D9 · `MONGO-TXN-PROD-C3` |
|
|
| 일시적 네트워크 오류 · replica set election | 드라이버가 **1회** 재시도. 애플리케이션 재시도를 겹치지 않는다 | D10 · `MONGO-RETRYWRITE-C3` |
|
|
| 지속적 네트워크 오류 | 드라이버 재시도로 해결되지 않음 → 실패로 전파 | D10 · `MONGO-RETRYWRITE-C3` |
|
|
| `updateMany`/`deleteMany` 실패 | 드라이버가 재시도하지 않음 → 멱등 설계 + 명시적 재시도 필요 | D10 · `MONGO-RETRYWRITE-C5` |
|
|
| 트랜잭션 내부 개별 write 실패 | 개별 재시도 없음 → 트랜잭션 전체 재시도 (조건은 D9-a, 미확정) | D10 · `MONGO-RETRYWRITE-C6` |
|
|
| change stream invalidate (collection drop/rename) | cursor 종료 → `startAfter` 로 재개 | D11 · `MONGO-CHANGESTREAM-C5`·`C8` |
|
|
| change stream 연결 종료·timeout | cursor 종료 → 저장된 token 으로 `resumeAfter` 재개 | D11 · `MONGO-CHANGESTREAM-C8` |
|
|
| sharded cluster 에서 shard 제거 | cursor 종료 → `resumeAfter` 재개 + 경보 | D11 · `MONGO-CHANGESTREAM-C8` |
|
|
| oplog 롤오버로 resume token 무효 | **재개 불가** → **소비 중단 + 경보**. 최신 시점부터 조용히 재개해 구간을 건너뛰는 동작은 **금지**(유실 은폐). 복구 절차 자체는 §구현 가이드 5 의 `OUT_OF_BRANCH_SCOPE`. 이 상태에 **도달하기 전** 경보하는 것이 D12 | D11 · D12 · `MONGO-CHANGESTREAM-C7` |
|
|
| secondary 읽기가 오래된 데이터 반환 | 정상 동작 — stale 허용을 선언한 경로에서만 발생해야 함 | D7 · `MONGO-READPREF-C3` |
|
|
| sharded collection 을 `available` 로 조회 | orphaned document 반환 → **금지** | D5 · `MONGO-READCONCERN-C3` |
|
|
| `local` read concern 데이터의 rollback | 과반수 미기록 데이터는 롤백될 수 있음 → 정확성 경로에서 `local` 금지 | D5 · `MONGO-READCONCERN-C1` |
|
|
| 기존 데이터가 unique index 제약을 위반 | index 생성 실패 → `MIGRATION_FAILED`. **기존 데이터 정리 전략은 본 branch 범위 밖** | D13 · §검증해야 할 주장 — *unique index 기존 데이터 정리 전략* |
|
|
| **Mongo 서버 미기동·unreachable** | `serverSelectionTimeoutMS` 초과 후 예외 → `TRANSIENT_DEPENDENCY` 계열 신규 code. 기동 시점이면 D2 의 startup 거부와 구분한다(런타임 실패 ≠ 설정 오류) | §구현 가이드 3 · `MONGO-CONNSTR-C1` |
|
|
| **자격증명 오류 / 최소권한 계정의 명령 거부** | 실패로 전파. **category 미확정**(`needs-confirmation`) — 기존 `AUTH_*` 는 JWT 사용자 인증용이라 의미가 맞지 않음 | §구현 가이드 3 · §검증해야 할 주장 — *토폴로지·FCV 질의를 최소권한 계정이 실행 가능한가* |
|
|
| **connection pool 고갈** | `maxPoolSize` 소진 → 대기 후 실패, `TRANSIENT_DEPENDENCY` 계열. Hikari 의 `DB_UNAVAILABLE` 은 JDBC 전용이라 재사용 불가 | §구현 가이드 3 · `MONGO-CONNSTR-C4`·`C5` |
|
|
| **쿼리 filter·document 값이 로그로 유출** | 드라이버·template logger 를 값이 새지 않는 레벨로 고정 (D18) | D18 · §구현 가이드 8 |
|
|
| migration runner 동시 실행 | lock 으로 단일 실행 보장. 획득 실패 시 대기 후 실패 | D14 · `MONGOCK-C3` |
|
|
| 트랜잭션 안에서 read concern `majority`/`snapshot` 을 쓰되 `w:"majority"` 로 커밋하지 않음 | **보증이 조용히 사라진다** → D5·D6 을 쌍으로 강제 | `MONGO-READCONCERN-C6`·`C10` |
|
|
|
|
**다른 계약 의존**:
|
|
|
|
- `WI-CA-SKELETON-OPERATIONAL-CONTRACT-060` = [[raw/branch-notes/feature-capability-provider-selection-contract]] — **D2**(활성화 축 단일화)·**D3**(단일 prefix + `APP_*` env registry)·**D6**(startup 검증 시점 = refresh 완료 전)·**D10**(startup 실패는 기존 registry code 재사용)에 의존. 그 branch 가 prefix 를 `app.*` 로 확정하면 본 branch 의 D1 키(`ca-skeleton.persistence-mongo.enabled`)가 rename 대상이 된다.
|
|
- [[raw/branch-notes/feature-env-driven-runtime-configuration]] — **D2**(`APP_` 통일, registry = SSOT)·**D7**(`verifyEnvKeys` 3-way drift). 본 branch 의 D2·D3·D4 가 제안하는 키가 이 branch 절차로 등록돼야 검증이 성립한다.
|
|
- [[raw/branch-notes/feature-migration-startup-contract]] — `STARTUP_VALIDATION_FAILED`·`MIGRATION_FAILED` code 의 owner. 본 branch 는 소비자다.
|
|
- [[raw/branch-notes/feature-read-consistency-query-contract]] — `DEC-…-READ-CONSISTENCY-001@1` owner. D7 이 제공하는 Mongo 표현 수단을 그 branch 의 정책이 소비한다.
|
|
- [[raw/branch-notes/feature-outbox-dispatch-mode-contract]] · [[raw/branch-notes/feature-idempotency-ownership-protocol-contract]] — D17 이 same-store 가능 조건만 넘기고 행 모델·프로토콜을 위임.
|
|
- [[raw/branch-notes/feature-contract-registry-governance]] — D12(metric)·D15(error code) 신규 제안의 등록 절차 owner.
|
|
- [[raw/branch-notes/feature-application-port-usecase-contract]] — `TransactionPort` 계약 owner. Mongo 트랜잭션 구현 여부는 이 계약과 조율 후 결정(§구현 가이드 7).
|
|
- [[raw/branch-notes/feature-migration-startup-contract]] **D6** — "multi-instance 에서는 app startup runner 를 그대로 확장하지 않고 platform one-shot job 또는 migration lock 검증 필요". **본 branch 의 D14 는 이 결정을 상속한다** — Job 이냐 lock 이냐의 선택 자체는 그 branch 소유이고, 본 branch 는 *Mongo index manifest 러너에도 같은 규율이 적용된다*는 것만 선언한다. §구현 가이드 6 의 lock 메커니즘 후보 (b)(배포 파이프라인 단일 Job)가 그 branch 의 `K8S-JOB-C1`/`C2` 근거와 같은 선택지다.
|
|
- [[raw/branch-notes/feature-background-job-async-contract]] **D3** — "scheduler/outbox publisher 는 single-instance 기본, multi-instance 시 DB advisory lock 또는 ShedLock 필수"(lock 전략 SSOT). D14 가 Mongo 자체 lock 컬렉션을 택하면 이 SSOT 와 **다른 메커니즘**이 되므로, §구현 가이드 6 의 후보 선택 시 그 정합을 먼저 확인해야 한다.
|
|
- [[raw/branch-notes/feature-metrics-alerting-contract]] **D2**(metric naming = Micrometer dot.case + unit suffix 강제)·**D3**(alert threshold 는 SLO/error budget 또는 documented operational default 에 연결 — **임의 수치 금지**). D12 의 oplog window metric 이름·임계는 이 두 결정을 따라야 한다 — 본 branch 가 임의 수치를 제안할 수 없는 이유다.
|
|
- [[raw/branch-notes/feature-log-management-contract]] **D1**(JSON log 기본 + **Logback masking converter = Layer 1 SSOT**)·**D4**(production logging stdout JSON default). **D18 의 집행 수단 후보 (a)(로깅 설정 파일에 logger 레벨 못박기)는 그 branch 소유 파일을 건드린다** — 채택 시 조율 필수. 그 masking 계층은 `actually-implemented` 다(`internal-code-fact`: `app-bootstrap/.../logging/SecretMaskingMessageConverter.java` + `SecretMaskingJsonGeneratorDecorator`, `logback-spring.xml:45-46`,`:81` 에 `conversionRule`/decorator 등록). **그럼에도 D18 이 필요한 이유**: 그 계층은 `LogMaskingPatterns` **카탈로그 기반 패턴 masking**(token/password/bearer 류 고정 패턴)이라 임의 업무 필드 값 — Mongo query `filter` 의 값 — 은 패턴에 걸리지 않는다. 즉 masking 이 있어도 `org.mongodb.driver.protocol.command` 의 DEBUG 출력(`MONGO-JAVA-LOG-C4`)은 그대로 남는다.
|
|
- [[raw/branch-notes/feature-transaction-concurrency-contract]] **D5**(lock 충돌 분류 — optimistic 409 non-retryable / deadlock·serialization retryable-by-policy)·**D6**("duplicate command → idempotency branch key scope. **retryable write without idempotency forbidden**"). **D10 의 "재시도 경로는 멱등성을 스스로 보장한다" 는 D6 의 재진술이 아니라 그 정책의 Mongo 적용점이다** — 재시도·멱등 정책 자체의 owner 는 그 branch다. D9-a(트랜잭션 재시도 라벨)도 근거 확보 후 그 branch 로 이관하는 것이 정합적일 수 있다.
|
|
|
|
<!-- section-id: claims-to-verify -->
|
|
## 검증해야 할 주장 / Claims To Verify
|
|
|
|
> 공식 문서가 증명한 것은 MongoDB 의 동작이지 **우리 배포에서의 동작**이 아니다. 아래는 구현 전/중/후에 실제로 확인해야 하는 주장이다.
|
|
|
|
| Claim | Why uncertain | How to verify | Status |
|
|
|---|---|---|---|
|
|
| 본 branch 가 소유할 관심사가 sibling branch 결정과 겹치지 않는다 | D-row 17개가 새로 생겼고 D7·D17 은 명시적으로 sibling 에 위임한다 | `/sync` 실행 — owner 중복·재진술 검출 | `needs-confirmation` |
|
|
| **Mongock 에 index drift 감지 기능이 없다** | 근거는 "이 페이지에서 확인되지 않음" 이지 "없다" 가 아니다(`MONGOCK-C4` Does-not-prove). D13 의 기각 논리가 여기에 걸려 있다 | Mongock v5 문서 트리 전체(특히 lock·기능 목록 페이지) 확인 후 D13 기각 사유 확정 또는 철회 | `needs-confirmation` |
|
|
| **Liquibase 무료 OSS MongoDB extension 에 drift 가 없다** | 확보한 자료는 "Pro 에 drift 가 있다" 만 증명한다. "무료엔 없다" 는 부정 명제는 미증명 | 무료 `liquibase-mongodb` extension 기능 목록 페이지를 별도 수집해 대조 | `needs-confirmation` |
|
|
| lock 컬렉션의 TTL 만료 정밀도가 migration runner 의 단일 실행 보장에 충분하다 | §구현 가이드 6 의 후보 (a)가 TTL 만료에 의존하는데, MongoDB 의 TTL 백그라운드 스캔 주기를 이번 회차에서 공식 문서로 확인하지 않았다 | MongoDB TTL index 공식 문서 수집 → 만료 지연 상한을 확인하고 "이중 실행 윈도우" 허용 여부 판정 | `planned` |
|
|
| 기존 데이터가 있는 컬렉션에 unique index 를 걸 때의 실패·정리 전략 | 실패 경로는 §엣지에 넣었으나 **기존 데이터 dedup 전략**(자동 정리 vs 수동 vs partial unique index 우회)은 어느 대안을 골라도 필요한 별도 결정이며 이번 조사 범위 밖 | MongoDB unique index 공식 문서 수집 후 별도 결정으로 추가 | `planned` |
|
|
| 트랜잭션 재시도를 촉발하는 에러 라벨 집합 | D9-a 가 `UNSUPPORTED_DECISION` — `TransientTransactionError`/`UnknownTransactionCommitResult` 가 수집한 두 자료 어디에도 없다(self-grep 0 매치) | MongoDB Java driver 의 transactions 에러 처리 페이지 1건 수집 → D9-a 확정 | `planned` |
|
|
| `transactionLifetimeLimitSeconds` 의 정확한 기본 정수값 | 원문은 "less than one minute" 서술만 제공한다. 60 이라고 단정하지 않았다 | MongoDB server parameters 페이지에서 해당 파라미터 기본값 확인 | `planned` |
|
|
| `maxStalenessSeconds` 의 수치 하한 | read preference 자료가 메커니즘만 설명하고 하한을 명시하지 않는다(해당 raw 의 Usage Boundaries) | read preference staleness 전용 페이지 수집 | `planned` |
|
|
| **Spring Data `MongoTemplate`/Query 로 read concern 을 연산 단위로 설정하는 API 수단** | `spring-data-mongodb-template-config-official` 문서에 "read concern"/"ReadConcern" 문자열이 0회 등장(self-grep 확인) — `WriteConcernResolver` 에 대응하는 read concern 결정 수단(예: `ReadConcernResolver` 류)이 이 페이지에 없다. D5(read concern 연산 단위 명시)의 Spring Data 측 적용 seam 이 미확보 상태 | MongoDB Java driver 의 `MongoCollection#withReadConcern()` 공식 문서 또는 Spring Data MongoDB `ClientSession`/세션 스코프 문서 1건 추가 수집 | `UNSUPPORTED_DECISION` |
|
|
| Spring 이 노출하는 `spring.data.mongodb.*` property 가 §구현 가이드 2 의 키 집합을 전부 덮는다 | 드라이버 옵션과 Spring property 의 대응 범위를 확인하지 않았다. §구현 가이드 2 의 `UNSUPPORTED_IMPL_DECISION` 이 여기에 걸려 있다 | Spring Boot `MongoProperties` 코드/문서 확인 후 URI-쿼리 vs property 결정 | `planned` |
|
|
| 토폴로지·FCV 질의 명령을 최소권한 계정이 실행할 수 있다 | §구현 가이드 4 의 게이트가 관리 명령에 의존하는데, 권한 부족 시 게이트 자체가 기동을 막는다 | 로컬 replica-set 컨테이너에 최소권한 사용자를 만들어 검증 명령 실행 | `planned` |
|
|
| **oplog window 를 재는 수단** (`replSetGetStatus` / `local.oplog.rs` / `db.getReplicationInfo()` 중 무엇) | §구현 가이드 6-1 의 후보 3종이 전부 무근거다. 수집한 change stream 자료가 "측정·임계는 이 자료 범위 밖" 이라고 스스로 명시한다 | change stream Production Recommendations 또는 `replSetGetStatus`/`db.getReplicationInfo` 공식 페이지 1건 수집 → §6-1 의 `UNSUPPORTED_IMPL_DECISION` 확정. **D12 코드 작성 전에** 닫는다 | `planned` |
|
|
| **레벨 고정 후에도 잔여 채널로 쿼리 값이 새지 않는다** | §구현 가이드 8 의 "덮지 않는 채널" 4종(Command Monitoring API · 나머지 driver logger · `protocol`↔`protocol.command` 상속 미확인 · Spring Data 자체 logger)이 미검증이다. "레벨만 내리면 다 막힌다" 는 주장을 하지 않았다 | 로컬 replica-set 에서 레벨 고정 후 실제 로그를 수집해 filter 값 유출 여부를 확인 (D18 통합 test) | `planned` |
|
|
| **Mongo 서버-대-DB 인증 실패의 category** | §구현 가이드 3 의 후보 3종(`AUTH`/`INTERNAL`/`PERMANENT_DEPENDENCY`)에 근거가 없고 기존 registry 에 *서버-대-의존성 인증 실패* 선례가 없다 | registry owner([[raw/branch-notes/feature-contract-registry-governance]])와 category 의미 확인 후 확정 | `needs-confirmation` |
|
|
| 위 계약이 실제 replica-set 에서 성립한다 | 현재 `docker-compose*.yml` 에 Mongo 서비스가 없고 Mongo Testcontainers test 도 없다 — 계약을 실행해 본 적이 없다 | replica-set 컨테이너 + 통합 test 추가 (완료 조건의 "concern·index manifest·replica-set 트랜잭션·change stream checkpoint test") | `planned` |
|
|
|
|
## 관심사 커버리지 (coverage-auditor 자동 생성 — 있을 때)
|
|
|
|
> `coverage-auditor` 2회차(2026-07-28) 결과. **Verdict: Covered** — Blocking 0 / Should-fix 0 / Advisory 3. 손으로 유지하지 않는다(매 `/coverage` 실행 시 재생성).
|
|
|
|
| 관심사 | 상태 | owner | 심각도 | 근거 |
|
|
|--------|------|-------|--------|------|
|
|
| URI·timeout 3종·pool 2종·tls 명시 | covered-here | — | — | D2·D3·D4 |
|
|
| read/write concern·read preference 명시 (`DEC-…-MONGO-BASELINE-001`) | covered-here | — | — | D5·D6·D7 · §구현 가이드 2-1 |
|
|
| transaction replica-set/FCV/storage engine 게이트 | covered-here | — | — | D8·D9 · §구현 가이드 4 |
|
|
| change stream replica-set 게이트 + resume token 영속화 | covered-here | — | — | D11 · §구현 가이드 5 |
|
|
| oplog window 감시 | covered-here(측정 수단 `UNSUPPORTED_IMPL_DECISION`) + delegated(임계) | [[raw/branch-notes/feature-metrics-alerting-contract]] D2·D3 | OK | D12 · §구현 가이드 6-1 |
|
|
| index manifest·auto-creation 금지·drift·migration runner | covered-here | — | — | D13 · §구현 가이드 6 |
|
|
| migration runner 단일 실행 lock (§25 Multi-Instance Guardrail) | covered-here(메커니즘 `UNSUPPORTED_IMPL_DECISION`) | — | — | D14 |
|
|
| retryable writes 위 애플리케이션 재시도 중첩 금지 | covered-here | — | — | D10 |
|
|
| 트랜잭션 재시도 라벨 | covered-here(`UNSUPPORTED_DECISION`, 후속 dispatch 계획 명시) | — | — | D9-a · §검증해야 할 주장 |
|
|
| **Mongo 쿼리·document 값 로그 유출 억제 (§11)** | covered-here | — | — | **D18 · §구현 가이드 8** (1회차 Blocking 해소) |
|
|
| Data integrity / DB unavailable → 실패 category | covered-here(제안) | [[raw/branch-notes/feature-contract-registry-governance]] | OK | D15 · §구현 가이드 3 |
|
|
| query timeout | covered-here(`socketTimeoutMS` 로 대체) | — | ⚪ Advisory | D3 — per-operation `maxTimeMS` 는 미언급. 소켓 상한이 무한대기는 막으므로 Blocking 아님 |
|
|
| JPA system failure (governing §11 6항목 중 1) | out-of-scope | [[raw/branch-notes/feature-persistence-failure-baseline]] | — | Mongo 모듈은 JPA 미사용 — D16 이 오히려 JPA 의존을 금지 |
|
|
| ArchUnit vendor-isolation rule | covered-here | — | — | D16 |
|
|
| same-store Mongo outbox/inbox | covered-here(가능 조건만) + delegated(행모델·프로토콜) | [[raw/branch-notes/feature-outbox-dispatch-mode-contract]] · [[raw/branch-notes/feature-idempotency-ownership-protocol-contract]] | OK | D17 |
|
|
| `ReadConsistency` → replica 라우팅 정책 | delegated | [[raw/branch-notes/feature-read-consistency-query-contract]] | OK | D7 · §구현 가이드 7 |
|
|
| env key 등록·수치 | delegated | [[raw/branch-notes/feature-env-driven-runtime-configuration]] | OK | D3·D4 · §구현 가이드 2 |
|
|
| secret 분류(URI) | delegated | [[raw/branch-notes/feature-secrets-config-source-contract]] | OK (1회차 Should-fix 해소) | D4 |
|
|
| 운영 runbook | delegated(부재 신고만) | [[raw/branch-notes/feature-operational-runbook-contract]] | ⚪ Advisory | §Audit A4 — Mongo runbook 미작성, 위임 경로는 명확 |
|
|
| `TransactionPort` Mongo 구현 여부 | out-of-scope(조율 대기) | [[raw/branch-notes/feature-application-port-usecase-contract]] | OK | §구현 가이드 7 |
|
|
| 활성화 축(boolean vs provider) | covered-here(refines) | — | — | D1 |
|
|
| §36.2 capability card 전체 | 미충족(**요구되지 않음**) | — | ⚪ Advisory | 목표 등급이 `R1` Local — §36.2 는 R2 이상 주장 시에만 요구. R2 승급 시 재요구 |
|
|
|
|
## 마주친 문제
|
|
|
|
아직 없음.
|
|
|
|
## 묶음 (이 branch에서 파생된 자료)
|
|
|
|
<!-- GENERATED: branches:start -->
|
|
<!-- GENERATED: branches:end -->
|
|
|
|
## 관련 일일 노트
|
|
|
|
해당 없음.
|
|
|
|
## 완료 후 정리
|
|
|
|
- PR 링크:
|
|
- 리뷰 메모:
|
|
- 머지 결과 / 배포 환경:
|
|
- **wiki 추출 대상** (verified만, `wiki/projects/`로만 추출):
|
|
- **추출하지 않을 항목** (planned / documented-only / abandoned):
|