# website 종류 매핑 — Q&A / 결정 기록

> `sources/website-kinds.html` 의 각 절에서 나온 질문과 답.
> **이 파일이 본체다.** HTML 은 `scaffold/render-website-kinds.py` 가 이 파일을 읽어 만든다.
> HTML 에 직접 쓴 글은 다음 재렌더에 사라지므로, 답은 항상 여기에 적는다.

## 쓰는 법

1. HTML 의 절마다 있는 입력창에 질문을 쓰고 **복사** 를 누른다.
2. `[website-kinds §N 제목] Q. …` 형식이 클립보드에 담긴다.
3. 그걸 대화에 붙여넣는다.
4. 답이 이 파일의 해당 `## §N` 아래에 `### Q{n}.` 으로 추가된다.
5. `python3 scaffold/render-website-kinds.py` 로 HTML 을 다시 만든다.

형식은 아래와 같다. 제목 다음 줄의 메타는 `` `날짜` · `상태` `` 이고 상태는 `닫힘` 또는 `열림` 이다.

```md
### Q1. 질문 제목
`2026-08-11` · `닫힘`

**A.** 답 본문.
```

---

## §1 한눈에

### Q1. 참조 사이트만 목록으로 안 닫히는 이유가 무엇인가
`2026-08-11` · `닫힘`

**A.** 후보 집합의 크기가 원리상 다르기 때문이다.

툴·소셜·디렉터리는 **새 서비스가 생겨야** 목록이 늘어난다. 거래소나 런치패드가 새로 나오는 일은 드물어서 46개가 거의 그대로 유지된다.

참조는 다르다. 발행자가 링크할 수 있는 뉴스·위키·브랜드 페이지는 **세상의 모든 사이트**가 후보다. 실측으로도 상위 30개 도메인이 website 전체의 30.1% 밖에 못 덮고, 상위 200개까지 가야 53.1% 다.

그래서 참조만 신호 기반이어야 한다. `isNews` · 도메인 나이 731일 이상 · `hasTracking` · `techCount` 4개 이상 넷이 세 시간분할에서 일관되게 이 구간을 잡는다는 실측이 `sources/website-image.html` §4 에 있다.

---

## §2 매핑

### Q2. `coincommunities.org` 는 참조인가 디렉터리인가
`2026-08-11` · `닫힘`

**A.** 디렉터리다. 다만 **구조로는 못 가른다.**

고유 URL 을 토큰 수로 나눈 비율이 0.78 인데, `wikipedia.org` 0.74 와 `bitcointalk.org` 0.77 이 바로 옆에 있다. 이 셋을 비율로 자르면 반드시 섞인다.

디렉터리로 보는 근거는 비율이 아니라 **페이지의 주인이 누구냐**다. `coincommunities.org` 의 각 페이지는 그 토큰에 대한 페이지이고, `wikipedia.org` 의 각 페이지는 그 밈에 대한 페이지다. 앞엣것은 토큰 이름과 CA 가 페이지에 있고 뒤엣것은 없다.

그래서 디렉터리는 목록으로만 관리한다. 7개뿐이라 목록이 더 싸다.

### Q3. `web_site` 안에 참조 사이트가 얼마나 섞여 있나
`2026-08-11` · `열림`

목록에 없는 참조는 전부 `web_site` 로 떨어진다. 실제로 `thaipbs.or.th` 13건이 그렇게 들어가 있다. 태국 공영방송인데, 13개 토큰이 **같은 기사 하나**를 걸어서 "토큰마다 다른 페이지" 규칙에 안 걸렸다.

규모를 재려면 `web_site` 657개 도메인을 훑어야 하는데 아직 안 했다.

### Q6. 상위 참조·소셜에서 "무슨 주제인가" 를 싸게 뽑을 수 있나
`2026-08-11` · `닫힘`

**A.** 뽑을 수 있고, **상위 10% 로 제한할 이유가 없다.** HTML 은 이미 받고 있어서 추가되는 것이 파싱뿐이기 때문이다. 다만 **어디서 나오고 어디서 안 나오는지가 갈린다.**

