---
template: policy-doc@1
doc_id: 01-terms-and-agreements
title: 약관과 동의 — 판 관리 부재와 그 결과
config: ../_meta/template-config.yaml
last_verified: 4d798f55
policies:
  - id: terms:version-column-absent
    status: active-prod
    evidence: [S-schema, S-contract]
    anchors:
      - insuniverse-server/drizzle/schema.ts:1947
      - insuniverse-server/drizzle/schema.ts:1961
  - id: terms:one-row-per-type
    status: staged-only
    evidence: [S-schema, S-contract]
    limitations:
      - 두 유일 제약을 더한 커밋 `c577fd29` (2026-07-16) 이 동결 stg 기준 운영 미도달이다. 따라서 이 제약은 운영에서 아직 강제되지 않으며, 같은 결과(이전 판이 행으로 남지 않음)의 사유로 쓸 수 없다. 운영의 사유는 갱신 경로의 형태이고 그것은 `terms:in-place-update` 가 진다
    anchors:
      - insuniverse-server/drizzle/schema.ts:1956
      - insuniverse-server/drizzle/schema.ts:1975
  - id: terms:in-place-update
    status: active-prod
    evidence: [S-guard, A-comment]
    anchors:
      - insuniverse-server/apps/api/src/modules/agency/terms/terms.repository.ts:131
      - insuniverse-server/apps/api/src/modules/agency/terms/terms.repository.ts:135
  - id: terms:agency-override
    status: data-dependent-unknown
    evidence: [S-schema, S-contract]
    unknown_reason: 기본값 표와 기업별 표의 관계는 동결 stg 4d798f55 의 코드로 확정되지만 어느 기업이 실제로 교체값을 갖는지는 저장소 밖 데이터에 있다. 인덱스가 이 키의 state 를 data-dependent-unknown 으로 기록했고 닫을 경로는 ops-40-db-snapshot 이다 — 그 원장이 서면 같은 게이트가 승격을 허용한다. 도래를 기다리지 않고 이름만 적는다
    anchors:
      - insuniverse-server/apps/api/src/modules/agency/terms/terms.repository.ts:116
      - insuniverse-server/drizzle/0024_company_terms_defaults.sql:1
  - id: terms:agency-content
    status: data-dependent-unknown
    evidence: [S-schema, S-contract]
    unknown_reason: 기업별 본문 칸의 존재는 스키마로 확정되지만 그 칸에 무엇이 들어 있는지는 저장소 밖 데이터에 있다. 닫을 경로는 ops-40-db-snapshot 이다
    anchors:
      - insuniverse-server/drizzle/schema.ts:1965
  - id: terms:content-sanitize
    status: data-dependent-unknown
    evidence: [S-constant, A-comment]
    unknown_reason: 정제 계층의 배선과 허용 목록은 동결 stg 4d798f55 의 코드로 확정되고 두 앵커 모두 운영에 도달했다. 그럼에도 이 정책이 부록인 것은 그것이 걸리는 대상(기업별 본문)의 state 가 data-dependent-unknown 이기 때문이다 — 정제된 결과물이 실제로 무엇인지는 저장소 밖 데이터에 있다. 닫을 경로는 ops-40-db-snapshot 이다
    anchors:
      - insuniverse-server/apps/api/src/helpers/sanitize.helper.ts:5
      - insuniverse-server/apps/api/src/modules/agency/terms/terms.service.ts:89
  - id: terms:consent-flag-only
    status: active-prod
    evidence: [S-schema, S-contract]
    anchors:
      - insuniverse-server/drizzle/schema.ts:2495
  - id: terms:consent-full-record
    status: staged-only
    evidence: [S-schema, S-contract]
    limitations:
      - 다섯 기록 축을 모두 갖는 유일한 경로를 더한 커밋 `8507677b` (2026-07-20) 이 동결 stg 기준 운영 미도달이다. 인덱스가 이 키를 partial 로 적은 것은 stg 문면이며 운영 문면은 그보다 얇다 — 운영에 도달한 동의 기록은 `terms:consent-flag-only` 한 칸뿐이다
    anchors:
      - insuniverse-server/drizzle/schema.ts:1767
  - id: terms:revocation-record
    status: staged-only
    evidence: [S-schema, S-contract]
    limitations:
      - 동의 철회 시각 칸을 더한 커밋 `8507677b` (2026-07-20) 이 동결 stg 기준 운영 미도달이다. 운영에는 동의 철회를 기록하는 칸이 없다
      - 계획서 축 2 가 이 정책의 앵커로 적은 행은 소비 시각 칸이고 동의 철회 시각 칸은 그 다음 행이다. 구현이 하중 행을 앵커에 더했고 두 행의 정의 커밋은 같다 — 활성성 판정은 그 보정으로 바뀌지 않는다
    anchors:
      - insuniverse-server/drizzle/schema.ts:1772
      - insuniverse-server/drizzle/schema.ts:1773
  - id: terms:revocation-term-split
    status: active-prod
    evidence: [S-guard, A-comment]
    anchors:
      - insuniverse-server/apps/api/src/modules/client/settlement/settlement-admin.service.ts:1
      - insuniverse-server/apps/api/src/modules/fc/fc.service.ts:1
  - id: terms:reconsent-absent
    status: active-prod
    evidence: [S-schema, S-contract]
    anchors:
      - insuniverse-server/apps/api/src/modules/agency/terms/terms.service.ts:98
      - insuniverse-server/apps/api/src/modules/agency/terms/terms.service.ts:99
      - insuniverse-server/drizzle/schema.ts:1970
