262 lines
19 KiB
Markdown
262 lines
19 KiB
Markdown
---
|
|
title: branch / feature-tenant-context-policy
|
|
source_type: branch-note
|
|
status: raw
|
|
branch: feature-tenant-context-policy
|
|
related_projects: [ca-skeleton]
|
|
tags: [branch, ca-skeleton, tenant, context]
|
|
created: 2026-05-22
|
|
target_merge:
|
|
status_label: in-progress
|
|
id: BR-CA-SKELETON-OPERATIONAL-CONTRACT-022
|
|
kind: project-work-item
|
|
project: ca-skeleton-operational-contract
|
|
work_item: WI-CA-SKELETON-OPERATIONAL-CONTRACT-022
|
|
inherits: [DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-FRAMEWORK-001@1]
|
|
refines: []
|
|
overrides: []
|
|
depends_on: []
|
|
contract_packet: 1
|
|
parent_branch:
|
|
contract_packet_sha256: a951c6ee8f27ba664f1919eab3d9750a969b5d76a0c0bd1a4ccc0d383c1ea699
|
|
---
|
|
|
|
# branch: feature-tenant-context-policy
|
|
|
|
> Layer: `raw/branch-notes/` — tenant context 지원/비지원 정책을 정의합니다.
|
|
|
|
<!-- section-id: branch-parent -->
|
|
## 부모 (필수)
|
|
|
|
- **Parent project (canonical SSOT)**: [[raw/project-notes/ca-skeleton-operational-contract]]
|
|
|
|
> ca-skeleton 은 별도 root branch 없이 project-note 가 SSOT 역할. 본 feature branch 는 project-note 의 운영 계약 중 해당 영역 (§<관련 섹션>) 의 결정/근거/금지 사항을 정제한다.
|
|
|
|
## 묶음
|
|
|
|
<!-- GENERATED: sources:start -->
|
|
- [[raw/company-tech-blogs/multitenancy-atlassian-tenant-context]]
|
|
- [[raw/company-tech-blogs/multitenancy-auth0-tenant-resolution]]
|
|
- [[raw/company-tech-blogs/multitenancy-hybrid-pooled-siloed-mix]]
|
|
- [[raw/company-tech-blogs/multitenancy-stripe-citus-schema-per-tenant]]
|
|
- [[raw/company-tech-blogs/multitenancy-subdomain-resolution-patterns]]
|
|
- [[raw/official-docs/multitenancy-aws-saas-tenant-isolation-whitepaper]]
|
|
- [[raw/official-docs/multitenancy-azure-architecture-patterns]]
|
|
- [[raw/official-docs/multitenancy-hibernate-user-guide]]
|
|
- [[raw/official-docs/multitenancy-microservices-io-pattern]]
|
|
- [[raw/official-docs/security-opa-policy-engine-official]]
|
|
<!-- GENERATED: sources:end -->
|
|
|
|
> 본 feature branch 는 leaf — 자식 자료 없음. Phase C2 실 코드 작성 단계에서 errors / interview prep / lectures 가 누적되면 본 섹션에서 그룹화.
|
|
|
|
### 오류 기록 (본 feature 작업 중 발생)
|
|
|
|
- (없음 — 현재 documented-only 단계)
|
|
|
|
### 면접 준비 (이 작업에서 나올 수 있는 면접 질문)
|
|
|
|
- (없음 — Phase C2 실 구현 단계에 누적)
|
|
|
|
<!-- GENERATED: branch-contract:start -->
|
|
<!-- section-id: branch-contract-packet -->
|
|
## 브랜치 계약 패킷
|
|
|
|
- **생성 시 프로젝트 개정**: `1`
|
|
- **패킷 스키마**: `contract_packet: 1`
|
|
- **완료 조건**: tenant propagation·clear negative fixture가 통과한다
|
|
|
|
<!-- section-id: inherited-project-decisions -->
|
|
### 상속한 프로젝트 결정
|
|
|
|
| Decision Ref | Project Summary | Branch Application | Source |
|
|
|---|---|---|---|
|
|
| `DEC-CA-SKELETON-OPERATIONAL-CONTRACT-STACK-FRAMEWORK-001@1` | framework는 Spring Boot 3.5.14다 | Work Item 완료 조건에 적용 | [[raw/project-notes/ca-skeleton-operational-contract]] |
|
|
|
|
<!-- section-id: branch-local-decisions -->
|
|
### 브랜치 지역 결정
|
|
|
|
> 기존 branch-local 결정은 아래 `## Decision Evidence Map / 결정-근거 매핑`의 D-row가 소유하며 이 packet에서 복제하지 않는다.
|
|
|
|
| Decision ID | Decision | Relation | Supporting Claims | Status |
|
|
|---|---|---|---|---|
|
|
|
|
<!-- section-id: declared-overrides -->
|
|
### 선언한 예외
|
|
|
|
| Override ID | Overrides | Reason | Approval | Status |
|
|
|---|---|---|---|---|
|
|
<!-- GENERATED: branch-contract:end -->
|
|
|
|
<!-- section-id: branch-goal -->
|
|
## 목표
|
|
|
|
멀티테넌트를 기본 지원하지 않더라도, 지원하지 않는다는 기준과 tenant header 처리 정책은 필요합니다. tenant context가 암묵적으로 섞이면 repository, log, security, cache key에서 누출 위험이 생깁니다.
|
|
|
|
- 이슈:
|
|
- PR:
|
|
|
|
<!-- section-id: branch-scope -->
|
|
## 범위
|
|
|
|
### 포함 범위
|
|
|
|
- multi-tenancy 지원 여부 명시.
|
|
- tenant header 허용/금지 기준.
|
|
- tenant context propagation 기준.
|
|
- tenant scoped repository는 tenant branch 활성화 시에만 허용.
|
|
- tenant leakage 테스트 기준.
|
|
- log/cache key tenant field 기준.
|
|
|
|
### 제외 범위
|
|
|
|
- 실제 SaaS tenant model 구현.
|
|
- tenant billing/plan policy.
|
|
- cross-tenant admin feature.
|
|
|
|
## TODO
|
|
|
|
> TODO drained 2026-05-22 — 결정은 아래 "결정 사항" / "Decisionized Work Items" 참조. multi-tenancy 지원 여부 / tenant header 허용/금지 / propagation / tenant scoped repository / leakage test / log·cache key 기준 모두 결정 라인 또는 matrix row로 반영됨. 잔존 TODO 없음.
|
|
|
|
## Work Item Contract
|
|
|
|
각 TODO는 아래 판정 단위로 재작성되어야 canonical 승급 가능합니다. TODO가 단순히 `기준 작성`으로 남아 있으면 이 branch는 완료로 보지 않습니다.
|
|
|
|
| field | required | rule |
|
|
| --- | --- | --- |
|
|
| Decision | yes | 구현자가 선택해야 하는 기본값 |
|
|
| Allowed | yes | 허용되는 예외와 조건 |
|
|
| Forbidden | yes | 절대 금지되는 구현/문서 상태 |
|
|
| Required registry update | conditional | error/env/header/log/metric/capability 변경 시 필수 |
|
|
| Required contract test | yes | 계약 위반 시 실패해야 하는 테스트 |
|
|
| Failure condition | yes | review/build에서 실패로 판정할 상태 |
|
|
| Canonical extraction target | yes | `wiki/projects` 승급 위치 |
|
|
|
|
## 진행 중 메모
|
|
|
|
- 지원하지 않는 기능도 out-of-scope로 명시해야 운영 ambiguity가 줄어듭니다.
|
|
|
|
## 결정 사항 (decisions)
|
|
|
|
- 2026-05-22: tenant context는 명시 정책이 필요.
|
|
- 2026-05-22: skeleton core는 multi-tenancy 미지원이 기본이며 tenant header는 기본 거부.
|
|
- 2026-05-22: tenant 활성화 시 idempotency/rate-limit/cache/log/repository key의 첫 scope는 tenant.
|
|
- 2026-05-22: tenant identifier는 raw PII가 아니어야 하며 log에는 opaque/pseudonymized id만 허용.
|
|
- 2026-05-22: tenant resolution 우선순위 = (1) JWT claim `tenant_id` (2) 명시적 X-Tenant-Id 헤더 (admin/internal API only) (3) subdomain. 충돌 시 (1) > (2) > (3).
|
|
- 2026-05-22: tenant ID format = opaque ULID (26 chars Crockford base32). UUID/numeric 금지. PII 아닌 opaque token.
|
|
- 2026-05-22: tenant 미지원 모드에서 X-Tenant-Id 헤더 수신 시 400 TENANT_NOT_SUPPORTED (filter 단계). gateway/interceptor가 아닌 Spring Security filter.
|
|
- 2026-05-22: async/event publish 경로 tenant propagation = TaskDecorator + message header `tenant_id`. consumer-side는 message에서 tenant 복원 후 SecurityContext에 inject. tenant_id는 background-job-async-contract의 TaskDecorator(SSOT)를 통해 async/event boundary에서 전파. 본 branch는 TaskDecorator의 tenant_id field 의무화만 명시. 별도 decorator chain 작성 금지.
|
|
- 2026-05-22: tenant_id ULID 원본은 metric tag에 직접 사용 금지. metric label 표현은 metrics-alerting-contract SSOT (bounded mapping id 또는 cohort bucket). 본 branch는 log/cache/repository scope에서만 ULID 원본 사용.
|
|
- 2026-05-22: repository-access-permission cross-cut = tenant 활성 시 모든 `@UseCaseRepositoryAccess` 호출은 tenant_id를 query에 자동 필터링. cross-tenant admin은 `CROSS_TENANT_ADMIN` capability 명시 선언 필요.
|
|
|
|
## 근거 (필수, 최소 1개+)
|
|
|
|
> 본 branch의 결정 근거. 상세 비교는 §외부 근거 / 대안 조사 (있다면) 참조.
|
|
|
|
| Source | 정당화하는 결정 |
|
|
|---|---|
|
|
| [[raw/official-docs/multitenancy-aws-saas-tenant-isolation-whitepaper]] | AWS의 Pool model (shared schema with row-level filter |
|
|
| [[raw/official-docs/multitenancy-hibernate-user-guide]] | Hibernate DISCRIMINATOR strategy (ca-tmpl 채택 |
|
|
| [[raw/company-tech-blogs/multitenancy-atlassian-tenant-context]] | 대규모 shared schema + tenant context 운영 사례 |
|
|
| [[raw/company-tech-blogs/multitenancy-auth0-tenant-resolution]] | JWT claim 우선 + subdomain/header 보조 (ca-tmpl과 정합 |
|
|
| [[raw/company-tech-blogs/multitenancy-subdomain-resolution-patterns]] | — |
|
|
| [[raw/company-tech-blogs/multitenancy-stripe-citus-schema-per-tenant]] | [[raw/official-docs/multitenancy-hibernate-user-guide]] (SCHEMA strategy |
|
|
| [[raw/official-docs/multitenancy-microservices-io-pattern]] | Silo model |
|
|
| [[raw/official-docs/multitenancy-azure-architecture-patterns]] | [[raw/company-tech-blogs/multitenancy-hybrid-pooled-siloed-mix]] |
|
|
| [[raw/company-tech-blogs/multitenancy-hybrid-pooled-siloed-mix]] | — |
|
|
|
|
## 외부 근거 / 대안 조사 (2026-05-22 — Topic 6)
|
|
|
|
본 branch의 multi-tenancy 결정 (opt-in `APP_TENANT_ENABLED` + shared DB + tenant_id column + ULID + JWT claim 우선 + `X-Tenant-Id` header admin only)에 대한 외부 source 조사. 비교 분석은 (예정) `wiki/concepts/multi-tenancy-isolation-patterns.md` 참조.
|
|
|
|
- **채택 결정 (opt-in shared DB + tenant_id column)**:
|
|
- [[raw/official-docs/multitenancy-aws-saas-tenant-isolation-whitepaper]] — AWS의 Pool model (shared schema with row-level filter)
|
|
- [[raw/official-docs/multitenancy-hibernate-user-guide]] — Hibernate DISCRIMINATOR strategy (ca-tmpl 채택)
|
|
- [[raw/company-tech-blogs/multitenancy-atlassian-tenant-context]] — 대규모 shared schema + tenant context 운영 사례
|
|
- **tenant resolution 비교**: [[raw/company-tech-blogs/multitenancy-auth0-tenant-resolution]] — JWT claim 우선 + subdomain/header 보조 (ca-tmpl과 정합)
|
|
- **검토한 대안**:
|
|
- **대안 1: Subdomain-based resolution** — [[raw/company-tech-blogs/multitenancy-subdomain-resolution-patterns]]
|
|
- **대안 2: JWT claim only (header 차단)** — `multitenancy-auth0-tenant-resolution` 의 variation
|
|
- **대안 3: Schema-per-tenant** — [[raw/company-tech-blogs/multitenancy-stripe-citus-schema-per-tenant]], [[raw/official-docs/multitenancy-hibernate-user-guide]] (SCHEMA strategy)
|
|
- **대안 4: Database-per-tenant (Silo)** — [[raw/official-docs/multitenancy-microservices-io-pattern]] (Silo model)
|
|
- **대안 5: Hybrid (tier-based / Deployment Stamps)** — [[raw/official-docs/multitenancy-azure-architecture-patterns]], [[raw/company-tech-blogs/multitenancy-hybrid-pooled-siloed-mix]]
|
|
- **비교 핵심**: ca-tmpl의 opt-in + shared DB + tenant_id는 B2B 초기 단계 적합 (tenant 수 수십~수백). **migration trigger**: (a) 규제(금융/의료)로 isolation 강제 → schema-per-tenant, (b) tenant 수 수백~수천 + 단일 row 수 수억 → schema-per-tenant 또는 hybrid, (c) enterprise tier 등장 시 isolation 가격화 → db-per-tenant.
|
|
|
|
## Decisionized Work Items
|
|
|
|
| item | Decision | Allowed | Forbidden | Required test |
|
|
| --- | --- | --- | --- | --- |
|
|
| support mode | disabled by default | explicit tenant branch activation | silent tenant header acceptance | unsupported header test |
|
|
| propagation | request context -> application -> repository/cache/log | async propagation with context wrapper | thread-local leak | propagation test |
|
|
| repository | tenant-scoped query required when enabled | cross-tenant admin with explicit capability | missing tenant predicate | leakage test |
|
|
| key prefix | tenant first | no tenant for disabled mode | tenant in some keys only | key consistency test |
|
|
|
|
## 테스트 계약
|
|
|
|
- tenant 미지원 모드에서 tenant header가 조용히 수용되면 실패.
|
|
- tenant 지원 모드에서 repository query에 tenant scope가 빠지면 실패.
|
|
- tenant id가 PII/secret처럼 과도하게 노출되면 실패.
|
|
- cache key에 tenant scope 기준이 없으면 실패.
|
|
- tenant 활성화 시 idempotency/rate-limit/cache/log principal scope가 서로 다르면 실패.
|
|
|
|
## 결정-근거 매핑
|
|
|
|
> 본 branch 의 각 결정이 어떤 raw source claim 으로 뒷받침되는지 명시한다. Decision ID 는 안정적으로 유지한다. company-tech-blog 출처는 `company-case-study` 로 표기하며 공식 best practice 로 일반화하지 않는다.
|
|
|
|
| Decision ID | Decision | Supporting Claims | Evidence Strength | Open Risk |
|
|
|---|---|---|---|---|
|
|
| D1 | tenant context 는 명시 정책 필요 | `raw/official-docs/multitenancy-aws-saas-tenant-isolation-whitepaper.md#AWS-TENANT-C1` (tenant isolation 은 SaaS 의 fundamental), `raw/official-docs/multitenancy-aws-saas-tenant-isolation-whitepaper.md#AWS-TENANT-C2` (boundary breach 는 un-recoverable) | `official-vendor-doc` (AWS whitepaper, 2026-05-27 main page verbatim 재확인) | AWS whitepaper Silo/Pool/Bridge sub-page 의 verbatim 정의는 `needs-confirmation` (2026-05-27 sub-page WebFetch truncated) |
|
|
| D2 | skeleton core = multi-tenancy 미지원 기본, tenant header 기본 거부 | `raw/official-docs/multitenancy-aws-saas-tenant-isolation-whitepaper.md#AWS-TENANT-C1`, `raw/official-docs/multitenancy-aws-saas-tenant-isolation-whitepaper.md#AWS-TENANT-C2` | `official-vendor-doc` | AWS whitepaper 는 "opt-in 기본 거부" 권장을 명시하지 않음 — ca-tmpl 운영 안전 default |
|
|
| D3 | tenant 활성화 시 idempotency/rate-limit/cache/log/repository key 의 첫 scope = tenant | `raw/official-docs/multitenancy-aws-saas-tenant-isolation-whitepaper.md#AWS-TENANT-C6` (Authentication is not isolation; resource layer enforcement — `needs-confirmation`), `raw/official-docs/multitenancy-hibernate-user-guide.md#HBN-MT-C2` (DISCRIMINATOR strategy 공식 지원) | `needs-confirmation` (AWS sub-page) + `needs-confirmation` (Hibernate body truncated, 구조는 `official-vendor-doc` 수준 확인) | AWS-TENANT-C6 의 verbatim 재확인 실패. Hibernate body verbatim 도 truncated — strategy 존재만 확인 |
|
|
| D4 | tenant identifier = raw PII 아님, log 에는 opaque/pseudonymized id 만 허용 | `raw/official-docs/privacy-gdpr-article-25-design.md#GDPR-A25-C1` (pseudonymisation as appropriate measure) — cross-link | `official-standard` | Art.25 는 tenant id 의 PII 여부를 명시하지 않음 — ca-tmpl 운영 안전 default |
|
|
| D5 | tenant resolution 우선순위 = (1) JWT claim `tenant_id` (2) `X-Tenant-Id` header (admin/internal API only) (3) subdomain | `raw/company-tech-blogs/multitenancy-auth0-tenant-resolution.md#AUTH0-TR-C1` ~ `C4` (JWT claim 우선 + subdomain/header 보조), `raw/company-tech-blogs/multitenancy-subdomain-resolution-patterns.md#MT-SUBDOM-C1` ~ `C7` | `company-case-study` (Auth0 + subdomain 패턴 — 공식 best practice 로 일반화 금지) | JWT claim 우선의 official standard 근거 없음. OIDC/JWT spec 의 multi-tenancy 관행 raw 미확보 |
|
|
| D6 | tenant ID format = opaque ULID (26 chars Crockford base32). UUID/numeric 금지 | UNSUPPORTED_DECISION — ULID 표준 spec raw 미확보 (Alizain Feerasta ULID spec 등) | none | ULID spec raw 등록 시 보강 가능 |
|
|
| D7 | tenant 미지원 모드에서 `X-Tenant-Id` 헤더 수신 시 400 TENANT_NOT_SUPPORTED (Spring Security filter) | `raw/official-docs/multitenancy-aws-saas-tenant-isolation-whitepaper.md#AWS-TENANT-C2` (boundary breach un-recoverable — fail-fast 정당화) | `official-vendor-doc` (간접 근거) | AWS whitepaper 는 specific HTTP status 또는 filter layer 를 명시하지 않음 — ca-tmpl 구현 선택 |
|
|
| D8 | async/event tenant propagation = TaskDecorator + message header `tenant_id` | UNSUPPORTED_DECISION — Spring TaskDecorator reference 또는 OpenTelemetry baggage 표준 raw 미확보 | none | Spring TaskDecorator / OpenTelemetry baggage spec raw 등록 시 보강 가능 |
|
|
| D9 | tenant_id ULID 원본은 metric tag 직접 사용 금지 (metrics-alerting-contract SSOT 가 bounded mapping 결정) | UNSUPPORTED_DECISION — high-cardinality label 회피 운영 결정. Prometheus 공식 doc raw 미확보 | none | Prometheus best practices raw 등록 시 보강 가능 |
|
|
| D10 | repository-access-permission cross-cut = tenant 활성 시 모든 `@UseCaseRepositoryAccess` 호출에 tenant_id 자동 필터링; cross-tenant admin = `CROSS_TENANT_ADMIN` capability 필수 | `raw/official-docs/multitenancy-aws-saas-tenant-isolation-whitepaper.md#AWS-TENANT-C6` (resource layer enforcement — `needs-confirmation`), `raw/official-docs/multitenancy-hibernate-user-guide.md#HBN-MT-C2` (DISCRIMINATOR strategy) | `needs-confirmation` + `needs-confirmation` | AWS sub-page 와 Hibernate body verbatim 모두 재확인 실패. capability 강제 enforcement 자체는 ca-tmpl 고유 |
|
|
|
|
## 검증해야 할 주장
|
|
|
|
| Claim | Why uncertain | How to verify | Status |
|
|
|---|---|---|---|
|
|
| AWS Silo/Pool/Bridge 정의의 verbatim 정확성 | 2026-05-27 sub-page WebFetch 가 페이지 title 만 반환 — body truncated | archive.org snapshot 으로 sub-page 본문 verbatim 재확인 또는 manual browser 검증 | `needs-confirmation` |
|
|
| AWS "Authentication is not isolation" 의 verbatim 정확성 | 2026-05-27 sub-page WebFetch 가 body truncated | archive.org snapshot 으로 sub-page 본문 verbatim 재확인 또는 manual browser 검증 | `needs-confirmation` |
|
|
| Hibernate 3 strategy (DATABASE/SCHEMA/DISCRIMINATOR) 정의의 verbatim 정확성 | WebFetch 가 sub-section 구조만 확인, body truncated | archive.org snapshot 으로 Hibernate User Guide chapter 24 본문 verbatim 재확인 또는 manual browser 검증 | `needs-confirmation` |
|
|
| `@TenantId` annotation 적용 시 entity 별 강제 여부 (opt-in 모델과 호환) | Hibernate 6 native 지원이라는 본문 verbatim 미확인 | Hibernate 6 reference doc + 실제 entity 에 적용 후 자동 필터 동작 contract test | `needs-confirmation` |
|
|
| `CurrentTenantIdentifierResolver` 의 ThreadLocal vs SecurityContextHolder 선택 | Spring Security 와의 통합 검증 미완 | Spring Security `SecurityContextHolder` 와 Hibernate resolver 통합 + thread-local leak 테스트 | `planned` |
|
|
| JWT claim `tenant_id` 우선이 OIDC/JWT 표준 multi-tenancy 관행 | OIDC/JWT multi-tenancy spec raw 미확보 | RFC 7519 (JWT) + RFC 7517 (JWK) + OIDC multi-tenancy 가이드 raw 등록 | `needs-confirmation` |
|
|
| ULID format opaqueness 가 PII 분류 회피 보장 | ULID spec 의 timestamp 추출 가능성 (앞 48-bit) | ULID spec raw 등록 + timestamp embed 의 PII risk 평가 | `needs-confirmation` |
|
|
| TaskDecorator + message header `tenant_id` propagation 의 thread-local leak 차단 | 비동기 경로 leak 테스트 미완 | `TenantPropagationContractTest` 구현 + @Async / Kafka publish 시 tenant_id leak 안 함 verify | `planned` |
|
|
| cache key `tenant_id` 우선 prefix 가 모든 cache 접근 경로에서 동작 | cache-consistency-contract 연동 미검증 | `CacheKeyTenantScopeTest` 구현 + Redisson / Caffeine 접근 시 tenant prefix 강제 verify | `planned` |
|
|
| migration trigger (tenant 수 수백~수천 + row 수억 → schema-per-tenant) 의 정량 기준 | 본 raw 의 비교 핵심은 일반 가이드. 실제 정량 trigger 미정 | tenant 증가 추이 + Citus / schema-per-tenant migration runbook 작성 | `needs-confirmation` |
|
|
|
|
## 마주친 문제
|
|
|
|
- 아직 없음.
|
|
|
|
## 구현 가이드
|
|
|
|
- ingress에서 검증한 tenant ID를 immutable context로 캡처하고 use case·outbound call에 명시적으로 전달한다.
|
|
- thread reuse·async handoff 전후에는 capture/restore/clear를 짝지어 이전 요청의 context가 남지 않게 한다.
|
|
- repository query와 cache key에는 같은 tenant scope를 적용하고 누락 시 fail-closed한다.
|
|
|
|
## 엣지·실패·의존
|
|
|
|
- context clear 누락은 cross-tenant data leak로 이어질 수 있으며 background job에는 요청 context가 없다는 별도 경계가 필요하다.
|
|
- authentication·runtime context propagation·persistence auditing 계약과 함께 검증한다.
|
|
|
|
## 관련 일일 노트
|
|
|
|
- 별도 일일 노트 없음.
|
|
|
|
## 완료 후 정리
|
|
|
|
- PR 링크:
|
|
- 리뷰 메모:
|
|
- 머지 결과 / 배포 환경:
|
|
- **wiki 추출 대상** (verified만, `wiki/projects/`로만 추출):
|
|
- `actually-implemented` 항목:
|
|
- `locally-verified` 항목:
|
|
- `prod-verified` 항목:
|
|
- **추출하지 않을 항목** (planned / documented-only / abandoned):
|