24건을 실제로 열어 확인했다.

- 뉴스와 위키밈은 완벽하다. `nypost` 3/3 과 `knowyourmeme` 3/3 이 `og:title` · `og:description` · `article:published_time` 을 전부 준다. `knowyourmeme` 의 `og:description` 은 밈 설명 전문이라 "이 토큰이 무슨 밈인가" 의 직접적인 답이 된다.
- `wikipedia.org` 는 `<title>` 만 준다. `og:description` 을 안 쓴다.
- `tenor.com` 은 GIF 제목을 준다.
- `truthsocial.com` 은 **3/3 전부 실패**다. `og:title` 이 `Donald J. Trump (@realDonaldTrump)` 로 계정명 고정이고 게시물 내용이 없다. 79건을 호출해도 얻는 것이 없다.
- `facebook.com` 은 절반이다. 로그인 벽에 안 걸린 건은 `og:description` 에 본문 전문이 왔고, 걸린 건은 제목이 `Facebook` 뿐이었다.
- `linktr.ee` 는 **403** 이다. 봇 차단이라 아예 못 연다.

**그래서 방법을 종류별로 매핑한다.**

| 갈래 | 방법 | 얻는 것 | 추가 비용 |
|---|---|---|---|
| 뉴스 · 위키밈 | `og:title` + `og:description` + `article:published_time` | 주제 · 요약 · 밈 나이 | 0 |
| 위키피디아 | `<title>` | 문서 제목 | 0 |
| GIF 호스트 | `og:title` | GIF 주제 | 0 |
| 단축링크 | `maxRedirects:0` → `Location` | 진짜 목적지 | HEAD 1회 |
| 랜딩형 · SPA 소셜 | 없다 | — | 호출하지 않는다 |

🔴 **부수적으로 더 큰 것이 나왔다.** `knowyourmeme` 의 `article:published_time` 이 2009 · 2018 · 2023 으로 왔다. 밈이 토큰보다 **몇 년 앞선 것인지**가 그냥 나온다.

이 필드는 audit 이 *"`isNews` 에 완전히 흡수된다"* 며 제거 확정한 것이다. 그 판단은 `tokens.web{}` 관점에서는 맞았다. 참조를 Content 로 보내면 이것이 `posted_at` 이고, X trending 의 snowflake 시각과 같은 역할을 한다 — *"밈이 토큰보다 앞서는가"*. **되살려야 한다.**

### Q7. `t.co` 는 실제 목적지를 가져올 수 있나
`2026-08-11` · `닫힘`

**A.** 된다. 실측 **4/4 가 301 로 풀렸다.**

```
t.co/ZE0NALoEfx → instagram.com/reels/DbLbraCOOwr/
t.co/B3PBdNIand → stockcat.lol
t.co/rmY6kBxVBW → catwifcap.fun
t.co/q1q66gv2gj → bootoshi.ai
```

목적지를 보면 `t.co` 35건은 **소셜이 아니다.** 대부분 토큰사이트이고 일부는 이미 fetcher 가 있는 소셜이다. 즉 §3 에서 단축링크를 `web_social` 에서 빼기로 한 것이 맞았고, 푸는 방법도 확인됐다.

이건 G-18(단축 해제)이 다룰 일이고 `bit.ly` 와 같은 맥락이다. G-18 이 세운 조건 두 개가 그대로 적용된다. 첫째, `tokenInfo` 경계에서 한 번 풀고 아래로는 canonical 만 흘린다. 둘째, SSRF 방어를 위해 `maxRedirects: 0` 으로 홉을 직접 돌며 사설 대역을 차단한다.

같이 확인된 것 — `linktr.ee` 2/2 가 **403** 이다. G-18 이 *"리다이렉트가 아니라 랜딩·스마트 링크라 따라가도 목적지가 안 나온다"* 고 적어둔 예외인데, 실제로는 그보다 나빠서 봇 차단으로 페이지 자체를 못 연다.

---

## §3 정리 안 된 것

### Q4. `blog.jp` 는 왜 목록으로 못 고치나
`2026-08-11` · `닫힘`