---

# 약관과 동의 — 판 관리 부재와 그 결과

이 문서는 약관 다섯 표면(`약관_버전` · `동의_시각` · `기업별_문구` · `철회` · `재동의`)이 저장
계층에서 어떤 형태를 갖는가를 적는다. **약관 원문은 싣지 않는다** — 판과 조건과 트리거만이 이
문서의 대상이고 본문은 저장소 밖 데이터이거나 별도 코퍼스에 있다.

각 표면의 상태는 이 문서가 고르지 않는다. 어휘 자격은 런타임 법적 표면 인덱스의 `state` 가
정하고, 활성성은 앵커 행의 blame 과 릴리스 인덱스의 미도달 집합이 정한다. 두 오라클의 곱이
아래 `## 표면 대조` 표의 뒤 세 열이며 그 열은 게이트가 매 실행 다시 만든다.

## 표면 대조

첫 열은 법적 표면 인덱스 `surface_traceability` 의 키를 **그대로** 옮긴 것이다. `state` 는 그
인덱스의 값이고, `앵커 도달성` 은 이 문서 프론트매터 앵커의 blame SHA 를 릴리스 인덱스의 미도달
집합과 대조해 만든 값이며, `status` 와 `배치` 는 그 둘의 곱이다.

| 인덱스 키 | 정책 id | state | 앵커 도달성 | status | 배치 |
|---|---|---|---|---|---|
| `약관_버전` | `terms:version-column-absent` | `partial` | `reached-prod` | `active-prod` | 본문 |
| `약관_버전` | `terms:one-row-per-type` | `partial` | `unreached` | `staged-only` | 부록 |
| `약관_버전` | `terms:in-place-update` | `partial` | `reached-prod` | `active-prod` | 본문 |
| `기업별_문구` | `terms:agency-override` | `data-dependent-unknown` | `unreached` | `data-dependent-unknown` | 부록 |
| `기업별_문구` | `terms:agency-content` | `data-dependent-unknown` | `reached-prod` | `data-dependent-unknown` | 부록 |
| `기업별_문구` | `terms:content-sanitize` | `data-dependent-unknown` | `reached-prod` | `data-dependent-unknown` | 부록 |
| `동의_시각` | `terms:consent-flag-only` | `partial` | `reached-prod` | `active-prod` | 본문 |
| `동의_시각` | `terms:consent-full-record` | `partial` | `unreached` | `staged-only` | 부록 |
| `철회` | `terms:revocation-record` | `ambiguous` | `unreached` | `staged-only` | 부록 |
| `철회` | `terms:revocation-term-split` | `ambiguous` | `reached-prod` | `active-prod` | 본문 |
| `재동의` | `terms:reconsent-absent` | `absent-in-repo` | `reached-prod` | `active-prod` | 본문 |

