# 최종 확정안 — insuniverse 정책 문서화 추출 규약

> 확정일: 2026-08-03
> 근거: 라운드 1(`01`) → 라운드 2 자체반박(`02`) → 라운드 3 Codex 교차검증(`03`, `04`)
> 이 문서가 **정본**이다. 이후 모든 라운드는 이 규약을 따르고, 규약 변경은 버전을 올려 기록한다.

---

## 0. 사용자 확정 사항 (2026-08-03)

| 항목 | 확정 내용 | 영향 |
|---|---|---|
| 저장소 | 3개 (`insuniverse-server`, `insuniverse-site`, `insuniverse-admin`) | 별도 앱 저장소 없음 |
| 모바일 | **FCM 푸시 + 웹뷰만. native 요소 없음** | 4번째 축 불필요 → 각 문서의 "플랫폼 차이" 절로 처리 |
| env | **stg 수준 제공** — `insuniverse-site/front/.env` 수령 | 아래 §6 참조 (한계 있음) |
| `planning/` | ~~참조 금지 (사용자가 의도 삭제)~~ → **legacy 참고(L)** (2026-08-11 재판정) | 자동 추출 제외는 유지. 사람·에이전트 열람은 §6-4 규격 안에서 허용 |
| 버저닝 축 | prod 머지 기준 (승인) | prod-only / stg-only 분기 처리 추가 |
| 인덱스 선행 생성 | 승인 | §5 |
| 권한 DB 스냅샷 | ~~**미제공**~~ → **제공 가능 확정 · 수령 대기** (2026-08-12 정정, §6-2 참조) | ~~메뉴 권한은 `data-dependent-unknown` 확정~~ → **admin 그룹별 배분만** unknown (2026-08-05 정정). 스냅샷은 아직 이 저장소에 없다 — `snapshot_state=pending` · `receipt_route=ops-40-db-snapshot` |

### 모바일 축 실측 (native 없음 확인)

```
웹뷰 브리지   BRIDGE_NAME 32회 · postMessage 9회 · webkit.messageHandlers.nativeApp 1회
              20+ 파일 — 주로 PDF 다운로드 위임, 알림, 게시판
FCM           server: modules/fcm 4파일 + modules/push 7파일(스케줄러·spec 포함)
              site: 1파일
앱 최소버전    front/src/shared/hooks/App/useAppVersionCheck.ts
```

→ **모바일 정책 표면 3종**: ① 웹뷰 브리지 위임 ② FCM 푸시(server 주도) ③ 앱 최소버전 강제.
독립 장이 아니라 각 정책 문서의 `## 플랫폼 차이` 절에 기술한다.

---

## 1. extraction-manifest.yaml (0단계 — 인덱스보다 먼저)

라운드마다 수치가 흔들리는 것을 막는 규약이다. `_meta/extraction-manifest.yaml` 로 저장하고, 모든 추출 스크립트·에이전트가 이것만 참조한다.

> **개정 · 2026-08-05 (version 1 → 2)** — exclude 규약이 이 문서와 `insuniverse-docs-00-protocol` PRD 본문 두 곳에 갈라져 교집합이 `node_modules/` 하나뿐이었다. 이 문서를 정본으로 두고 PRD 쪽 항목을 흡수해 `exclude_pathspec` 을 8 → 14 항목으로 늘리고, `exclude_glob` 블록을 신설한다. git pathspec 하나로는 파일시스템 스캔(`grep`/`find`)이 걸러지지 않아 `dist/` 가 통과했고, 그것이 `@Roles` 137 이 나온 경로다. 두 목록의 정합 검사는 마일스톤 3 verify 소관이다.
>
> **개정 · 2026-08-05 (version 2 → 3)** — `env_gate_rules.known_gates` 에 런타임 축 게이트 4종을 추가한다. `RUN_MODE`·`SCHEDULER_TYPE` 은 CI 가 동일 이미지에 주입해 **스케줄러의 실제 활성 여부를 결정**하며, 이 둘 없이는 "코드에 있는 스케줄러가 실제로 도는가"가 판정되지 않는다. `REACT_APP_SMARTRO_MODE`·`REACT_APP_TOSS_CLIENT_KEY` 는 결제 벤더의 정책 분기가 환경별로 갈리는 지점이다 — 값은 시크릿이 아니라 모드 표시(`STG`/`PROD` · `test_ck_` 접두)이므로 `policy_gates_only: true` 를 위반하지 않는다. 요청자는 `12-index-runtime`. **게이트 이름은 이 문서와 manifest 소유, 게이트가 만드는 정책 의미는 `12-index-runtime` 소유**라는 경계를 같이 확정한다.
>
> **개정 · 2026-08-11 (version 8 → 9)** — 분쟁 5종 재측정(마일스톤 4)의 전제로 판정 규칙 **2종**을 확정한다. `commit_rules.body_present_rule` 은 "무엇이 trailer 인가"를 손으로 규정하지 않고 `git log --format=%(trailers:only)` 에 **위임**한다 — 정규식과 목록을 나란히 적으면 서로를 규정하지 못해 정의가 둘이 되고, 그것이 351 vs 349 를 낳은 경로다. `comment_rules` 에는 `dead_code_rule` 을 신설한다 — `exclude_commented_out_code: true` 한 줄이 9,462 vs 11,952 의 실제 축이었다. 두 블록 모두 아래 YAML 에 반영했으며 `extraction-manifest.yaml` 과 문자열 동기 상태다.
> **이 개정은 마일스톤 4(2026-08-06)에 확정됐고 당시 번호는 `2 → 3` 이었다.** `12-index-runtime` 축이 같은 기간 manifest 를 `3 → 8` 로 올려 `3` 이 두 뜻을 갖게 되므로, main 통합 시점(2026-08-11)에 `8 → 9` 로 재번호했다 — 내용은 그대로다.