**A.** 종류가 틀린 게 아니라 **도메인 키 계산이 틀린 것**이기 때문이다.

나머지 넷(단축링크 · GIF 호스트 · `google.com` · `archive.org`)은 도메인이 하나로 정해져 있고 그 도메인을 어느 종류에 넣을지만 정하면 끝난다.

`blog.jp` 는 도메인 자체가 잘못 만들어졌다. `*.blog.jp` 는 일본 개인 블로그 호스팅이라 서브도메인마다 주인이 다른데, 이번 분류에 쓴 접미사 34개 목록에 `blog.jp` 가 없어서 30건이 한 도메인으로 합쳐졌다.

같은 종류의 오류가 그 34개 밖에 몇 개 더 있는지는 Public Suffix List 를 제대로 쓰기 전에는 알 수 없다.

### Q5. `google.com` 66건을 검색으로 보낸다면 G-4 와 어떻게 맞추나
`2026-08-11` · `열림`

G-4 는 검색 URL 을 외부 호출 없이 검색어 한 행으로 만든다. `google.com` 의 66건 중 검색 결과 URL 은 그 규칙에 그대로 들어갈 수 있어 보인다.

문제는 `docs.google.com` 과 `sites.google.com` 이다. 이 둘은 검색이 아니라 **문서와 사이트 호스팅**이고, 서브도메인마다 주인이 다르므로 `blog.jp` 와 같은 부류다. 66건의 내부 구성을 아직 안 봤다.

---

## §4 컨텐츠 추출

### Q8. 표준만으로 컨텐츠 정보를 뽑을 수 있나 · 폴백 순서는 무엇인가
`2026-08-11` · `닫힘`

**A.** 뽑을 수 있다. 열린 도메인 기준 **82%** 에서 `text` 가 나온다. 다만 폴백 순서는 예상과 달랐다.

180개 도메인 308 URL 을 조사했고 **124 도메인이 열렸다(69%).** 못 연 56개는 403 봇 차단이 대부분이다. 수집은 결정적인 스크립트가 했고, "있다" 와 "쓸모 있다" 를 가르는 판정은 Sonnet 에이전트 9개가 나눠 맡았다.

**폴백 체인은 실제 기여도로 정했다.** 표준을 하나씩 더할 때 `text` 가 채워지는 도메인이 몇 개 늘어나는지를 잰 결과다.

| 순서 | 표준 | 추가 도메인 | 누적 |
|---|---|---|---|
| 1 | `og` | +76 | 61.3% |
| 2 | `meta`(`<title>`·`description`) | +15 | 73.4% |
| 3 | `jsonld` | +10 | 81.5% |
| 4 | `twitter` | +1 | 82.3% |

🔴 **JSON-LD 우선이라는 예상이 뒤집혔다.** 구글 리치결과 때문에 유인이 가장 강할 것으로 봤는데, 실제로는 `og` 하나가 61.3% 를 혼자 덮고 JSON-LD 는 10 도메인만 더한다. 표준 `<title>` 이 JSON-LD 보다 앞선다.

**필드마다 값어치가 다르다.**

| 종류 | n | text | published | tags | media |
|---|---|---|---|---|---|
| `web_reference` | 60 | **95%** | **65%** | 40% | **87%** |
| `web_tool` | 8 | 88% | 12% | 0% | 50% |
| `web_social` | 21 | 71% | 29% | 24% | 62% |
| `web_site` | 28 | 68% | 21% | 7% | 32% |
| `web_directory` | 7 | 57% | 0% | 0% | 43% |

- `published` 는 **참조에서만** 나온다. `article:published_time` 이 참조 밖에서 사실상 0 인 것과 일치한다.
- `tags` 는 쓸 수 없다. 최고가 참조 40% 이고 툴·디렉터리는 0% 다. 에이전트가 *"site-wide category labels, not topics"* 라고 반복해 지적했다.
- JSON-LD `articleBody` 로 **본문 전문**이 오는 곳은 11개 도메인이고 전부 뉴스사다(`cnn`·`foxnews`·`cbsnews`·`dexerto`·`vogue`·`mirror.co.uk` 등).