**`앵커 도달성` 과 `status` 는 같은 축이 아니다.** `기업별_문구` 세 행이 그 차이를 드러낸다 —
셋 중 둘은 앵커가 운영에 도달했는데도 부록이다. 인덱스가 그 키를 `data-dependent-unknown` 으로
적었고 그 자격이 도달성보다 앞서기 때문이다. 코드가 운영에 있다는 것과 그 코드가 무엇을 다루고
있는지 아는 것은 다른 질문이다.

## 판 관리 실태

**두 약관 표 어디에도 판을 가리키는 칸이 없다.** 전역 약관 표는 다섯 칸이고 기업별 약관 표는
열 칸인데, 그중 어느 것도 개정 회차를 담지 않는다. 두 표의 정의 커밋은 각각 운영에 도달해
있으므로 이것은 미배포 상태가 아니라 **운영 사실**이다.

**이전 판이 행으로 남지 않는 것도 운영 사실이다.** 기업별 약관의 갱신 경로는 같은 행을 제자리에서
덮어쓰고 갱신 시각만 다시 적는다. 삭제 경로는 상태 칸을 세우는 형태가 아니라 행을 지운다. 두
경로의 정의 커밋은 운영에 도달해 있다.

**그러나 그 결과의 사유로 유일 제약을 들 수 없다.** 두 약관 표에 타입당 한 행을 강제하는 유일
제약이 있고, 그것이 이전 판의 병존을 막는 것처럼 읽힌다. 그 제약을 더한 커밋은 동결 stg 기준
**운영 미도달**이다. 같은 문장 안에 도달한 절과 미도달인 절을 함께 두면 미도달 근거로 운영 사실을
설명하게 되므로, 이 문서는 둘을 다른 정책으로 갈라 두고 배치도 다르게 한다 — 갱신 경로는 본문,
유일 제약은 부록이다.

**개정이 일어나면 무엇이 일어나는가.** 재동의를 요구하는 절차가 저장소 안에 없다. 갱신 경로는
본문을 바꾸고 갱신 시각을 다시 적을 뿐 동의를 다시 받지 않고, 이미 동의한 쪽에 아무 신호도
보내지 않는다. 그리고 판이 남지 않으므로 **개정 전에 무엇에 동의했는지도 저장 계층에서 되찾을 수
없다.** 두 사실은 별개다 — 앞은 절차의 부재이고 뒤는 기록의 부재다.

`재동의` 키의 부재는 확인 못 함이 아니라 **관측된 부재**다. 인덱스가 그 키의 근거를 세 저장소
스캔 우주에서 0건으로 적었고, 이 문서의 게이트가 같은 스캐너로 그 0을 다시 만든다. 그 0이
스캐너 고장이 아님은 같은 스캔의 양성 프로브가 먼저 보증한다. **우주는 전수가 아니다** — 확장자
allowlist 와 manifest 의 제외 목록이 그 경계를 정하고, 인덱스도 같은 경계를 쓴다.

## 동의 기록의 두 문면

인덱스는 `동의_시각` 을 `partial` 로 적었다. 그 값은 동결 stg 트리를 본 문면이고, **운영 문면은
그보다 얇다.** 다섯 기록 축 전건이 미도달 경로에만 있다.

