# findings · 20-foundation-access

> **형식**: `docs/_meta/findings/README.md` §기록 형식 — 발견 하나가 `## ` 표제 하나이고 그 아래
> 필수 6항목을 `### ` 표제로 순서 그대로 둔다. **판단과 수정 제안은 적지 않는다 — 사실만.**
>
> **`발견 일시` 와 `상태` 는 열거 필드다.** 값 하나만 적는다. 단서·유보는 `관찰 사실` 이나
> `심각도` 로 간다 — 그 두 칸에 붙이면 게이트가 정확 일치·1행 규약으로 거부한다.
>
> **발견 사이에 `---` 구분자를 두지 않는다.** 구분자는 `## ` 표제다. 수평선을 넣으면 그것이
> 열거 필드의 두 번째 비공백 행으로 잡혀 앞 발견까지 함께 떨어진다.
>
> **조사 기준점**: 이 저장소는 커밋 `323dcfc`, 서브모듈은 `docs/_meta/extraction-manifest.yaml`
> `repos[].head` 3종(`4d798f55` · `767d1e44` · `fb001e1d`). 아래 근거 앵커의 행 번호는 그 기준이다.

## 코드 인덱스의 조사 기준점이 manifest 동결 SHA 보다 앞서 있다

### 발견 일시

2026-08-25

### 관찰 사실

`docs/_meta/index/codes/` 의 여섯 yaml 은 헤더 `source_sha` 에 저장소 3종의 SHA 를 싣는다. 여섯 파일이
같은 값을 쓰며 그 값은 `c7597fc7` · `b9a90a42` · `7df733a5` 다. 같은 세 저장소에 대해
`docs/_meta/extraction-manifest.yaml` `repos[].head` 가 동결한 값은 `4d798f55` · `767d1e44` · `fb001e1d` 이며
서로 다르다. `git merge-base --is-ancestor` 를 세 쌍에 각각 돌리면 3/3 이 참이다 — manifest 값이
인덱스 값의 조상이다.

그 상태에서 `docs/_meta/verify/verify-code-index.mjs` 는 헤더가 문서화한 호출 형태
(`--repos-root ../..`)와 서브모듈이 manifest head 로 체크아웃된 워크트리 루트 양쪽에서 exit 1 이다.
두 실행의 진단 건수는 서로 다르다 — 전자는 `defined_in` 5건 · `consumed_by` 566건, 후자는
`defined_in` 7건 · `consumed_by` 739건이다.

앵커를 manifest 가 동결한 SHA 기준으로 다시 해석하면 필드별로 아래와 같다. 해석은 검증기와 같은
세 층으로 본다 — 그 SHA 에 파일이 있는가 · 행 번호가 파일 범위 안인가 · 그 행이 항목의 지역
식별자를 단어 경계로 담는가.

| 앵커 필드 | 동결 기준선에서 해석 | 전수 |
|---|---|---|
| `defined_in` | 611 | 618 |
| `consumed_by` | 3916 | 4655 |

같은 어긋남이 계수에도 나타난다. `docs/_meta/index/codes/screen.yaml` 헤더의 `route_count` 는 284 이고,
같은 축을 manifest `code_namespaces.screen_codes.route_count` 는 282 로 동결한다.

이 문서 창(`20-foundation-access` 마일스톤 2)의 산출물 두 건은 인덱스에서 앵커를 옮겨 오지 않고
동결 SHA 기준 소스에서 다시 유도했다.

### 재현 절차

이 저장소 루트에서, 서브모듈이 manifest `repos[].head` 로 체크아웃된 상태로 실행한다.