**도메인별 셀렉터는 만들지 않는다.** 조사한 것은 표준 8종뿐이고, 그것만 보는 이유는 사이트가 그것을 유지할 유인이 따로 있기 때문이다. 검색 리치결과와 SNS 공유 미리보기가 깨지면 사이트 주인이 손해를 본다. CSS 셀렉터에는 그런 유인이 없어서 리뉴얼 한 번에 깨지고, 180개를 유지할 방법도 없다.

### Q9. 컨텐츠가 안 나오는 도메인은 어떤 것들인가
`2026-08-11` · `닫힘`

**A.** 두 부류로 갈리고, 한쪽은 **그 자체가 신호**다.

첫째는 **로그인 벽과 템플릿 응답**이다. 에이전트 note 가 그대로 짚었다 — LinkedIn 과 Instagram 은 `og` 세 개가 전부 로그인 안내문이었고, Twitch 는 채널 태그라인 고정, Myspace 는 사용자명만 끼워 넣은 사이트 공통 템플릿이었다. `truthsocial` 은 이미 확인한 대로 계정명 고정이다.

둘째가 흥미롭다. `text` 가 boilerplate 인 `web_site` 도메인이 이렇다.

```
longdog.lol · pumpga.fun · littlepoe.lol · crypto.grok.me · darkarena.app
```

**소유권 조사에서 `placeholder` 로 나온 바로 그 도메인들이다.** 에이전트도 독립적으로 같은 것을 짚었다 — *"description matches the homepage tagline verbatim, not specific to this token"*.

즉 템플릿을 깔고 내용을 안 채운 사이트는 **CA 도 안 채우고 OG 도 안 채운다.** 두 조사가 서로 모른 채 같은 집단을 가리켰다. `text` 결측을 "정보 없음" 으로 버리면 안 되고 **`placeholder` 로 기록해야 하는 근거**가 하나 더 생겼다.

### Q12. `web_social` 은 정말 부를 가치가 없나
`2026-08-11` · `닫힘` · **실호출로 재측정**

**A.** 아니다. **한 덩어리로 본 것이 틀렸고, 세 부류였다.**

Q9 는 도메인당 샘플 두 개로 판정했는데 그 둘이 서로 다른 URL 종류인 경우가 있었다. 다섯 호스트를 UA 네 종(일반 Chrome · `facebookexternalhit` · `Twitterbot` · `Googlebot`)과 **우리 fetcher 의 실제 UA** 로 다시 열었다.

| 호스트 | 우리 UA 로 부르면 | 얻는 것 |
|---|---|---|
| `tenor` | 200 | `og:title` 항목별 · `twitter:description` 주제어 · `keywords` 7개 · JSON-LD |
| `threads` | 200 | `og:description` = **게시물 본문** (`"Shroud / Kenya, 2023"`) · 실제 사진 |
| `facebook` | 200 | `og:description` = **릴 캡션** · 실제 이미지 |
| `truthsocial` | 200 | 플랫폼 소개문뿐. 게시물 정보 0 — **Q9 판정이 맞았다** |
| `pinterest` | 200 · 1.1MB | `<title>` 조차 없다. 크롤러 UA 3종도 동일 |

**UA 가 결정적이었다.** 일반 Chrome UA 로는 `facebook` 이 HTTP 400 을 낸다. **브라우저 위장이 오히려 나빴고, 크롤러를 사칭할 필요도 없었다** — 우리 UA 그대로 og 가 온다.

`pinterest` 는 URL 도 `/pin/{숫자}` 라 슬러그가 없어 **호출 0회로 채울 필드가 하나도 없다.** 그래서 남긴다.

`truthsocial` 은 응답이 비어도 **URL 이 값을 담고 있다** — post id 가 Mastodon 스노플레이크라 상위 48비트가 발행시각이다. 실제 id 셋이 `2026-07-25` · `07-26` · `07-26` 으로 순서까지 맞았다. 결정은 `website.md` W-18.