| 기록 축 | 운영 도달 경로 | 미도달 경로 |
|---|---|---|
| 동의 판 | 없음 | 있음 |
| 동의 본문 | 없음 | 있음 |
| 본문 해시 | 없음 | 있음 |
| 동의 시각 | 없음 | 있음 |
| 동의 철회 시각 | 없음 | 있음 |

운영에 도달한 동의 기록은 주문 상세 표의 참·거짓 한 칸뿐이다. 그 칸은 동의 여부만 담고 언제
동의했는지도, 무엇에 동의했는지도 담지 않는다. 다섯 축을 모두 갖는 경로는 자동차 수집 일회 요청
표 하나이며 그 표를 더한 커밋이 운영에 도달하지 않았다.

**따라서 동의 시각을 묻는 질문의 답은 경로마다 갈린다.** 운영에서는 답이 없고, stg 에서는 한
경로에 한해 답이 있다. 두 문면을 한 값으로 접으면 그 갈림이 사라진다.

## 정책 (기획)

**이 절에 본문 정책이 실린다. 이 PRD 의 다섯 창 가운데 처음이다.** 앞 네 창의 미판정은 전부
배포 환경이 정하는 값(환경 게이트 · 데이터 편성)에 걸려 있었고 그 값은 저장소 밖이었다. 법적
축의 핵심 사실은 **저장 계층의 선언**이다 — 칸이 있는가 없는가, 제약이 걸려 있는가. 그것은
저장소 안에서 확정되고 활성성도 앵커 blame 으로 판정된다.

### terms:version-column-absent

전역 약관과 기업별 약관 어느 표에도 개정 회차를 가리키는 칸이 없다. 판을 저장 계층에서 식별할
수단이 없다는 뜻이며, 그 결과 어떤 행이 몇 번째 판인지는 저장소 안 근거로 답해지지 않는다.

이것은 결손의 보고가 아니라 **관측된 형태**다. 두 표의 정의 커밋이 각각 운영에 도달해 있으므로
현재 운영이 이 형태로 돌고 있다.

### terms:in-place-update

기업별 약관의 갱신은 같은 행을 제자리에서 덮어쓰고 갱신 시각만 다시 적는다. 삭제는 상태 칸을
세우는 형태가 아니라 행 자체를 지운다. **그래서 이전 판은 행으로 남지 않는다.**

판이 남지 않는 원인은 이 경로의 형태이지 유일 제약이 아니다. 제약 쪽은 운영에 도달하지 않았고
부록에 있다.

### terms:consent-flag-only

운영에 도달한 동의 기록은 주문 상세 표의 참·거짓 한 칸이다. 기본값은 거짓이고 값이 반드시 있어야
한다. 이 칸은 **동의 여부만** 담는다 — 시각도, 대상 판도, 본문도 담지 않는다.

마케팅 수신에 대한 동의 한 종류만 이 형태로 남는다. 그 밖의 동의는 인덱스가 스키마 155표를 훑은
탐색에서 관측되지 않았다 — 그 탐색은 컬럼명 형태소 기반이라 전수가 아니며 인덱스도 그 사실을
값으로 적는다.

### terms:revocation-term-split

**같은 표기가 서로 다른 두 대상에 쓰인다.** 하나는 이 문서가 다루는 동의 철회이고, 다른 하나는
정산과 기업 계약 축의 사업 취소다. 인덱스가 세 저장소 전수에서 그 표기의 출현을 세어 동의 축과
사업 축과 미분류로 갈랐고, 사업 축 쪽이 더 많다.

그래서 이 문서군은 법적 표면에서 **전개형만** 쓴다. 단독 표기는 문면이 어느 축을 가리키는지 답하지
못하며, 그 규약은 용어 정본 문서가 확정한 것이고 이 창의 게이트가 매 실행 세 문서에서 단독 표기
0건을 확인한다.

### terms:reconsent-absent