```bash
# 1) 두 기준점을 나란히 본다
grep -n 'source_sha' docs/_meta/index/codes/*.yaml
grep -n 'head:' docs/_meta/extraction-manifest.yaml

# 2) 조상 관계 — 세 쌍 모두 종료코드 0 이면 manifest 값이 인덱스 값의 조상이다
git -C insuniverse-server merge-base --is-ancestor 4d798f55f021d93c889725e50f18cbce20272fd1 c7597fc7a05d3e5cdd29ac203a4afa244e8df14d; echo "server=$?"
git -C insuniverse-site   merge-base --is-ancestor 767d1e441637b5cba759acbe1f928a47f17397eb b9a90a42f4ee18aa364e03400465a2eba0ad0532; echo "site=$?"
git -C insuniverse-admin  merge-base --is-ancestor fb001e1d26d342130cf9a8dc8fe5e8a64e1da9c8 7df733a595bd35d7c3593a2fd68d364fadb8f74e; echo "admin=$?"

# 3) 검증기 두 호출 형태의 종료코드와 진단 건수
node docs/_meta/verify/verify-code-index.mjs > /tmp/vci.worktree.out 2>&1; echo "worktree=$?"
node docs/_meta/verify/verify-code-index.mjs --repos-root ../.. > /tmp/vci.reposroot.out 2>&1; echo "repos-root=$?"
grep -c 'defined_in: 앵커'  /tmp/vci.worktree.out  /tmp/vci.reposroot.out
grep -c 'consumed_by: 앵커' /tmp/vci.worktree.out  /tmp/vci.reposroot.out

# 4) 필드별 해석률 · route_count 두 값 — 마일스톤 2 하네스가 같은 수를 다시 유도한다
node .claude/plans/insuniverse-docs-20-foundation-access-m2.gates.mjs check10
```

`3)` 의 두 실행은 읽는 소스가 다르다 — 앞은 이 워크트리의 서브모듈(manifest head), 뒤는 상위
저장소의 서브모듈이며 그쪽 체크아웃 SHA 는 이 기준점과 무관하게 움직인다.

### 근거 앵커

이 저장소 커밋 `323dcfc` 기준이다.

- 인덱스 헤더의 `source_sha` — `docs/_meta/index/codes/_unclassified.yaml:16` · `docs/_meta/index/codes/domain_status.yaml:21` · `docs/_meta/index/codes/error.yaml:20` · `docs/_meta/index/codes/menu_db.yaml:16` · `docs/_meta/index/codes/menu_hardcoded.yaml:13` · `docs/_meta/index/codes/screen.yaml:15` · `docs/_meta/index/codes/vendor.yaml:12`
- manifest 의 `repos[].head` — `docs/_meta/extraction-manifest.yaml:62` (server) · `docs/_meta/extraction-manifest.yaml:67` (site) · `docs/_meta/extraction-manifest.yaml:72` (admin)
- 계수가 갈리는 자리 — `docs/_meta/index/codes/screen.yaml:17` (`route_count`) · `docs/_meta/extraction-manifest.yaml:306` (`route_count`)
- 검증기 — `docs/_meta/verify/verify-code-index.mjs:34` (헤더가 문서화한 호출 형태) · `docs/_meta/verify/verify-code-index.mjs:546` (앵커 판정 세 층)
- 인덱스 자신이 같은 축을 이미 기록한 자리 — `docs/_meta/index/codes/domain_status.yaml:9`

### 심각도

오동작 축이다. 노출도 데이터 손실도 관측되지 않았다.

범위는 코드 인덱스를 앵커 원본으로 쓰는 소비자에 한정된다. 인덱스의 `consumed_by` 앵커 4,655 건
중 739 건이 manifest 동결 SHA 에서 세 층 판정을 통과하지 못하므로, 그 앵커를 그대로 옮겨 적은
산출물은 동결 기준선에서 재현되지 않는다. `defined_in` 축은 618 건 중 7 건이다.

`docs/_meta/verify/verify.sh --check` 는 이 어긋남과 무관하게 통과한다 — 그 게이트가
`verify-code-index.mjs` 를 부르지 않는다(2026-08-25 관측, exit 0).

미검증 범위를 적는다. 인덱스의 `serialized` · `persisted` · `active_reference_count` 세 필드는 이
관측의 대상이 아니다. 두 기준점 사이에 실제로 무엇이 바뀌었는지도 이 관측이 세지 않았다.

### 상태

미판정

## 개발 편의 우회 2종의 게이트 조건과 운영 배포 환경변수 값에 대한 저장소 안 진술이 서로 어긋난다

### 발견 일시

2026-08-26

### 관찰 사실

`insuniverse-server` 의 인증 축에는 토큰 검증 실패를 대체 주체로 통과시키는 자리가 두 갈래 있고, 두
갈래의 게이트 조건이 같은 환경변수 `NODE_ENV` 를 서로 다른 값으로 본다.