#### 나머지 13개도 전수로 재봤다 (호스트당 샘플 4개)

**여섯이 더 뒤집혔다.**

| 호스트 | 링크 | 얻는 것 |
|---|---|---|
| `4chan.org` | 11 | og 는 없는데 **`<title>` 이 스레드 제목**이다 — `/an/ - This is my cat, Kitty. Sadly, she passed away thre…`. 폴백 체인 3순위(`meta.title`)라 그대로 잡힌다 |
| `tumblr.com` | 5 | 4/4 항목별. 게시물 `"the shibes are in"`, 답글 `"Reply from @karssenleon to @iamtheguyyy"` + 본문 |
| `qq.com` | 7 | `news.qq.com` 은 제목·요약·**발행시각**까지 |
| `discord.gg` + `discord.com` | 7 | 살아있는 초대가 서버명·멤버수·소개를 준다 — `"Join the Doji’s Cabal Discord Server! / …hang out with 7262 other members…"` |
| `spotify.com` | 2 | 곡명 + `"아티스트 · 앨범 · Song · 2025"` + JSON-LD 발매일 |
| `naver.com` | 5 | `<title>` 이 카페 글 제목 |

계정 수준까지만 나오는 `twitch.tv`(28) · `bilibili.com`(6)도 같이 옮겼다 — 루트 URL 은 R4② 가 `site_wide` 로 알아서 떨어뜨린다.

**남긴 아홉은 이유가 각각 다르다.** `medium` · `patreon` · `kick` · `4plebs` 는 **전부 403**(Cloudflare)이라 옮기면 매번 `ERROR` 가 되고 재시도 배치가 영원히 두드린다. `pinterest` · `douyin` 은 받아도 값이 없고, `linkedin` 은 4건 중 2건이 429·999 로 막히며, `myspace` 는 `og:description` 이 사이트 공통 문구 고정이다. `truthsocial` 은 W-18 갈래다.

#### 옮기고 나서 실호출로 두 개가 더 걸렸다

**① `twitch` 가 `BLOCKED` 로 떨어졌다 — R1 오탐.** `og:description` 이 *"rasmrr streams live on Twitch! Check out their videos, **sign up to** chat, and join their community."* 인데 `GATE_PHRASES` 에 `'sign up to'` 가 있었다. 페이지는 안 막혀 있고 그건 채팅 권유다. `gated` → `BLOCKED` 은 *"재시도로 풀릴 수 있다"* 라 **28건이 영원히 재시도 대상**이 된다.

조사 샘플 전수로 대조하니 실제로 발동한 적 있는 문구는 `log in` · `login` · `create an account` · `로그인` 넷뿐이고 `'sign up to'` 는 **한 번도 없었다.** 뺐다.

**② `discord.gg` 만 옮기면 아무 일도 안 일어난다.** 리다이렉트 해제(G-18)가 `discord.com/invite/{code}` 로 바꿔 놓기 때문이다. `discord.com` 도 같이 옮겨야 뜻이 있다.

⚠️ 알려진 손해 — `discord.com` **루트 URL** 은 경로가 없어 R4③ 이 못 잡고 플랫폼 소개문이 약한 `entry` 로 저장된다. `qq` 의 `mp.weixin` 페이지는 3.1MB 라 `maxContentLength`(3MB)를 넘어 `ERROR` 다. `naver` 는 `<title>` 만 있고 `description` 이 없어 R3 의 `isBareTemplate` 에 걸려 `unfilled` 이 된다 — **`title` 만 있는 페이지는 구조상 `title_only` 에 도달할 수 없다**(R3 이 R6 보다 앞이라).

### Q10. RSS 는 쓸 수 있나
`2026-08-11` · `닫힘`

**A.** 쓸 수 있다. 다만 **전량이 아니라 결측 보정용**이다.

처음에는 못 쓴다고 판단했다. 근거는 6주 지난 링크 42건 중 피드에 남아 있는 것이 6건(14.3%)뿐이었다는 것이다. **그 판단이 두 군데 틀렸다.**