**재동의 절차가 저장소 안에 없다.** 갱신 경로는 본문과 갱신 시각을 다시 쓸 뿐 동의를 다시 받지
않고, 이미 동의한 쪽을 다시 부르는 지점도 없다. 세 저장소 전수 스캔에서 그 어휘의 출현이 0건이며,
인덱스가 그 부재를 값으로 적었다.

**부재는 관측이지 판정이 아니다.** 저장소 밖 절차가 존재하는지 여부는 여기서 갈리지 않는다 — 이
문서가 말하는 것은 저장 계층과 코드 경로에 그 절차가 없다는 것뿐이다.

## 강제 지점 (개발)

정책 내용을 다시 쓰지 않는다. 각 정책이 저장소 안 **어디에서** 강제되는가와 그 기제만 적는다.
배치가 부록인 정책도 이 절에 있다 — 이 절의 id 집합은 배치와 무관하게 프론트매터 전체와
결속돼 있다. 기준점은 전건 동결 stg `4d798f55` 다.

### terms:version-column-absent

- 강제 방식: 없음. 스키마 선언에 그 칸이 존재하지 않는 것이 관측 지점이다.
- 앵커: `insuniverse-server/drizzle/schema.ts:1947` (전역 약관 표 첫 칸) · `:1961` (기업별 약관 표 첫 칸)
- 게이트: 없음 — 저장 계층 선언이며 조건에 걸리지 않는다.

### terms:one-row-per-type

- 강제 방식: 유일 제약 두 건. 전역 약관은 타입 한 칸에, 기업별 약관은 기업과 타입 두 칸에 걸린다.
- 앵커: `insuniverse-server/drizzle/schema.ts:1956` · `:1975`
- 게이트: 없음 — 다만 두 제약의 정의 커밋이 운영에 도달하지 않았다.

### terms:in-place-update

- 강제 방식: 리포지터리 갱신·삭제 경로. 갱신은 기본키로 같은 행을 덮어쓰고, 삭제는 행을 지운다.
- 앵커: `insuniverse-server/apps/api/src/modules/agency/terms/terms.repository.ts:131` (갱신) · `:135` (삭제)
- 게이트: 없음.

### terms:agency-override

- 강제 방식: 기본값 조회 경로. 전역 약관 표에서 정해진 타입 목록만 골라 기본값으로 내려주고, 기업별 표의 같은 타입 행이 그것을 대신한다.
- 앵커: `insuniverse-server/apps/api/src/modules/agency/terms/terms.repository.ts:116` (기본값 조회) · `insuniverse-server/drizzle/0024_company_terms_defaults.sql:1` (교체 관계를 세운 시드)
- 게이트: 없음 — 다만 두 앵커의 정의 커밋이 운영에 도달하지 않았다.

### terms:agency-content

- 강제 방식: 스키마 선언. 기업별 약관 표에 본문 칸이 있고 값이 없어도 된다.
- 앵커: `insuniverse-server/drizzle/schema.ts:1965`
- 게이트: 없음.

### terms:content-sanitize

- 강제 방식: 저장 직전 화이트리스트 정제. 서비스 계층의 생성·갱신 두 지점이 저장소 호출 앞에서 부른다. 허용 목록 밖 태그는 통째로 버려진다.
- 앵커: `insuniverse-server/apps/api/src/helpers/sanitize.helper.ts:5` (허용 태그 목록) · `insuniverse-server/apps/api/src/modules/agency/terms/terms.service.ts:89` (생성 경로 배선)
- 게이트: 없음.

### terms:consent-flag-only

- 강제 방식: 스키마 선언. 참·거짓 한 칸이고 기본값이 있으며 값이 반드시 있어야 한다.
- 앵커: `insuniverse-server/drizzle/schema.ts:2495`
- 게이트: 없음.

### terms:consent-full-record