| 갈래 | 게이트 조건 | 조건이 적힌 자리 |
|---|---|---|
| 발신 주소 화이트리스트 | `NODE_ENV` 가 `'production'` **이 아닐 때** | `apps/api/src/modules/auth/user/jwt.guard.ts:44` |
| API 문서 화면 요청 | `NODE_ENV` 가 `'dev'` **일 때** | `apps/api/src/helpers/request.helper.ts:20` |

같은 저장소의 세 번째 자리는 **운영 배포의 `NODE_ENV` 가 `'dev'` 라고 적는다.** 인증 쿠키 옵션
헬퍼의 주석이며, 그 주석은 그 사실을 이유로 그 자리에서 `NODE_ENV` 로 분기하지 않는다고 함께
적는다(`apps/api/src/helpers/cookie.helper.ts:12`).

세 진술을 나란히 놓으면 관측되는 것은 다음 둘이다.

- 두 갈래의 게이트 조건은 `NODE_ENV` 의 한 값에 대해 **동시에 참이 될 수 있다** — 값이 `'dev'` 이면
  첫 갈래의 `'production'` 이 아니라는 조건과 둘째 갈래의 `'dev'` 라는 조건이 함께 성립한다.
- 세 번째 자리의 진술이 참이면 그 동시 성립이 **운영 배포에서** 일어난다.

두 번째 갈래의 통과 지점은 두 인증 표면 모두에 있다 —
`apps/api/src/modules/auth/user/jwt.guard.ts:68` 과
`apps/api/src/modules/auth/admin/jwt.guard.ts:42` 이며, 두 자리 모두 같은 판정 헬퍼를 부른다.

세 번째 자리의 진술이 지금도 참인지는 이 관측이 확인하지 않았다. 확인하려면 배포 환경변수 값을
읽어야 하고 그것은 이 저장소 밖이다. 첫 갈래에 환경 조건이 붙은 시점은 커밋 `c3760627`
(2026-06-30) 이며, 세 번째 자리의 주석이 언제 적혔는지는 이 관측이 세지 않았다.

### 재현 절차

이 저장소 루트에서, 서브모듈이 `docs/_meta/extraction-manifest.yaml` `repos[].head` 로 체크아웃된
상태로 실행한다. 동결 SHA 는 `4d798f55` 다.

```bash
# 1) 인증 축의 NODE_ENV 게이트 조건 두 갈래
git -C insuniverse-server grep -n -F "NODE_ENV !== 'production'" 4d798f55f021d93c889725e50f18cbce20272fd1 -- apps/api/src/modules/auth
git -C insuniverse-server grep -n -F "NODE_ENV === 'dev'" 4d798f55f021d93c889725e50f18cbce20272fd1 -- apps/api/src/helpers/request.helper.ts

# 2) 운영 배포의 그 변수 값을 적은 자리
git -C insuniverse-server grep -n -F "NODE_ENV" 4d798f55f021d93c889725e50f18cbce20272fd1 -- apps/api/src/helpers/cookie.helper.ts

# 3) 두 번째 갈래의 통과 지점 — 두 인증 표면 모두
git -C insuniverse-server grep -n -F "isDevSwaggerRequest" 4d798f55f021d93c889725e50f18cbce20272fd1 -- apps/api/src/modules/auth

# 4) 마일스톤 3 하네스가 같은 세 자리를 다시 유도한다
node .claude/plans/insuniverse-docs-20-foundation-access-m3.gates.mjs check8 check11
```

`1)` 과 `2)` 는 각각 1행을 낸다. `3)` 은 들여오기 2행과 호출 2행을 낸다.

### 근거 앵커

동결 SHA `4d798f55` 기준이다.

- 발신 주소 화이트리스트 갈래의 게이트 조건 — `insuniverse-server/apps/api/src/modules/auth/user/jwt.guard.ts:44`
- API 문서 화면 갈래의 게이트 조건 — `insuniverse-server/apps/api/src/helpers/request.helper.ts:20`
- 운영 배포의 `NODE_ENV` 값을 적은 주석 — `insuniverse-server/apps/api/src/helpers/cookie.helper.ts:12`

### 심각도

노출 축이다. 손실도 오동작도 관측되지 않았다.