> **이 YAML 은 규약 원안이지 동결본이 아니다.** 동결본은 `docs/_meta/extraction-manifest.yaml` 이며
> `repos[].path`·`head` 는 서브모듈 재구성(마일스톤 1) 이후 그쪽에만 실측값이 있다. 위 개정 주석의
> "문자열 동기"는 아래 `commit_rules`·`comment_rules` **두 블록**에 한정한다 — 그 둘만 manifest 와
> 글자 단위로 같다. `version` 은 manifest 를 따른다(현재 **9**).
>
> **아래 블록에 아직 반영되지 않은 항목** — `code_namespaces.menu_codes`(구 정규식, 실측 81 중 5 탈락)
> · `code_namespaces.screen_codes`(`null` → 추출기 + 전수 282 + `set_sha256`). 두 항목은 manifest 가
> 확정값을 갖고 있으며 **그쪽이 실제로 참조되는 산출물이다.** manifest `version: 4` 이후의 `reproduce:`
> 개정 5건은 이 문서에 대응물이 없다 — §1 YAML 블록에 `reproduce:` 블록 자체가 존재하지 않는다
> (manifest 의 `3 → 4`~`7 → 8` 주석이 매번 같은 사유를 기록한다). 이 블록의 동기화는 `00-protocol` 소관이다.

```yaml
version: 9                                  # manifest 와 같은 번호. 위 개정 주석 3건이 1 → 2 → 3 → (재번호) 9
created: 2026-08-03

repos:
  # 아래 path 는 서브모듈 전환 전의 원안이다. 실측 경로·SHA 는 manifest 가 갖는다.
  - name: insuniverse-server
    path: c:/Users/madsc/_projects/insuniverse-server
    branch: stg
    head: <추출 시점 SHA 고정 기록>
    src_roots: [apps, libs]
  - name: insuniverse-site
    path: c:/Users/madsc/_projects/insuniverse-site
    branch: stg
    head: <SHA>
    src_roots: [front/src]
  - name: insuniverse-admin
    path: c:/Users/madsc/_projects/insuniverse-admin
    branch: stg
    head: <SHA>
    src_roots: [src]

exclude_pathspec:          # 모든 git 명령에 강제 적용
  # ── 원본 (2026-08-03) ──
  - ":(exclude)planning/*"              # 사용자 의도 삭제 — 히스토리 포함 금지
  - ":(exclude)node_modules/*"
  - ":(exclude)*/_statics/*"
  - ":(exclude)*/pdfjs-dist/*"
  - ":(exclude)public/_statics/*"
  - ":(exclude)*.bcmap"
  - ":(exclude)*.svg"
  - ":(exclude)*.pfb"
  # ── 2026-08-05 병합: PRD 본문 exclude_paths 흡수 (@Roles 137 재발 방지) ──
  - ":(exclude)dist/*"
  - ":(exclude)build/*"
  - ":(exclude)coverage/*"
  - ":(exclude)migrations/*"
  - ":(exclude)seeds/*"
  - ":(exclude)artillery/*"

exclude_glob:              # 파일시스템 스캔(grep/find)에 강제 적용 — 2026-08-05 신설
  # pathspec 과 같은 대상 + git 이 애초에 보지 않아 pathspec 에는 불필요했던 항목
  - ".git/"                             # git 이 자기 디렉터리를 추적하지 않으므로 pathspec 에 없음
  - "node_modules/"
  - "dist/"
  - "build/"
  - "coverage/"
  - "planning/"
  - "migrations/"
  - "seeds/"
  - "artillery/"
  - "_statics/"                         # pathspec 의 */_statics/* · public/_statics/* 대응
  - "pdfjs-dist/"                       # pathspec 의 */pdfjs-dist/* 대응
  # *.bcmap · *.svg · *.pfb 는 이 목록에 없다 — 확장자 필터이지 디렉터리가 아니므로
  # 디렉터리 prune 규칙과 층이 다르다. 두 목록의 차집합 사유는 manifest 주석에 기록한다.
  #
  # _statics/ · pdfjs-dist/ 는 2026-08-05 대조에서 추가했다. PRD 본문의 9항목 초안은 이 둘을
  # 빠뜨렸는데, 둘 다 디렉터리라 glob 쪽에도 있어야 한다. 없으면 grep/find 스캔이 정적 자산과
  # 벤더 번들을 통과시켜 @Roles 137 과 같은 부류의 과다 계수가 다시 난다.

commit_rules:
  date_field: author          # committer 아님 — 재기록 이력 때문
  include_merges: true        # 릴리스 축에 필요. 단 파일 통계에서는 제외
  follow_renames: true        # site _site/ → front/ 이동 추적
  large_commit_threshold: 100 # 파일 수 초과 시 정책 추출 제외, "구조 변경 이벤트"로만 기록
  body_present_rule:          # 351 vs 349 분쟁 해소
    min_length: 5             # trailer·서명 제거 후 5자 이상
    strip_trailers: true      # Co-Authored-By, Signed-off-by 등
    # 2026-08-06 (version 2 → 3) — 정의를 git 에 위임한다. 판단 주체가 하나면 값도 하나다.
    trailer_source: 'git log --format=%(trailers:only)'   # 정의는 git 소관. 이 저장소가 규정하지 않는다
    git_min_version: "2.32"                               # %(trailers:only) 지원 하한. 미만이면 exit 1
    git_version_measured: "git version 2.55.0.windows.3"  # 확정값을 낸 그 버전. golden env.git_version 행과 같다
    measure: >-
      같은 git log 호출에서 %b(git 정의의 body — %B 는 제목을 포함해 2,590 중 2,588 이 통과하므로 분쟁 축이 아니다) 와 %(trailers:only) 를 함께 받아, 본문 꼬리에서 trailer 텍스트를
      제거 → 공백 정규화(연속 공백 1개 · 양끝 trim) → 길이 ≥ min_length
    caveat: >-
      하한 통과는 "%(trailers:only) 가 존재한다"만 보장하지 버전 간 동일 산출을 보장하지 않는다.
      버전을 값의 일부로 만들어(golden env.git_version) 발산을 시끄럽게 한다.
  era:
    A: {from: 2024-08-01, to: 2025-12-31}   # 자유형식, scope 없음 → 파일경로 기반 추출
    B: {from: 2026-01-01, to: null}         # conventional + scope → 제목 기반 추출

comment_rules:                # 9,462 vs 11,952 분쟁 해소
  languages: [ko]
  min_hangul_chars: 4
  block_types: [line, block, jsdoc]
  exclude_commented_out_code: true    # 주석 안이 코드 문법이면 제외
  unit: block                         # 라인 아님 — 연속 주석은 1블록
  classification:                     # 5단계. 1~2만 정책 후보
    1_policy:     [규칙, 정책, 기준, 제한, 상한, 하한, 차단, 허용, 불가, 전용, 필수, 의무, 만료, 이내, 초과]
    2_rationale:  [때문, 이유, 예외, 폴백, 방지, 실측, 사고, 주의, 우선]
    3_impl:       null      # 나머지 — 제외
    4_todo:       [TODO, FIXME, 임시, 확인 필요, 검증 필요]
    5_dead:       null      # 주석 처리된 코드 — 제외

  # ── dead_code_rule — 2026-08-06 신설 (version 2 → 3). 5_dead 판정을 코드로 고정한다 ──
  # 이 값은 참값이 아니라 아래 규칙의 함수다. 규칙을 바꾸면 golden 이 붉어지는 것이 정상이다.
  dead_code_rule:
    line_is_code_if_any:
      - '[;{}]\s*$'                                   # 문장/블록 종결
      - '^\s*(const|let|var|function|import|export|return|if|else|for|while|switch|class|await|async)\b'
      - '^\s*</?[A-Za-z][A-Za-z0-9]*[\s/>]'           # JSX 태그
      - '^\s*[A-Za-z_$][\w$]*\s*\([^)]*\)\s*;?\s*$'   # 단독 호출
      # 연속 행 6종 — 표본 대조가 드러낸 미검출 (2026-08-06)
      - '^\s*[A-Za-z_$][\w$-]*=[{"''`]'               # JSX 속성
      - '^\s*\{\.\.\.'                                # 스프레드
      - '^\s*[)\]}>/]+[;,]?\s*$'                      # 닫는 행
      - '^\s*[A-Za-z_$][\w$]*:\s*[{["''`]'            # key: {
      - '^\s*@[A-Za-z_$][\w$]*\s*\('                  # 데코레이터
      - '^\s*[A-Za-z_$][\w$]*:\s*\S.*,\s*$'           # 객체 속성 연속행
    # v2 표본이 드러낸 미검출 (2026-08-07)
    strip_trailing_comment: "위 패턴이 안 맞으면 행 끝 `// …` 를 떼고 재판정. 따옴표 안과 `://` 는 제외. code 판정을 더하기만 한다"
    block_is_dead_if: "비율 > 0.5. 정확히 0.5 면 tie_break_strong 으로 가른다"
    tie_break_strong: '^\s*(const|let|var|function|import|export|return|if|else|for|while|switch|class|await|new|this)\b'
    rationale: >-
      비율 판정인 이유는 주석 처리된 코드에 설명 한 줄이 섞이는 것이 흔하기 때문이다.
      경계가 `>` 인 이유는 산문 속 예시 한 줄이 `}` 로 끝나 오탐을 냈기 때문이고,
      동점을 따로 가르는 이유는 그것만으로는 설명 1행 + 코드 1행이 살아나기 때문이다.
      트레일링 주석을 떼는 이유는 주석 처리된 코드에 원래 붙어 있던 주석이 그 코드를
      코드가 아닌 것으로 만들었기 때문이다.
    corpus_check:
      sample: docs/_meta/verify/fixtures/dead-code-sample.tsv
      size: 40
      gate: "불일치 0. 표본 부재·행수 미달·형식 위반·id/경로:행 중복도 동일하게 exit 1 (중복 금지가 '40행'과 '서로 다른 40블록'을 가른다)"
      result: >-
        v1 (2026-08-06): 6/40 불일치 → 전부 규칙 결함이라 규칙을 고쳐 40/40 일치.
        v2 (2026-08-07): 고친 규칙으로 표본을 다시 뽑아 재라벨링 → 2/40 불일치.
        1건은 규칙 미검출(트레일링 주석)이라 규칙을 고쳤고, 1건은 라벨러가 근거와 어긋난
        라벨을 정정했다. 결과 40/40 일치.
      resample_policy: >-
        규칙을 고쳐도 표본을 다시 뽑지 않는다. 표본이 규칙 결함을 찾는 도구인 이상 고칠
        때마다 층화가 움직이고, 그때마다 재라벨링을 강제하면 수렴하지 않는다(40 → 32 → 23 → …).
        층화 이동은 golden 행 sample.stratum_sha256 이 드러내고, 새 표본은 사람이 필요하다고
        판단할 때 뽑는다. 커밋된 표본이 실제 코퍼스 블록이라는 것은 접지 대조가 매 실행 보증한다.