- 강제 방식: 스키마 선언. 자동차 수집 일회 요청 표가 판·본문·해시·동의 시각·동의 철회 시각 다섯 칸을 갖는다.
- 앵커: `insuniverse-server/drizzle/schema.ts:1767`
- 게이트: 없음 — 다만 이 표의 정의 커밋이 운영에 도달하지 않았다.

### terms:revocation-record

- 강제 방식: 스키마 선언. 같은 표의 소비 시각 칸과 동의 철회 시각 칸이 나란히 있다.
- 앵커: `insuniverse-server/drizzle/schema.ts:1772` (소비 시각) · `:1773` (동의 철회 시각 — 하중 행)
- 게이트: 없음 — 다만 두 칸의 정의 커밋이 운영에 도달하지 않았다.

### terms:revocation-term-split

- 강제 방식: 없음. 표기 축이며 강제 기제가 아니다. 앵커는 사업 축 표기가 쓰이는 두 모듈의 진입 파일이고, 이 문서가 그 축과 갈리는 자리를 가리킨다.
- 앵커: `insuniverse-server/apps/api/src/modules/client/settlement/settlement-admin.service.ts:1` · `insuniverse-server/apps/api/src/modules/fc/fc.service.ts:1`
- 게이트: 없음.

### terms:reconsent-absent

- 강제 방식: 없음. 갱신 경로에 재동의 분기가 존재하지 않는 것이 관측 지점이고, 갱신 시각 칸이 그 경로가 남기는 유일한 흔적이다.
- 앵커: `insuniverse-server/apps/api/src/modules/agency/terms/terms.service.ts:98` (계획서 축 2 가 적은 관측 지점) · `:99` (갱신 경로 진입 — 하중 행) · `insuniverse-server/drizzle/schema.ts:1970` (갱신 시각 칸)
- 게이트: 없음.

## 적용 범위 (actor × 서비스)

`적용` 은 그 표면이 해당 축에서 실제로 관측되는 경우에만 쓴다. 그 밖은 `—` 이며 빈 칸이 아니라
관측된 부재다.

actor 축은 대부분 `—` 다. `actors` 는 **로그인 주체 축**이고, 약관 저장 계층의 정책은 로그인
주체로 분기하지 않는다 — 기업별 약관의 편집 경로만 관리자 콘솔에 있고 나머지는 저장 계층 선언이다.
동의 기록 두 축은 로그인하지 않는 표면에서 발생하므로 이 축의 원소를 지목하지 않는다.

서비스 축은 갈린다. 모든 앵커가 server 저장소에 있으므로 `server` 는 전건 `적용` 이고, `site` 와
`admin` 은 그 정책이 만든 값을 읽을 뿐 정책을 강제하지 않아 `—` 다.

| 정책 id | 관리자 | 영업대리점 | 기업담당자 | 일반회원 | 기업회원 | server | site | admin |
|---|---|---|---|---|---|---|---|---|
| terms:version-column-absent | — | — | — | — | — | 적용 | — | — |
| terms:one-row-per-type | — | — | — | — | — | 적용 | — | — |
| terms:in-place-update | 적용 | — | — | — | — | 적용 | — | — |
| terms:agency-override | 적용 | — | 적용 | — | — | 적용 | — | — |
| terms:agency-content | 적용 | — | 적용 | — | — | 적용 | — | — |
| terms:content-sanitize | 적용 | — | — | — | — | 적용 | — | — |
| terms:consent-flag-only | — | — | — | — | — | 적용 | — | — |
| terms:consent-full-record | — | — | — | — | — | 적용 | — | — |
| terms:revocation-record | — | — | — | — | — | 적용 | — | — |
| terms:revocation-term-split | — | — | — | — | — | 적용 | — | — |
| terms:reconsent-absent | 적용 | — | — | — | — | 적용 | — | — |

## 예외·폴백

예외가 없는 정책도 행을 둔다 — 없다는 것도 확인의 결과이며 그 확인 근거를 함께 적는다.