범위는 두 갈래의 통과 지점이 걸린 요청 경로에 한정된다 — 첫 갈래는 이용자 표면 하나, 둘째 갈래는
이용자 표면과 관리자 콘솔 표면 둘이다. 두 갈래 모두 토큰 검증이 실패한 요청에만 관여하며, 검증에
성공한 요청의 처리는 이 관측의 대상이 아니다.

미검증 범위를 적는다. 배포 환경변수의 현재 값은 읽지 않았다. 두 갈래가 요구하는 나머지 조건 — 첫
갈래의 발신 주소 목록 일치, 둘째 갈래의 참조 출처 문자열과 발신 주소 일치 — 이 실제 요청에서
성립하는지도 이 관측이 확인하지 않았다. 세 번째 자리의 주석이 적힌 시점과 그 뒤 배포 설정이
바뀌었는지도 세지 않았다.

### 상태

미판정

## 역할 선언 개소를 말하는 저장소 안 진술 세 자리가 서로 다른 수를 적는다

### 발견 일시

2026-08-26

### 관찰 사실

`insuniverse-server` 의 역할 선언 개소를 말하는 진술이 이 저장소 안에 세 자리 있고, 세 자리가
서로 다른 수를 적는다.

| 자리 | 성격 | 적힌 수 |
|---|---|---|
| `docs/00-index.md:83` | 문서 트리 행 — `00-protocol`(archived) 소유의 읽기 전용 공유 자산 | 136 |
| `docs/_meta/index/policy-anchors.yaml:40` | 생성 인덱스 머리말 — `11-index-code`(archived) 산출물 | 137 |
| `docs/_meta/verify/golden.txt:32` | 동결 golden 키 `roles.insuniverse-server.count` | 138 |

세 자리의 기준점은 서로 다르다. 생성 인덱스는 자기 헤더 `source_sha` 가 가리키는 SHA 를 기준으로
하고, golden 키는 `docs/_meta/extraction-manifest.yaml` `repos[].head` 의 동결 SHA `4d798f55` 를
기준으로 한다. 문서 트리 행이 어느 기준을 옮긴 것인지는 그 행이 적지 않는다.

golden 키의 값은 **토큰 출현 수**다. 그 값을 만드는 추출기가 `@Roles` 를 행 단위 문자열 일치로
세며(`docs/_meta/verify/extract-disputed-counts.mjs:792`), 그래서 주석 안의 언급도 한 건으로
포함된다. 같은 동결 SHA 에서 인자를 실제로 받는 `@Roles(...)` 선언만 세면 137 이고, 토큰 출현은
138 이다 — 차이 1건이 주석 안의 언급이다.

세 자리 중 어느 것이 옳은가, 왜 갈렸는가, 무엇을 고쳐야 하는가는 이 관측이 적지 않는다.

### 재현 절차

이 저장소 루트에서, 서브모듈이 `docs/_meta/extraction-manifest.yaml` `repos[].head` 로 체크아웃된
상태로 실행한다. 동결 SHA 는 `4d798f55` 다.

```bash
# 1) 세 자리가 적은 수
sed -n '83p' docs/00-index.md
sed -n '40p' docs/_meta/index/policy-anchors.yaml
sed -n '32p' docs/_meta/verify/golden.txt

# 2) 생성 인덱스가 쓰는 기준점 — golden 이 쓰는 동결 SHA 와 같은지 본다
grep -n 'source_sha' docs/_meta/index/policy-anchors.yaml | head -3
grep -n 'head:' docs/_meta/extraction-manifest.yaml

# 3) golden 값이 토큰 출현 수라는 것 — 세는 자리
sed -n '790,796p' docs/_meta/verify/extract-disputed-counts.mjs

# 4) 마일스톤 4 하네스가 같은 세 자리를 다시 유도한다
node .claude/plans/insuniverse-docs-20-foundation-access-m4.gates.mjs check15
```

`1)` 은 각각 1행을 낸다. `4)` 는 세 자리가 해석되고 세 값이 서로 다름을 값으로 찍는다.

### 근거 앵커

이 저장소 커밋 `edbf629` 기준이다. 세 자리는 전부 저장소 안이며 서브모듈 앵커가 아니다.