**첫째, 시간창을 잘못 봤다.** 내가 근거로 삼은 `knowyourmeme` 이 항목 4개짜리로 가장 짧은 축이었다. 실제로는 사이트마다 자릿수가 다르다.

```
trib.al          150항목  283일
dailymail.com    150항목   41일
knowyourmeme       4항목    3일    ← 이것만 보고 판단했다
foxnews.com       25항목    0일
```

**둘째, 더 중요한 것 — OpenGraph 가 비는 사이트에서 RSS 가 유일한 출처다.** `dailymail.com` 기사 하나를 실제로 열어 확인했다.

```
기사 페이지 899KB              RSS 항목
  og:title  없음                 title  Andy Burnham says high streets…
  og:desc   빈 문자열            desc   The Prime Minister has pledged…
  pub_time  없음                 date   Mon, 10 Aug 2026 22:09:40 GMT
```

`dailymail.com` 은 배정에서 `P3 title-only` 였다. OpenGraph 가 통째로 비어 `jsonld:headline` 으로 제목만 겨우 건졌기 때문이다. **피드를 보면 P1 으로 올라간다.**

**그리고 실시간이면 적중률이 다르다.** 14.3% 는 링크를 받은 지 6주 지난 데이터로 잰 하한이다. 실제 파이프라인은 `token.flow` 를 실시간으로 받으므로 토큰이 방금 건 기사는 대개 피드 안에 있다. RSS 의 값어치는 **수집 시점과 발행 시점의 간격**이 정한다.

**확인한 사실 — 태그가 있다고 피드인 것이 아니다.** 34개 도메인에 `rel=alternate rss` 태그가 있었는데 실제로 파싱된 것은 24개, 요약까지 있는 것은 22개다. 나머지는 앱 껍데기이거나 404 다. `truthsocial` 에서 같은 함정을 먼저 봤다 — 존재하지 않는 계정의 `.rss` 도 200 과 함께 3.5KB 껍데기를 돌려준다.

**그래서 규칙을 좁혔다.**

```
① OG 폴백 체인을 먼저 돌린다
② text_body 또는 published 가 비었나?
     아니오 → 끝. RSS 를 부르지 않는다        (P1 · P2 는 여기서 끝난다)
     예   → ③
③ 그 도메인에 rel=alternate rss 링크가 있나?
④ 피드를 받아 항목 link 를 이 URL 과 대조하고 빈 칸만 메운다
```

이러면 "추가 호출 0회" 원칙이 P1·P2 에서는 유지되고, OpenGraph 가 약한 P3·P4 에서만 도메인당 1회가 든다. 피드는 도메인당 하나라 캐시가 잘 들어서, 같은 사이트 기사 100건이면 호출은 1회다.

⚠️ **실시간 조건의 적중률은 아직 못 쟀다.** 지금 데이터로는 잴 수 없고 파이프라인에 붙여야 나온다.

**④ 의 "대조" 가 실제로 무너졌다** (2026-08-11 실호출). 구현이 대조 키에서 **쿼리를 통째로 떼고** 있었다. 추적 파라미터를 흡수하려던 것인데, 워드프레스 기본 퍼머링크가 `?p=1` 이라 항목 링크 `https://littlepoe.lol/?p=1` 이 경로 `/` 만 남아 **사이트 홈과 같은 키**가 됐다.

```
토큰이 건 URL   https://littlepoe.lol          → littlepoe.lol
피드 항목       https://littlepoe.lol/?p=1     → littlepoe.lol     ← 일치해 버린다
```

그래서 워드프레스 기본 글 *"Hello world!"* 의 안내문이 `text.body` 로, 그 `pubDate` 가 `published_at` 으로 들어갔다. **최신 항목을 집어오지 말자고 만든 대조가 정확히 그 일을 했다.**

손해가 더 큰 쪽은 Q9 다. 새로 깐 워드프레스는 그 글을 항상 하나 갖고 있어서 **`placeholder` 집단이 통째로 `article` 로 올라간다.** 위에 적은 넷 중 `longdog.lol` · `pumpga.fun` · `littlepoe.lol` 셋이 워드프레스 피드를 갖고 있었다.

