토큰 이벤트가 들어와 contents 에 저장될 때까지 무엇을 거치는지 한 장에 그린다. 단계마다 어디까지 정해졌는지를 색으로 구분한다.
본선은 위에서 아래로 흐르고, 오른쪽으로 빠지는 것은 거기서 끝나는 갈래다. 테두리가 실선이면 정해지고 구현까지 된 것, 파선이면 정했는데 아직 코드가 없는 것, 흐린 채움이면 제안 단계다.
website URL 하나가 거치는 전 과정
⑥번 HTML GET 부터가 추출 규격 문서가 다루는 범위다. 그 위의 다섯 단계는 어느 문서에도 그려진 적이 없었다.
세 곳에서 갈라진다. ⑤에서 툴과 소셜이 호출 없이 빠지고(16.7%), ⑦에서 P4~P7 이 Content 없이 사유만 남기고, ⑨에서 도메인 지문은 web_site 에만 붙는다.
끝까지 가서 Content 가 되는 것은 P1~P3 로 링크 1,219건 · 배정 대상의 55.0% 다.
각 단계가 무엇을 정했고, 근거가 어디 있고, 구현이 어디까지 됐는지다.
단계 15개
§1 그림이 열한 칸인데 이 표가 열다섯 줄인 것은, 그림에서 한 칸에 묶은 것을 여기서는 결정 단위로 폈기 때문이다.
| # | 단계 | 무엇을 정했나 | 상태 | 근거 | 숫자 |
|---|---|---|---|---|---|
| 1 | 이벤트 수신 · URL 4종 | token.flow 를 받아 SolTrackerApiSdk 로 website·twitter·telegram·discord 를 얻는다. 이벤트 payload 에 이 네 필드가 없어 외부 호출이 불가피하다. | 확정 · 구현됨 | social-record.processor.ts | 호출 1회/토큰 |
| 2 | 라우팅 · web catch-all | 호스트 조회표에서 담당 소셜을 못 찾은 URL 이 SocialPlatform.WEB 으로 떨어진다. WebsiteFetcher.hosts 가 비어 있는 이유다 — 웹은 호스트를 소유하지 않는다. | 확정 · 구현됨 | social-fetcher.router.ts:47 | website 6,264건 · 전체의 24.1% |
| 3 | PSL 교정 | apex 도메인 계산을 하드코딩 6개에서 Public Suffix List 로 바꾼다. grok.me 46종 · vercel.app 26종처럼 주인이 다른 서브도메인이 한 키로 합쳐진다. | 제안 | 이번 조사 | 토큰링크의 19.1% 가 잘못 묶임 |
| 4 | 단축링크 해제 | tokenInfo 경계에서 한 번 풀고 아래로는 canonical 만 흘린다. maxRedirects:0 으로 홉을 직접 돌며 사설 대역을 차단한다. | 확정 · 미구현 | G-18 (decisions.md) | t.co 실측 4/4 가 301 로 풀림 |
| 5 | 종류 판정 1단 — 목록 | 툴 13 · 소셜 26 · 디렉터리 7 · 참조 상위 94 를 목록으로 건다. 툴과 소셜은 여기서 끝난다 — 도메인이 남의 것이라 지문도 페이지도 그 토큰을 말하지 않는다. | 제안 | website-kinds.html §1 | 목록 140개로 16.7% 를 호출 전에 처리 |
| 6 | 종류 판정 2단 — 토큰명 대조 | 토큰 이름이 도메인 문자열에 있으면 자기 사이트로 본다. 외부 호출이 0회다. | 제안 | 이번 조사 | 정밀도 88.9% · 토큰사이트 46.9% 적중 |
| 7 | HTML 받기 | 페이지를 평범한 GET 으로 받는다(최대 3MB). 이미 하고 있고, 지금은 OG 세 개만 뽑고 본문을 버린다. | 확정 · 구현됨 | website.fetcher.ts:175 | 실측 180 도메인 중 124 응답 · 69% |
| 8 | 표준 8종 파싱 | og · jsonld · article · twitter · meta · canonical · rss · oembed 를 전부 먼저 읽는다. 중간에 끊으면 “있는데 사이트 공통 문구” 인지 알 수 없다. | 확정 · 구현됨 | probe-website-extract.py | og 80.6% · jsonld 54.2% |
| 9 | 패턴 판정 — 캐스케이드 | R1 로그인벽 → R2 SPA → R3 미기입 → R4 사이트공통 → R5 제목·요약 → R6 제목만. 못 쓰는 경우를 먼저 걸러낸다. | 확정 · 구현됨 | website-content-mapping.html §5 | P1~P3 이 링크의 55.0% |
| 10 | 필드별 체인 | 칸마다 시도 순서가 다르다. text 는 OpenGraph 가 압도하는데 published 는 JSON-LD 가 이긴다. | 확정 · 구현됨 | 〃 §2 | 체인 다섯 · text 누적 82.3% |
| 11 | RSS 보정 | text_body 나 published 가 빈 경우에만 도메인당 1회 피드를 받아 URL 로 대조한다. P1·P2 는 호출이 0회 그대로다. | 확정 · 구현됨 | 〃 §2 · Q10 | 피드 24개 · 요약까지 22개 |
| 12 | 도메인 지문 — RDAP · urlscan | web_site 에만 부른다. 셋 다 무료다. 세 시간분할 일관 신호가 여기서 나온다 (731일+ · hasTracking · techCount 4개+). | 확정 · 구현됨 | website.fetcher.ts · urlscan.client.ts | 커버 RDAP 81.7% · urlscan 53% |
| 13 | ContentInput 조립 | platformKey 는 정규화 URL 이다. 페이지 정보는 최상위로, 도메인 지문 17필드는 data{} 로 간다. ContentDataInput 의 정의가 “조회축이 아닌 값” 이라 정확히 맞는다. | 확정 · 구현됨 | 오늘 확정 · Q11 | 지문 66B/토큰 |
| 14 | tokens.web{} 폐기 | 쓰지 않는다. 지문이 Content.data 로 가면서 자리가 없어졌다. fingerprints[] 에서도 domain: 을 빼고 image: 만 남긴다. | 확정 · 미구현 | 오늘 확정 · Q11 | token.model.ts:154 제거 대상 |
| 15 | 소유권 판정 | 본문에서 계약 주소를 대조해 match · other · placeholder · none · unseen 다섯으로 가른다. other 는 사칭 확정이다. | 제안 | Q6 · Q7 | tnos.global 26건이 전부 사칭 |
구현까지 된 것이 9개, 정했는데 코드가 없는 것이 2개, 제안 단계가 4개다. 제안 넷 중 셋(③ PSL · ⑤ 종류 판정 1·2단)은 외부 호출을 줄이는 쪽이라 없어도 파이프라인은 돌지만, 없으면 호출이 늘고 grok.me 46종이 한 도메인으로 세어진다.
그리면서 답이 없어 비워둔 자리다. 구현을 실제로 막는 것부터 적는다.
미확정 5건
1번이 그림에 직접 구멍을 낸다 — ⑨번 상자에서 나가는 화살표를 어디로 그릴지가 이것에 달렸다.
| # | 무엇이 | 왜 막나 | 어떻게 닫나 |
|---|---|---|---|
| 1 | P4~P7 에서도 도메인 지문을 받나 | Content 를 안 만드는데 RDAP·urlscan 을 부를 것인가. 부르면 저장할 자리가 없고, 안 부르면 그 토큰의 도메인 나이를 영영 못 본다. | web_site 이면서 P4~P7 인 경우가 몇 건인지 세어 정한다 |
| 2 | 종류 판정기를 어디에 두나 | 1단은 classify() 가 순수·무료라 맞는데, 2단은 토큰명이 필요해서 ParsedUrl 로는 안 된다. | Generator 진입 시점으로 내리거나, classify() 의 입력을 넓힌다 |
| 3 | tags 를 계약에서 뺄지 | ContentInput.tags 는 필수 배열인데 실측 커버리지가 P1 에서만 절반이다. | 빈 배열로 두고 P1 에서만 채운다 — 다만 “안 채운 것” 과 “없는 것” 이 구분 안 된다 |
| 4 | web_site 안의 참조 비중 | 목록에 없는 참조가 web_site 로 떨어진다. thaipbs.or.th 13건이 실제로 그랬다. | 657개 도메인을 훑어야 한다 — qna/website.md Q3 이 열려 있다 |
| 5 | google.com 66건 | 검색 결과 · docs. · sites. 가 섞여 있고 G-4 의 검색 규칙과 겹친다. | 66건의 내부 구성을 본다 — Q5 가 열려 있다 |
이 그림이 담지 못하는 것 둘.
첫째, 실패 경로가 없다. 403 봇 차단이 조사에서 74건이었고 사이트가 죽는 경우도 16곳 중 5곳이었다. 재시도 정책은 FetchStatus 와 retryUnconverged 가 이미 다루지만 website 종류별로 무엇이 재시도 대상인지는 안 정했다.
둘째, 다른 7소셜과의 관계가 없다. 단축링크를 풀면 목적지가 인스타그램일 수 있고(실측 4건 중 1건), 그러면 website 가 아니라 그쪽 fetcher 로 넘어가야 한다. 그 되돌아가는 화살표가 이 그림에 없다.
단계 정의는 scaffold/render-website-pipeline.py 상수이고, 숫자는 같은 폴더의 JSON 에서 읽는다. 데이터가 바뀌면 그림의 숫자도 따라간다.