- 문서 트리 행 — `docs/00-index.md:83`
- 생성 인덱스 머리말 — `docs/_meta/index/policy-anchors.yaml:40`
- 동결 golden 키 — `docs/_meta/verify/golden.txt:32`

### 심각도

문서 축이다. 실행 경로에 관여하지 않으며 손실도 오동작도 관측되지 않았다.

범위는 세 자리의 진술에 한정된다. 세 자리 전부 읽기 전용 공유 자산이거나 생성 산출물이고, 이 창은
그중 어느 것도 고치지 않았다. 산출 문서 `docs/02-access/02-authz-roles.md` 는 세 값 중 어느 것도
인용하지 않고 집합만 여는 쪽을 택했다.

미검증 범위를 적는다. 생성 인덱스가 자기 헤더 SHA 기준으로 재실행됐을 때 어떤 수가 나오는지는
이 관측이 확인하지 않았다. 문서 트리 행이 어느 기준점을 옮긴 것인지도 그 행이 적지 않아 세지
못했다. 세 자리 밖에 같은 축을 말하는 네 번째 자리가 있는지는 이 저장소의 `docs/**` 와
`.claude/meta/**` 범위에서만 훑었다.

### 상태

미판정

## 기업 메뉴 축의 실효 집합을 말하는 저장소 안 진술 두 자리가 코드와 어긋난다

### 발견 일시

2026-08-27

### 관찰 사실

기업담당자에게 실제로 보이는 메뉴 코드 집합을 말하는 진술이 이 저장소 안에 두 자리 있고, 두 자리
모두 `insuniverse-admin` 동결 SHA `fb001e1d` 의 필터 함수와 어긋난다. 두 행은 서로 다른 축이다.

| 행 | 자리 | 성격 | 어긋나는 지점 |
|---|---|---|---|
| 1 | `docs/_meta/verify/extract-menu-codes.mjs:78` · `docs/_meta/extraction-manifest.yaml:356` | 커밋된 추출기와 그 값을 실은 추출 규약 동결본 | 실효 집합을 트리와 필터의 교집합으로만 산출한다 |
| 2 | `insuniverse-admin/src/features/authority/lib/corpRole.ts:43` | 조직 범위 주체에게 숨길 코드 목록 | 원소 하나가 트리에도 필터에도 없다 |

**행 1.** 추출기는 기업 축 실효 집합을 트리 상수와 필터 배열의 교집합으로 산출하고, 그 값이
동결본의 `menu_codes.hardcoded.corp.effective` 로 실려 있다. 그 산출에는 입력이 둘뿐이다.
같은 동결 SHA 의 프론트 필터 함수(`insuniverse-admin/src/features/authority/lib/corpRole.ts:75`)는
필터 배열 외에 **두 값을 더 받는다** — 기업 안 등급(`:77`)과 기업별 분석 화면 노출 정도(`:78`) 이며,
그 둘로 두 갈래 게이팅(`:81` · `:86`)을 돌려 집합을 줄인다. 그래서 그 두 값에 따라 브라우저가
그리는 집합이 추출값보다 작아지는 경우가 실재한다. 동결 SHA 실측으로, 최고 등급이 아니고 분석
화면 노출 정도가 요구 단계에 미치지 않는 주체에게 남는 집합은 원소 1개이며 추출값은 5개다.

**행 2.** 조직 범위 주체에게 숨길 코드 목록의 원소 중 하나(`CRP_MNY003`)가 기업 트리 상수
(`insuniverse-admin/src/features/authority/constants/menu.ts:190`)에도 필터 배열
(`:269`)에도 없다. 그 원소는 걷어낼 대상이 없어 아무것도 걷어내지 않는다. 나머지 원소 셋은
필터 배열 안에 있다.

두 행 중 어느 쪽이 옳은가, 왜 갈렸는가, 추출기와 목록 중 무엇을 고쳐야 하는가는 이 관측이
적지 않는다.

### 재현 절차

이 저장소 루트에서, 서브모듈이 `docs/_meta/extraction-manifest.yaml` `repos[].head` 로 체크아웃된
상태로 실행한다. `insuniverse-admin` 의 동결 SHA 는 `fb001e1d` 다.