test_rules:                   # site 16 vs 0 분쟁 해소
  patterns: ["*.spec.ts", "*.spec.tsx", "*.test.ts", "*.test.tsx"]
  scan_all_tracked: true      # src_roots 한정하지 않음
  record: [skip_status, in_ci]

code_namespaces:              # 단일 codes.yaml 금지
  error_codes:         '^E[0-9]{4}$'
  menu_codes:          '^(CRP|SAL)_[A-Z]{3}[0-9]{3}$|^[A-Z]{3}[0-9]{3}$'
  domain_status_codes: '^(ANS|ODD|SUB|PAY|MEM|PRD|BRD|IVC|LSA|REF|SND|POP|BNN|INS|ADL|ADT|APT)[0-9]{3}[A-Z]?$'
  vendor_codes:        '^(CF|CODEF|COOCON|HYPHEN|OCTOVER)[-_A-Z0-9]+$'
  screen_codes:        null   # 라운드 4에서 확정
  fields_per_code: [defined_in, consumed_by, serialized, persisted, active_reference_count, parent]

release_rules:
  # 2026-08-05 정정 — 원문 `compare: "..." / "..."` 은 유효한 YAML 이 아니었다(스칼라 뒤에 / 와
  # 두 번째 스칼라). 이 블록이 한 번도 파싱된 적이 없다는 뜻이다. 의미는 그대로 두고 구조화한다.
  compare:
    prod_only: "origin/prod --not stg"
    stg_only:  "stg --not origin/prod"
  buckets: [prod_merge, prod_only, stg_only]
  release_note_pattern: '^merge(\(stg\))?: *stg *(→|->|to) *prod'