대조 키를 파이프라인의 `normalizeUrl` 에 위임했다 — 추적 파라미터만 지우고 나머지 쿼리는 남긴다. **대가가 없다는 것도 확인했다**: 위 34개 피드에서 **항목 링크에 쿼리가 붙은 것이 0건**이다(`dailymail` 150건 포함). 같이 나온 것 하나 더 — 피드만 HTML 엔티티를 안 풀고 있어서 `&amp;` 가 `amp;b` 라는 없는 파라미터로 파싱되고 있었다.

### Q11. 도메인 지문 17필드는 어디로 가나 · `tokens.web{}` 는 어떻게 하나
`2026-08-11` · `닫힘`

**A.** **`Content.data{}` 로 간다. `tokens.web{}` 는 폐기한다.**

website 를 Content 로 다루기로 한 이상 지문도 거기 붙는 것이 맞다. 근거가 셋이다.

**첫째, `ContentDataInput` 의 정의가 정확히 그것이다.** 주석이 *"조회축이 아닌 값만 담는다 — 축이 되는 순간 최상위 필드로 승격한다"* 이고, 키 이름은 fetcher 반환 타입 그대로 쓴다(G-7). `registrar` · `techCount` · `hasTracking` · `certIssuer` 는 전부 배경정보라 이 정의에 맞는다.

**둘째, 저장소가 하나로 준다.** `tokens.web{}` 를 안 쓰면 `updateWeb()` 의 통째 교체 문제도 사라진다. 그 메서드는 부분 병합이 안 돼서 지문 일부만 갱신할 방법이 없었다.

**셋째, 도메인 공유 카운트가 자연스러워진다.** `fingerprints[]` 라는 별도 축을 만든 이유가 *"같은 도메인을 쓴 토큰이 또 있나"* 였는데, `data.domain` 집계로 끝난다. `token_links.linked_at` 이 as-of 절단축을 이미 갖고 있어 look-ahead 방어도 그대로 붙는다.

**앞서 내가 우려한 “복제” 는 실은 이득이다.** 같은 도메인을 30개 토큰이 쓰면 지문이 30번 복제된다고 걱정했는데, `resolveContent` 가 기존 행을 갱신하지 않으므로(G-6) **한 행에 모으면 첫 관측 시점이 영구 고착된다.** `techCount` · `hasTracking` · `server` · `ip` 는 `WEBSITE_VALUE_NATURE` 가 `mutable` 로 찍은 값이라, 반년 뒤 토큰이 같은 도메인을 걸어도 반년 전 값을 보게 된다. 복제하면 각 행이 자기 시점의 지문을 갖는다. 그것이 `tokens.web{}` 의 유일한 장점이었고, 복제로 그대로 얻는다. 비용은 실측 66B/토큰이라 무시할 수준이다.

**붙는 조건 — `platformKey` 는 정규화 URL 이다.** 여기서 복제 여부와 페이지 뭉갬이 갈린다. apex 도메인으로 잡으면 지문은 안 복제되지만 `coincommunities.org` 112개 페이지가 한 행으로 눌린다. `text` 를 뽑기로 한 이상 URL 이어야 한다 — 경로 있는 링크가 최소 51.7% 이고, 공유 도메인 키의 33.3% 가 서로 다른 페이지를 담고 있었다.

**따라오는 것 둘.**

- `fingerprints[]` 에서 `domain:` 을 뺀다. `image:` 만 남는다. 앞서 *"툴·참조 도메인이 지문에 들어가면 같은 팀이 아니라 같은 거래소를 쓴다를 세게 된다"* 고 한 문제가 여기서 자동으로 해결된다.
- `data.domain` 과 `data.domainCreatedAt` 은 실제로 조회축이라 `ContentDataInput` 주석 규칙과 어긋난다. MongoDB 는 중첩 필드에도 인덱스를 걸 수 있어 기능상 문제는 없지만, **왜 축인데 `data` 에 두는지를 주석으로 남긴다.** 나중에 누가 규칙만 보고 되돌리는 것을 막는다.