```bash
# 1) 추출기가 실효 집합을 만드는 자리 — 입력이 트리와 필터 둘뿐이다
sed -n '75,79p' docs/_meta/verify/extract-menu-codes.mjs

# 2) 동결본이 그 값을 그대로 싣는 자리
sed -n '353,357p' docs/_meta/extraction-manifest.yaml

# 3) 프론트 필터 함수 — 추가 입력 둘과 게이팅 분기 둘
git -C insuniverse-admin show fb001e1d:src/features/authority/lib/corpRole.ts | sed -n '75,87p'

# 4) 숨김 목록의 원소와, 트리·필터가 실제로 담은 코드 집합
git -C insuniverse-admin show fb001e1d:src/features/authority/lib/corpRole.ts | sed -n '43,48p'
node docs/_meta/verify/extract-menu-codes.mjs --json

# 5) 마일스톤 5 하네스가 세 축을 함께 재유도한다
node .claude/plans/insuniverse-docs-20-foundation-access-m5.gates.mjs check19
```

`1)`~`4)` 는 각각 자리를 그대로 낸다. `4)` 의 두 번째 명령이 내는 `corp.tree` 와 `corp.filter`
어디에도 `CRP_MNY003` 이 없다. `5)` 는 세 축을 재유도해 값으로 찍는다 — 앞의 두 축은 하나라도 0 이면 exit 1 이고, 위
문단이 적은 두 계수(1 과 5)는 재유도값에서 문장을 다시 만들어 이 문서의 문면과 대조하므로
어긋나면 exit 1 이다. 계수는 이 문서가 스스로 단언하지 않고 하네스가 매 실행 다시 만든다.

### 근거 앵커

이 저장소 커밋 `3c08c27` 기준이며, 서브모듈 앵커는 `insuniverse-admin` 동결 SHA `fb001e1d` 기준이다.

- 추출기의 실효 집합 산출 — `docs/_meta/verify/extract-menu-codes.mjs:78`
- 동결본이 실은 값 — `docs/_meta/extraction-manifest.yaml:356`
- 프론트 필터 함수 — `insuniverse-admin/src/features/authority/lib/corpRole.ts:75`
- 추가 입력 둘 — `insuniverse-admin/src/features/authority/lib/corpRole.ts:77` · `insuniverse-admin/src/features/authority/lib/corpRole.ts:78`
- 게이팅 분기 둘 — `insuniverse-admin/src/features/authority/lib/corpRole.ts:81` · `insuniverse-admin/src/features/authority/lib/corpRole.ts:86`
- 숨김 목록 — `insuniverse-admin/src/features/authority/lib/corpRole.ts:43`
- 기업 트리 상수 · 필터 배열 — `insuniverse-admin/src/features/authority/constants/menu.ts:190` · `insuniverse-admin/src/features/authority/constants/menu.ts:269`

### 심각도

문서·추출 축이다. 실행 경로에 관여하지 않으며 손실도 오동작도 관측되지 않았다. 브라우저가 그리는
집합은 필터 함수가 정하므로 화면 동작은 이 갈림과 무관하다.

범위는 두 자리의 진술에 한정된다. 추출기와 동결본은 이 PRD 가 고칠 수 없는 자리다 — 추출기를
고치면 동결본의 `menu_hardcoded` 집합 해시가 움직여 추출 규약 변경이 되고, 그 변경은 이 규약을
읽는 모든 산출물을 무효화한다. 숨김 목록은 서브모듈 소스이며 코드 수정은 우산 PRD 범위 밖이다.
이 창의 산출 문서 `docs/02-access/04-authz-db-menu.md` 는 두 축 중 어느 계수도 인용하지 않고
자리만 여는 쪽을 택했다.

미검증 범위를 적는다. 두 게이팅 입력이 운영에서 실제로 어떤 값을 갖는지는 이 관측이 확인하지
않았다 — 어긋남은 코드 대 코드의 관측이며 런타임 대조가 아니다. 대상 없는 숨김 원소가 과거 어느
시점에 대상을 가졌는지도 확인하지 않았다. 두 자리 밖에 같은 축을 말하는 세 번째 자리가 있는지는
이 저장소의 `docs/**` 와 `.claude/meta/**` 범위에서만 훑었다.

### 상태

미판정