env_gate_rules:
  # 게이트 **이름** 목록은 이 문서와 manifest 소유.
  # 게이트가 만드는 **정책 의미**는 12-index-runtime 의 schedules.yaml · vendor-behaviors.yaml 소유.
  policy_gates_only: true     # 비밀값·접속정보 제외
  known_gates:
    - ADMIN_VIEW_TOKEN_ENFORCE
    - ADMIN_VIEW_TOKEN_TTL_MS
    - RATE_LIMIT_CERTIFY_PER_MIN
    - CAR_VIEW_ADMIN_IDS
    - DISEASE_PDF_HIDE_NAME_AGENCY_ID
    - TREAT_PROVIDER
    - TREAT_POOL
    - ENABLE_TREAT_CAR
    - INHOUSE_CAR_CANARY
    - B2C_BYPASS_SLOT
    - SETTLE_TARGET_DATE
    # ── 2026-08-05 version 3: 런타임 축. 12-index-runtime 요청분 ──
    - RUN_MODE                # CI 주입. 스케줄러 실제 활성 여부를 결정
    - SCHEDULER_TYPE          # CI 주입. 위와 같음
    - REACT_APP_SMARTRO_MODE  # site 빌드타임. 결제 벤더 환경별 정책 분기 (STG/PROD)
    - REACT_APP_TOSS_CLIENT_KEY  # 위와 같음 (test_ck_ 접두 = 테스트 모드)