| 정책 id | 예외 | 폴백 | 확인 근거 |
|---|---|---|---|
| terms:version-column-absent | 없음 | 없음 — 판을 담는 다른 칸이 두 표에 없다 | `insuniverse-server/drizzle/schema.ts:1947` · `:1961` 의 칸 열거 전수 |
| terms:one-row-per-type | 운영에서는 이 제약이 아직 강제되지 않는다 | 없음 | 제약 정의 커밋이 릴리스 인덱스의 미도달 집합에 있다 |
| terms:in-place-update | 없음 | 없음 — 삭제 경로도 행을 남기지 않는다 | `insuniverse-server/apps/api/src/modules/agency/terms/terms.repository.ts:131` · `:135` |
| terms:agency-override | 기업별 행이 없으면 전역 기본값이 그대로 쓰인다 | 전역 약관 표의 같은 타입 행 | `insuniverse-server/apps/api/src/modules/agency/terms/terms.repository.ts:116` |
| terms:agency-content | 본문 칸이 비어 있을 수 있다 | 없음 — 빈 값의 처리는 이 문서가 판정하지 않는다 | `insuniverse-server/drizzle/schema.ts:1965` 의 칸 선언에 필수 표시가 없다 |
| terms:content-sanitize | 입력이 비어 있으면 그대로 돌려준다 | 허용 목록 밖 태그는 폴백 없이 버려진다 | `insuniverse-server/apps/api/src/helpers/sanitize.helper.ts:5` |
| terms:consent-flag-only | 없음 — 기본값이 있고 값이 반드시 있어야 한다 | 없음 | `insuniverse-server/drizzle/schema.ts:2495` |
| terms:consent-full-record | 운영에서는 이 경로가 존재하지 않는다 | 없음 — 운영의 동의 기록은 참·거짓 한 칸뿐이다 | 표 정의 커밋이 릴리스 인덱스의 미도달 집합에 있다 |
| terms:revocation-record | 운영에서는 이 칸이 존재하지 않는다 | 없음 | 칸 정의 커밋이 릴리스 인덱스의 미도달 집합에 있다 |
| terms:revocation-term-split | 없음 | 없음 — 표기 축이라 폴백이 성립하지 않는다 | 인덱스의 표기 분류 3분해와 그 규칙 해시 |
| terms:reconsent-absent | 없음 | 없음 — 재동의를 대신하는 다른 경로가 관측되지 않는다 | 세 저장소 스캔 우주 0건 (양성 프로브 통과 뒤의 0이다) |

## 부록

이 절의 정책은 **현행 발효 여부가 판정되지 않아** 본문에서 격리한다. 격리 사유는 둘로 갈린다 —
세 건은 운영 미도달이고(유일 제약 · 다섯 축 동의 기록 · 동의 철회 시각 칸), 세 건은 데이터
의존이다(기업별 표 세 정책). 앞은 코드가 아직 운영에 없다는 뜻이고 뒤는 코드는 있는데 그것이
다루는 값이 저장소 밖이라는 뜻이다. 두 사유를 한 문장으로 접으면 서로 다른 결손이 같아 보인다.

**여기 실린 어떤 정책도 그 형태가 틀렸다고 말하지 않는다.** 미판정은 부정이 아니다.

### terms:one-row-per-type

두 약관 표에 타입당 한 행만 허용하는 제약이 걸려 있다. 같은 타입의 둘째 행을 넣으려 하면 저장이
거부된다는 뜻이며, 그래서 이전 판이 새 행으로 병존할 수 없다.

**이 제약은 운영에 도달하지 않았다.** 그러므로 지금 운영에서 이전 판이 남지 않는 이유는 이것이
아니라 갱신 경로의 형태다. 두 사유를 하나로 접으면, 운영에 없는 근거로 운영 사실을 설명하게 된다.

### terms:agency-override

기업이 전역 약관 대신 자기 문구를 쓸 수 있다. 전역 표가 기본값을 들고, 기업별 표에 같은 타입의
행이 있으면 그것이 대신한다.