```

---

## 2. 확정 소스 등급

라운드 3 정정 반영. **소스 전체가 아니라 항목 단위로 등급을 매긴다.**

| 등급 | 소스 | 실측 | 신뢰 조건 |
|---|---|---|---|
| **S** | 테스트 (`*.spec/test.*`) | server 37 / site 16 / admin 0 | skip 아님 + CI 포함 확인 시. 정책의 실행 가능 명세 |
| **S** | `@Roles()` 매트릭스 | 136개소 / `admin`·`sales`·`corp` | 완전 기계 추출 |
| **S** | Swagger + class-validator | 3,213회/245파일 + 3,047회/137파일 | 입력·응답 계약의 정본 |
| **S** | DB 스키마 제약 | 153테이블 / `.default()` 299 / `.notNull()` 896 | 테이블 수가 아니라 제약이 정책 |
| **S** | Guard 13종 + `constants/*.constant.ts` | E0001~E05xx | — |
| **S** | 정책 상수 | `MAX_MONEY_*`, `*_TTL_MS`, `RATE_LIMIT_*`, `BATCH_SIZE`, `*_TIMEOUT` 등 | — |
| **A** | 한글 주석 **분류 1~2단계만** | 전체 블록 중 25~40% 추정 | **단독 근거 금지** — 코드·테스트·스키마와 결합 필수 |
| **A** | 스케줄러 16종 + CI `SCHEDULER_TYPE` | 시간 기반 정책 | CI 주입값으로 실제 활성 확인 |
| **A** | 벤더 분기 | 1,762회 / 155파일 | 벤더별 성공/자료없음/실패 의미가 다름 |
| **A** | 약관·동의 | 427회 / 60파일 | 법적 정책 |
| **A** | prod 머지 / prod-only / PR / `feat/*` 에픽 | — | 시점·서사 |
| **B** | Era B 커밋 제목 | 2026년 ~1,000건 | why 보조 |
| **C** | Era A 커밋 제목 | 2024~25 ~2,000건 | 시점 추정만 |
| **D** | task-ID / 한글 토큰 교집합 | 12·10·5개 / 370토큰(노이즈 과다) | 조인 키로 쓰지 않음 |
| **L** | `planning/` | admin 34 / site 8 파일 (**HEAD 추적 중**) | **legacy 참고 전용.** 본문 인용 금지 · 값 인용 금지. §6-4 규격 안에서만 |
| **U** | 유지보수자 구술 (`10-user-oral-source.md`) | 2026-08-11 수령 1건 | **정책 본문 금지.** `90-history/00-origin.md` 와 glossary `출처: 구술` 표기 항목 전용. 저장소로 검증 불가함을 문서에 명시 |
| **O** | ops 원장 — 운영 문서 판독을 앵커 결박 statement 로 만든 것 | 계수 주체는 `ops-doc-reconcile` 우산이다. 이 저장소는 그 수를 세지 않는다 | `assignment_scope=unassigned` · 우산의 verdict 표를 그대로 따른다 — `agree` 만 값 인용 가능 · `ops-only` 는 **정책 본문 금지**이며 `docs/90-history/` 전용 · `stale` · `unresolvable` 은 쓰지 못한다 |

등급 **O** 행은 **어휘 자리만 만든다.** 어느 정책이 등급 O 를 받는지, 등급 O 근거가 본문에
들어갈 수 있는 조건이 무엇인지는 이 문서가 정하지 않는다 — 그 판정은 `ops-doc-reconcile`
소관이다. 그 제약은 산문이 아니라 위 신뢰 조건 셀의 기계 표식 `assignment_scope=unassigned`
가 지고, 게이트는 **그 필드만** 읽는다. 표식 옆의 서술은 자유롭게 다시 쓸 수 있다.
config 쪽 대응 어휘는 `evidence_grades` 의 `O-ops` 이며 둘은 **쌍**이다 — 한쪽만 있으면
근거 없는 등급이 통과하거나(`O-ops` 만) `evidence: [O-ops]` 를 쓴 문서가 거부된다(`O` 행만).

### 확정된 개념 분리 (라운드 3 정정)

```
역할(role)          admin · sales · corp          ← @Roles() 대상
기업역할(agcRole)    CORP_SUPER · CORP_ORG         ← 2종. auth.service.ts:132/192
능력(capability)     orgCanWrite (bool)
열람레벨             corpAnalysisViewLevel         ← SetMetadata 키
에러상수             CORP_FORBIDDEN = E0401        ← 역할 아님
```
이 5개를 한 목록에 섞으면 권한 문서가 틀린다.

---

## 3. 활성성(status) 모델 — "안 쓰는 정책은 안 쓴다"의 구현

| status | 라벨 | 배치 | 판정 | 문서 처리 |
|---|---|---|---|---|
| `active-prod` | 정상 | `body` | prod 도달 + 게이트 없음/활성 | **본문 수록** |
| `conditional-enabled` | 게이트 활성 | `body` | 게이트 있고 활성 확인 | **본문 수록** + 게이트 명시 |
| `prod-hotfix-unreconciled` | 미정합 | `body` | prod만 있고 stg 역류 안 됨 | **본문 수록** + 불일치 경고 |
| `conditional-unknown` | 게이트 미상 | `appendix` | 게이트 있고 운영값 미확인 | 부록 격리 |
| `staged-only` | 미배포 | `appendix` | stg만, prod 미도달 | 부록 격리 |
| `data-dependent-unknown` | 데이터 의존 | `appendix` | DB 데이터 의존 | 구조만 서술 + 한계 명시 |
| `dead-code-candidate` | 사장 후보 | `deprecated` | 참조 0 / 도달 불가 | `99-deprecated/` |
| `deprecated-confirmed` | 폐기 확정 | `deprecated` | 삭제·revert 확정 | `99-deprecated/` |

`배치` 는 **이 표가 정본**이며 `docs/_meta/template-config.yaml` 의 `statuses[].placement` 가 그
전사다. lint 규칙 `placement` 가 `policy-doc@1` 문서에서 그 배치를 강제한다 — `appendix` 정책이
본문 절(`## 정책 (기획)`)에 있거나 `body` 정책이 `## 부록` 에 있으면 위반이다. 우산 제약 #2
(*"현행 유효성이 판정되지 않은 정책은 본문에 넣지 않는다"*)를 사람 준수에서 기계 검사로 옮긴
자리다. `라벨` 은 뷰어가 쓸 짧은 표시명이고 역시 이 표가 정본이다.

**`data-dependent-unknown` 하나만 `문서 처리` 열에서 배치가 문자 그대로 유도되지 않는다.**
그 셀은 *"구조만 서술 + 한계 명시"* 라 본문/부록을 직접 말하지 않는데, 제약 #2 가 묻는 것은
*현행 유효성이 판정되었는가* 이고 이 status 는 판정되지 않았다 — 형식이 이미 `unknown_reason`
을 요구하는 것이 그 선언이다. *"구조만 서술 + 한계 명시"* 는 **무엇을** 쓰는지이지 **어디에**
두는지가 아니며 그 지시는 부록 안에서도 그대로 성립한다. 결정적인 것은 규칙이 양방향이라는
점이다 — `body` 로 적으면 더 안전한 부록 배치가 lint 위반이 되어, 제약 #2 의 기계화가
막으려던 바로 그것을 강제하게 된다.

**`docs/00-index.md` §상태 범례 사본은 2열 그대로이며 `라벨`·`배치` 를 전사하지 않는다.**
그 사본은 뒤 두 열(`판정` · `문서 처리`)을 ` — ` 로 이어 붙인 형태이고, 배치 열을 인덱스로
내리면 사본이 2열을 유지할 수 없어 `run_index_gate` 검사 3 의 결합 규칙이 무너진다. 배치를
읽어야 하는 소비자는 인덱스가 아니라 config 를 읽는다.

**폐기 판정 5중 필터** (순서대로 적용):
1. 활성화 — env 게이트 기본값이 off인가
2. 존재 — HEAD 코드에 guard/constant/컬럼이 있는가
3. 삭제 이력 — `--diff-filter=D` 대상인가 (admin `Admin/Point` 등)
4. revert — revert 후 reapply 안 됐는가 (server 10 / site 10건)
5. 도달성 — prod 배포에 포함됐는가 + 참조 카운트 > 0

---

## 4. 확정 docs 구조

순서 원칙: **선행 개념 없이 읽히는 문서가 없도록.** 번호가 곧 읽는 순서다.

```
docs/
  00-index.md                          읽는 순서 · 상태 범례 · 폐기 목록 링크

  01-foundation/
    01-glossary.md                     용어 · actor · 서비스 경계
    02-identifier-namespaces.md        코드 사전 — namespace 5분할 + 계층(ANS003A/B)
    03-error-codes.md                  E0001~E05xx 계층 규칙

  02-access/
    01-authn.md                        인증 (server 74 / site 120 커밋 언급 최다)
    02-authz-roles.md                  역할 — @Roles 136개소 매트릭스
    03-authz-capabilities.md           agcRole 2종 + orgCanWrite + corpAnalysisViewLevel
    04-authz-db-menu.md                DB 메뉴 권한 — data-dependent-unknown 명시
    05-admin-view-token.md             관리자 열람 단명토큰 (IDOR 차단)

  03-contracts/
    01-input-validation.md             class-validator 3,047회
    02-api-response.md                 Swagger 3,213회 + interceptor
    03-data-model.md                   153테이블 · default 299 · notNull 896
    04-state-transitions.md            ANS/ODD/SUB/PAY 상태 전이
    05-external-client-api.md          고객사(client) 대외 API 계약

  04-limits/
    01-amounts-and-quotas.md           MAX_MONEY_* 등 금액 상한
    02-ttl-and-timeouts.md             *_TTL_MS · AUTH_TIMEOUT · VENDOR_TIMEOUTS
    03-rate-limits-and-batch.md        RATE_LIMIT_CERTIFY_PER_MIN · BATCH_SIZE
    04-feature-gates.md                env 정책 게이트 — known_gates 전건. status 는 근거대로

  05-schedules/
    01-retention.md                    delete-analysis/treat/fc/invoice-files
    02-billing-cycles.md               regular-subscription · settlement-* · expired-money
    03-monitoring.md                   analysis-stall · alimtalk-status · fax-status
    04-runtime-topology.md             CI RUN_MODE · SCHEDULER_TYPE 주입

  06-vendors/
    01-treat-providers.md              codef · coocon · hyphen · octover · inhouse
    02-failure-semantics.md            성공/자료없음/실패 의미 벤더별 차이
    03-payments.md                     toss · smartro
    04-messaging.md                    aligo(알림톡) · FCM

  07-legal/
    01-terms-and-agreements.md         약관 버전 · 기업별 문구 · 재동의
    02-privacy-and-masking.md          개인정보 · 마스킹 · 수탁업체
    03-audit.md                        감사 로그

  10-server/                           modules 40개 → 8개 도메인 문서
    01-analysis.md  02-treat.md  03-subscription-order.md  04-settlement-money.md
    05-agency-corp.md  06-communication.md  07-content.md  08-b2c-insurance.md

  20-site/                             features 26 / pages 19
    00-ia.md                           흐름이 지나는 화면 · 그 이탈 대상
    01-analysis-flow.md  02-alimtalk-certify.md  03-subscription-payment.md
    04-mypage-account.md  05-content-board.md

  30-admin/                            actor 3분할이 이미 존재
    00-ia.md                           흐름이 지나는 화면 · 그 이탈 대상
    01-admin-console.md  02-corp-console.md  03-sales-console.md
    04-authority-management.md  05-treat-monitoring.md

  90-history/
    00-origin.md                       조직·의사결정 서사 (등급 U 전용)
    01-timeline.md                     prod 릴리스 연대기
    02-epics.md                        feat/* 8개 에픽 서사
    03-divergence.md                   prod-only / stg-only 현황
    04-ops-intent.md                   운영 문서 판독 의도·경위 (등급 O 전용)

  99-deprecated/
    01-removed-features.md             Admin/Point 등 + 삭제 커밋 SHA
    02-reverted-policies.md            revert 후 미복원 20건

  _meta/                               §5 산출물 (사람이 읽는 문서 아님)
```

**모바일**: 별도 디렉터리 없음. 해당 정책 문서마다 `## 플랫폼 차이 (웹 / 웹뷰)` 절 — 브리지 위임(PDF 다운로드 등), FCM 푸시, 앱 최소버전.

### 읽기 축 2 — 흐름 진입

**위 트리는 소유 축이다** — 주제별로 누가 쓰고 누가 고치는가를 정하며, 번호가 곧 읽는 순서다.
**읽기 축은 하나 더 있다** — 흐름(`flow-doc@1`) → 화면(`ia-doc@1`) → 정책(`policy-doc@1`) 세 단으로,
같은 문서 집합을 사용자 여정 순서로 연다. 소유 축과 겹치지 않으므로 **파일을 옮기지 않는다** —
진입점 하나가 더 생길 뿐이며 사본의 자리는 `docs/00-index.md` §흐름으로 읽기 다.
**각 단에서 나가는 자리는 프론트매터와 계약된 표에서 계산하고 사람이 열거하지 않는다** —
흐름은 `실패·이탈` 표의 `결과` 열과 `refs`, 화면은 `surfaces[].exits` 로 **다음 단**을 가리킨다.
**정책은 마지막 단이라 다음 단이 없다** — `policies[].anchors` 는 `<repo>/<경로>#<심볼>` 로 코드에,
`policies[].supersedes` 는 폐기 문서(시간 축)에 닿는다. 둘 다 이 세 단 밖으로 나가는 자리다.
손으로 적은 목록은 문서가 늘 때마다 동기화 지점을 하나씩 더 만든다.

### 모든 정책 문서 공통 형식

> **⚠ 참고로 강등됨 (2026-08-04).** 아래 블록은 **정본이 아니다.** 문서 형식 정본은
> `.claude/templates/doc-kit/` (`policy-doc.template.md` · `ia-doc.template.md` · `lint-docs.mjs`) 이다.
> 아래 블록은 두 가지 점에서 현행과 다르다 — (1) `## 정책` 단일 절이지만 현행은 우산 제약 #1 에 따라
> `## 정책 (기획)` + `## 강제 지점 (개발)` 2절이다, (2) 문서당 `status` 1개이지만 현행은 정책 블록마다
> `status` 를 갖는다. **소비 문서를 이 블록에 맞춰 쓰지 않는다.**
> 이 블록을 삭제하지 않고 남기는 이유는 3라운드 검증 산출물의 이력이기 때문이다.
> `05-final-spec.md` 의 다른 절(§2 소스 등급 · §3 status 모델 · §5 산출 순서)은 **여전히 정본이며**,
> doc-kit 의 `template-config.yaml` 이 그 값들을 그대로 참조한다.

```markdown
---
id: <문서 고유 id>
status: active-prod | conditional-enabled | ... (§3)
depends_on_data: true | false
anchors:
  - insuniverse-server/apps/api/src/modules/.../x.guard.ts#ClassName
  - insuniverse-server/apps/api/src/.../x.spec.ts
evidence: [S-test, S-roles, A-comment, ...]     # 근거 등급
last_verified: <SHA>
---

## 정책
## 적용 범위 (actor × 서비스)
## 예외·폴백
## 플랫폼 차이 (웹 / 웹뷰)      ← 해당 시에만
## 연혁                          릴리스 | 날짜 | SHA | 변경 요약
```

---

## 5. 산출물 생성 순서

```
0단계  _meta/extraction-manifest.yaml               ← 이것부터. §1
       + 규약 검증 테스트 (같은 입력 → 같은 수치 재현)

1단계  사실 인덱스 (병렬 생성 가능)
       commits.jsonl ×3          comments.jsonl ×3 (5단계 태그)
       codes/{error,menu,domain_status,vendor}.yaml
       constants.yaml            env-gates.yaml
       api-contracts.jsonl       schema-constraints.jsonl
       tests.jsonl               schedules.yaml
       vendor-behaviors.yaml     legal-surfaces.yaml
       releases.yaml             cross-repo-days.csv
       policy-anchors.yaml       dead-candidates.yaml

2단계  policy-candidates.jsonl   ← 위 전부를 정책 후보 단위로 통합
       각 후보에 §3 status 부여

3단계  다관점 추출 (증거원 분할 — 같은 질문 반복 금지)
       Code-truth   HEAD 코드 + 테스트 + 스키마만          (커밋 열람 금지)
       History      commits.jsonl Era B
       Archaeology  commits.jsonl Era A
       Release      releases.yaml (prod / prod-only / stg-only)
       Negative     dead-candidates.yaml + revert 20건     (반박 담당)

4단계  대조
       Code-truth ∩ History     → confirmed
       Code-truth 단독          → undocumented (표시하되 수록)
       History 단독             → 폐기 의심 → Negative 회부
       Negative 반박            → 99-deprecated/ 강제 이동

5단계  문서 생성 (docs 번호 순)

종료 조건  2라운드 연속 신규 정책 0건 + 관점 간 불일치 0건
반복 비교  policy-candidates.jsonl 을 diff (문서 본문 아님)
```

---

## 6. 확정된 한계 — 문서에 반드시 명시할 것

정직하게 남긴다. 이걸 숨기면 문서가 틀린 확신을 준다.

### 6-1. env — site stg만 수령, **정책 게이트는 미해결**

수령한 `insuniverse-site/front/.env`(stg)에는 §1 `known_gates` 의 정책 게이트가 **하나도 이름으로 등장하지 않는다.** 클라이언트 변수뿐이며, 그중 두 개는 정책성이라 2026-08-05 에 본문으로 승격됐다:

```
REACT_APP_BACKEND_HOST=https://s-api.insuniverse.com   (stg)
REACT_APP_SMARTRO_MODE=STG                              ← 결제 벤더 모드 (정책성)
REACT_APP_TOSS_CLIENT_KEY=test_ck_...                   ← 테스트 키 (정책성)
REACT_APP_BRIDGE_NAME=insuniverse                       ← 웹뷰 브리지 (§0 모바일 확정 근거)
REACT_APP_BASE_URL / IMG_URL / DREAM_SECURITY_JS_URL    (접속정보)
```

**`known_gates` 의 나머지는 server 측이고 `insuniverse-server` 에 `.env` 가 없다.** 위 인용의 `REACT_APP_SMARTRO_MODE`·`REACT_APP_TOSS_CLIENT_KEY` 는 site 빌드타임 변수라 그 서술의 예외이며, stg 값이 이미 위에 실려 있다 — 이 둘은 미해결분에 들지 않는다.

→ 확정: 값이 확인되지 않은 `known_gates` 정책은 **`conditional-unknown`** 으로 `04-limits/04-feature-gates.md` 에 격리한다. 분모는 §1 `known_gates` 이고, 스캔 우주 안에서 실재가 확인된 집합은 `docs/_meta/index/env-gates.yaml` 이 든다 — `RUN_MODE`·`SCHEDULER_TYPE` 은 **결손이 아니라 스캔 우주 밖**이라 이 격리 대상이 아니다. 코드상 기본값(`ADMIN_VIEW_TOKEN_ENFORCE` 미설정=경고만, `RATE_LIMIT_CERTIFY_PER_MIN=0`=무제한)은 기록하되 **"stg/prod 실제 활성 여부 미확인"** 을 명시한다.
server stg `.env`(또는 게이트 키의 on/off 목록만)를 추가로 받으면 그 격리분이 본문으로 승격된다.

### 6-2. 권한 DB — 미제공 확정

> **2026-08-05 범위 정정.** 아래 원문은 유지하되, `data-dependent-unknown` 의 적용 범위가 실제보다
> 넓게 잡혀 있었다. 실측 결과 **미확보는 admin actor 의 그룹별 배분(`tn_menu_auth_map` 행)뿐**이다.
>
> | 대상 | 원 판정 | 정정 후 |
> |---|---|---|
> | `tn_menu` 구조 (1단계 23 · 2단계 79) | unknown | **확정** — DB 덤프 확보 |
> | sales 메뉴 트리 (`salesMenus` 14코드) | 하드코딩이라 확인 가능 (원문도 인정) | **확정** |
> | corp 메뉴 트리 (`corpMenus` 5코드) | 언급 없음 | **확정** — 원문의 "영업대리점만" 은 사실 오류. corp 도 하드코딩이다 |
> | admin 권한 그룹별 배분 | unknown | **unknown 유지** ← 유일한 미확보 |
>
> 메뉴 트리 소스는 actor 마다 다르다 — admin 만 DB 조회이고 sales·corp 는 프론트 상수다.
> 또한 메뉴 권한은 사이드바 렌더링과 로그인 직후 진입 화면만 정하며 **서버 API 를 막지 않는다.**
> 근거·앵커·수치: `.claude/meta/06-access-model-findings.md`

> **2026-08-12 전제 정정.** 절 제목의 *"미제공 확정"* 은 **원문 보존**이며 그 전제 자체는 열렸다.
> 사용자 확정으로 권한 DB 스냅샷은 **제공 가능**하다 — 바뀐 것은 *"영구히 받을 수 없다"* 가
> *"수령 대기"* 라는 것이다. 이 저장소 시점에 스냅샷은 **아직 수령되지 않았다.**
>
> | 사실 | 값 |
> |---|---|
> | 제공 가능 여부 | 확정 — 사용자가 제공 가능함을 확인했다 |
> | 스냅샷 수령 | **아직 아니다** — `snapshot_state=pending` |
> | 수령 경로 | `receipt_route=ops-40-db-snapshot` (`ops-doc-reconcile` 우산) |
>
> 그래서 `data-dependent-unknown` 판정은 **그대로 유지한다** — 없는 데이터를 있다고 적지 않는다.
> 상태가 뒤집히려면 위 두 표식이 바뀌어야 하며, 게이트는 산문이 아니라 **그 필드만** 읽는다.
> 산문은 자유롭게 다시 쓸 수 있고 표식이 계약이다. 같은 두 표식이 §0 요약표의 권한 DB 행에도
> 있으며, 두 자리가 어긋나면 어느 쪽이 정본인지 사라지므로 게이트가 둘을 대조한다.
>
> **절 제목은 바꾸지 않는다** — 이 절을 인용하는 자리가 소유 밖에 5곳 있고, 제목을 고치면 그
> 인용이 전부 죽는다. 소유 밖 5자리의 정정은 각 소유자 몫이며 이 PRD 는 사실로 기록만 한다.

`tn_auth` / `tn_menu` / `tn_menu_auth_map` 스냅샷 없음. `02-access/04-authz-db-menu.md` 는 **구조·연결관계만 서술**하고 실제 권한 배분은 `data-dependent-unknown` 으로 남긴다. (영업대리점 메뉴만 하드코딩이라 코드로 확인 가능)

### 6-3. 정확도가 근거에 종속되는 항목

- ~~주석 정책성 비율 25~40%는 **60줄 표본 추정치** — 전수 분류 후 확정~~
  → **전수 실측 완료 (2026-08-11): 12,688 블록 중 `policy_candidate` 1,370 = 10.8%.** 추정 하한에도
  못 미친다. 원인은 분류 키워드가 규범문 전용이라는 것이며 분석은 `08-operational-blindspot-causes.md`
  원인 3, 개선안은 `09-operational-knowledge-remediation.md` 개선 3
- 테스트의 CI 실행·skip 여부 미확인 → `tests.jsonl` 생성 시 확인
- E코드 distinct 개수·계층 규칙 미확정
- `Admin/Point` 삭제가 폐기인지 이동인지 미확정

### 6-4. `planning/` 재판정 — 등급 L(legacy 참고) · 2026-08-11 확정

**정정 사유.** 구 규약은 `planning/` 을 등급 X(참조 금지)로 두고 근거를 "사용자가 의도 삭제
(admin 34 / site 8 파일)"로 적었다. 실측하면 **34·8 은 삭제 파일 수가 아니라 HEAD 추적 파일 수**다.

```
insuniverse-admin  stg  git ls-tree -r --name-only HEAD -- planning | wc -l  → 34
insuniverse-site   stg  동                                                   → 8
양쪽 git status --porcelain planning → 빈 출력 (clean). 삭제 커밋 없음.
```

**재판정.** 등급 X → **L(legacy 참고)**. 사용자 확정(2026-08-11): *"legacy 파일임을 인지하고
최소한의 규격만 판단"*.

| | 허용 | 금지 |
|---|---|---|
| 무엇에 쓰나 | 용어의 **존재** 확인 · 분류 **축**과 구조 판단 · `onboarding-questions.yaml` 초안 입력 | 정책 **값**(금액·비율·기간·상한) 인용 · 문장 전재 |
| 산출물 반영 | 코드에서 **재확인된 사실만** 본문에. 근거는 코드 앵커로 적는다 | `evidence:` 에 `L-planning` 단독 표기 — 이 값은 존재하지 않는다 |
| 불일치 시 | 코드를 정본으로 삼고 차이는 `findings/` 에 기록 | planning 쪽으로 코드 서술을 맞추는 것 |

**자동 추출 제외는 그대로 유지한다.** `extraction-manifest.yaml` 의 `exclude_pathspec` ·
`exclude_glob` 에서 `planning/` 을 빼지 않는다 — legacy 주석·커밋이 `policy-candidates.*.jsonl` 에
섞이면 status 판정이 오염된다. **재판정이 바꾸는 것은 열람 권한이지 추출 대상이 아니다.**
따라서 manifest `version` 은 올리지 않으며 인덱스 재생성도 유발하지 않는다(주석 문구만 정정).

제약 #3(근거 2중화)은 그대로다. L 은 근거 2개 중 하나로 **세지 않는다** — 값을 못 쓰므로 애초에
근거가 되지 못한다. 대조·발견 도구다.

**대조 근거**: `07-domain-knowledge-gap.md` · `08-operational-blindspot-causes.md` §원인 1 ·
`09-operational-knowledge-remediation.md` 개선 1.

---

## 7. 라운드 4 이월 과제

1. 주석 5단계 전수 분류 → 정책 후보 실수치 확정
2. revert 20건 중 미복원분 확정
3. `Admin/Point` 폐기/이동 판별
4. site `_site/ → front/` 경로 매핑 테이블
5. E코드 계층 규칙 확정
6. 코드 서브레벨(ANS003A/B 등) 전수
7. 3-repo 동시 커밋일 조인 표본 검증
8. `screen_codes` namespace 정규식 확정

---

## 부록. 3라운드 정정 이력

| 라운드 | 정정 내용 |
|---|---|
| 2 → 1 | 스코프는 2026년 전용 → 파일경로가 1차 축 |
| 2 → 1 | drizzle 번호 = 척추 **기각** (중복 3쌍, journal 1건, dialect 불일치) |
| 2 → 1 | prod = 릴리스 경계 **부분기각** (양방향 발산 확인) |
| 2 → 1 | 코드 존재 = 현행 **기각** (env 게이트) |
| 2 → 1 | 주석 9,462줄 · 권한 3중 표면 · 모바일 축 **신규 발견** |
| 3 → 2 | 기업 역할 4종 → **2종** (CORP_ANALYSIS·CORP_FORBIDDEN은 역할 아님) ★ |
| 3 → 2 | 주석 전체 S+ → **블록 단위 분류 후 25~40%만** |
| 3 → 2 | env 게이트 6 → **11종**, spec 32 → **37+16**, 스키마·Swagger·벤더·약관·CI 신규 |
| 3 → 2 | 단일 codes.yaml → **namespace 5분할** |
| 3 → 2 | **extraction-manifest 선행 고정** 신규 (양쪽 모두 놓침) ★ |
| 3 검증 | Codex "site spec 0개" **틀림** (16개 실재) · "prod 격차 불일치" **오독** |
| 3 검증 | ANS/ODD가 실제 3-repo 공유 데이터 계약임 **입증** |