**어느 기업이 실제로 교체값을 갖는지는 저장소 안에서 답해지지 않는다.** 구조는 확정돼 있고 값은
운영 데이터에 있다.

### terms:agency-content

기업별 약관 표에 본문 칸이 있고 값이 없어도 된다. 칸이 비어 있는 행과 값이 든 행이 저장 계층에서
같은 형태이므로, 어느 쪽이 실제 상태인지는 데이터를 봐야 갈린다.

### terms:content-sanitize

기업별 약관 본문은 저장 직전에 허용 목록 기반으로 정제된다. 허용 목록 밖 태그는 통째로 버려지고,
링크 주소는 정해진 스킴만 통과한다. 배선은 저장소 계층이 아니라 서비스 계층에 있고 생성과 갱신
두 지점에 각각 걸려 있다.

**정제 계층 자체는 운영에 도달해 있다.** 이 정책이 부록인 것은 그것이 걸리는 대상의 값이 저장소
밖이기 때문이다 — 정제가 무엇을 통과시켰고 무엇을 버렸는지는 여기서 답해지지 않는다.

### terms:consent-full-record

동의를 온전히 남기는 경로가 하나 있다. 자동차 수집 일회 요청 표가 동의 판·본문·본문 해시·동의
시각·동의 철회 시각을 각각 칸으로 갖는다.

**이 표는 운영에 도달하지 않았다.** 인덱스가 `동의_시각` 을 `partial` 로 적은 근거의 절반이
여기이고, 그 절반이 운영 문면에는 없다.

### terms:revocation-record

같은 표에 동의 철회 시각 칸이 있다. 동의를 되돌린 시점을 남길 수 있다는 뜻이다.

**이 칸도 운영에 도달하지 않았다.** 운영 저장 계층에는 동의 철회를 기록하는 칸이 없다.

## 연혁

릴리스 태그가 존재하지 않는다. 릴리스 열은 `—` 로 두고 커밋 SHA 로 추적한다. 행은 커밋 인덱스가
이 문서의 앵커 경로 접촉으로 기록한 커밋 집합 안에서 **약관과 동의의 저장 형태가 바뀐 시점**만
골랐다. 앵커 경로 중 스키마 파일 하나가 접촉 300건을 넘으므로 전건을 싣지 않는다 — 그 선별은
사람 판단이고 게이트는 앵커 경로 접촉까지만 본다.

| 릴리스 | 날짜 | SHA | 변경 요약 |
|---|---|---|---|
| — | 2024-09-02 | `e9ae08db` | 전역 약관 표가 처음 선다 — 판을 담는 칸 없이 다섯 칸으로 시작한다 |
| — | 2025-12-22 | `110e08e3` | 주문 상세 표에 동의 참·거짓 한 칸이 붙는다 — 운영에 도달한 유일한 동의 기록의 시작 |
| — | 2026-05-18 | `78ca58ff` | 기업별 약관 표가 선다 — 열 칸이고 여기에도 판을 담는 칸이 없다 |
| — | 2026-05-18 | `d4f95786` | 기업별 약관의 생성·갱신·삭제 경로와 저장 전 정제 계층이 배선된다 — 제자리 갱신과 행 삭제가 이때 형태를 갖는다 |
| — | 2026-06-04 | `acf9c24a` | 관리자 콘솔 쪽 목록·검색·수정 경로가 붙는다 |
| — | 2026-07-16 | `c577fd29` | 두 약관 표에 타입당 한 행 제약이 더해지고 기본값 시드가 선다 — **운영 미도달이며 이 문서가 그 사실을 배치로 가른다** |
| — | 2026-07-20 | `8507677b` | 동의 다섯 축을 모두 갖는 표가 더해진다 — 판·본문·해시·동의 시각·동의 철회 시각. **운영 미도달** |
