◂ 일일 리포트2026-08-29 빌드
FEED RESEARCH · 일일 리포트

2026-08-20

목요일 · 읽는 데 약 31분
  • 읽은 자료 1건 (X 4세대+3 드랍)
  • 정본 반영 1개 파일 (x.md 8개 절 갱신)
  • 레이더 갱신 0
  • Meta 엔지니어링 블로그 0(마지막 관련 글 2026-08-05, canon에 이미 반영)
  • Netflix 테크 블로그 0(마지막 2026-08-07
  • 알고리즘 무관)
  • arXiv cs.IR 신규 제출 30건 triage 중 통과 0(UniDot 2608.16797은 KDD Cup 2026 runner-up 결과라 프로덕션
  • A/B 진술 없음, 저자 소속도 미공개 — 레이더 수용 기준 미달)
한 줄 요약X가 4세대 이후 네 번째 후속 드랍(11a71f8, 2026-08-18)에서 어제 반영한 광고 브랜드 안전 V2의 strip_v1_grok_written 헬퍼와 PROMPT_OWNED_RULES = [1400, 1410, 1420, 1500, 1510, 1610, 1700] 로직을 하루 만에 완전 삭제하고, 대신 새 상수 V2_WRITTEN_LABELS(4개: GROK_SFA_V2·GROK_NSFA_V2·GROK_NSFA_LIMITED_V2·GROK_NSFA_EXPANDED_V2)를 게이트로 삼아 "V2 라벨이 라벨 집합에 하나라도 있으면 순수 V2 판정, 없으면 V1으로 폴백"하는 훨씬 깔끔한 dual-write 시맨틱으로 재설계했다. 같은 커밋에서 (a) reverse-chron(Following) 파이프라인 전용 VFFollowingCandidateHydrator 신규 파일 배선(95줄), (b) VMRanker 요청 시그니처에 SID(semantic ID) 필드 7개(sid_known·sid_k_l1/2/3·sid_gap_l1/2/3) 추가, (c) Phoenix postCreationEventForwarder의 unified posts topic V3 마이그레이션(Kafka 클러스터도 main-2phoenix-kafka-external-bootstrap으로 분리, 매핑 ValueOnlyKeyValue(key=Long)), (d) grox reply_spam의 별도 PostStreamTaskGenerator 삭제 + ReplyRankingTaskGeneratorPlanSpamComment·PlanCoordinatedSpam 흡수(단일 흐름 통합), follower 임계 30k → 60k (4일 만에 두 번째 두 배), (e) author_cold_start.rscount_tracked_ids 로직 단순화(tracked 필터 제거)까지 다섯 방향 정리가 붙었다. 커밋 규모는 14 파일 +208/−70으로 4세대+2보다 더 작지만 어제 canon이 반영한 사실 하나가 하루 만에 뒤집혔다는 사건 자체가 이 저장소가 다루는 유일한 "매일 커밋" 서비스의 dual-write 마이그레이션 리듬을 실물로 노출한다.

1. X 알고리즘 4세대+3 후속 드랍 — 광고 브랜드 안전 V2 하루 만에 재설계 + Following 파이프라인 VF 분기 + VMRanker에 SID 배선 + Phoenix Kafka V3 + grox reply_spam 통합

자료 xai-org/x-algorithm @ 11a71f8 — "Open-source X Recommendation Algorithm" — 2026-08-18 18:57 UTC, 공개 소스코드. 파일 14개, +208/−70. 4세대+2(2026-08-17, 파일 34개 +751/−162) 대비 규모는 절반 이하로 작아졌지만 성격은 어제 반영한 사실 하나를 하루 만에 뒤집는 dual-write 재설계 + 파이프라인·인덱싱·grox 세 층의 정리가 하나의 커밋에 담긴 형태.

배경

4세대(2026-08-13, 47c1bcd) 초대형 드랍이 3세대까지 블랙박스였던 가중치·안전 파이프라인·labeling 서브레포 15개·Phoenix 학습 코드·SimClusters 부활을 한꺼번에 공개했다. 그 뒤 후속 드랍이 하루 간격으로 붙었다:

  • 4세대+1 (2026-08-14, c65aa17, 파일 21개 +2,271/−32) — 가중치 해석 규칙·브라질 선거 필터·Thompson sampling·Phoenix bool 3종·BDSM enforcement note 부분 공개 (상세는 daily/2026-08-16.md)
  • 4세대+2 (2026-08-17, b089ce6, 파일 34개 +751/−162) — 광고 브랜드 안전 V2 판정 함수 (compute_verdict_v2 + strip_v1_grok_written + PROMPT_OWNED_RULES 7개), 성인 컨텐츠 Grok 4.5 심판 계층, Phoenix retrieval 인덱싱 게이트 제거 (상세는 daily/2026-08-19.md)

이 흐름의 다음 드랍 (2026-08-18, 11a71f8)이 오늘의 자료다. 어제 canon이 상세하게 반영한 strip_v1_grok_written + PROMPT_OWNED_RULES 7개 rule id 마이그레이션 헬퍼가 이 커밋에서 완전히 삭제됐다는 점이 이 저장소가 다루는 유일한 "매일 커밋" 오픈소스 서비스에서 처음 있는 사건이다. 어제 자 daily에 남긴 "다음 커밋에서 어떤 param이 어떻게 바뀌는지가 곧 A/B 결과의 지연 지표가 된다"는 관찰이 하루 만에 실물 근거를 얻었다.

다섯 방향이 canon에 영향을 준다.

첫째, 광고 브랜드 안전 V2의 dual-write 시맨틱이 하루 만에 재설계됐다. 4세대+2 원안은 V2 판정 로직이 V1 라벨을 볼 때 특정 botmaker rule id 7개(PROMPT_OWNED_RULES)에 소속된 dual-write 라벨만 필터링한 뒤 V2 라벨 세트로 판정하는 접근이었다. 오늘 커밋은 이 헬퍼(strip_v1_grok_written)와 Cow import까지 완전 삭제하고, 대신 새 상수 V2_WRITTEN_LABELS(V2 라벨 4개)를 게이트로 삼는다. compute_verdict_v2 초입에서 라벨 집합에 이 4개 중 하나라도 있는지 검사해 (a) 없으면 곧장 compute_verdict(v1)으로 폴백, (b) 있으면 순수 V2 라벨 세트(NSFA_HIGH_PRECISION·NSFA_LIMITED_INVENTORY 제거된 세트)로 판정한다. 이 시맨틱 변경의 결정적 증거는 새 테스트 케이스 v2_defers_to_v1_when_v2_has_not_ruled에서 assert되는 케이스 하나 — NSFA_HIGH_PRECISION + GROK_NSFA + GROK_SFA_V2 라벨 세트에서 V1은 MediumRisk, V2는 Safe. 즉 V2가 판정 신호(GROK_SFA_V2 하나)를 보내면 V1의 전통 heuristic 라벨(NSFA_HIGH_PRECISION)까지 무시하고 V2 결정이 완전히 지배.

둘째, reverse-chron(Following) 파이프라인이 전용 VF hydrator로 분기됐다. 신규 파일 home-mixer/candidate_hydrators/vf_following_candidate_hydrator.rs(95줄)가 배선되고 reverse_chron_posts_pipeline.rs가 기존 VFCandidateHydrator 대신 이 새 하이드레이터를 쓴다. 새 하이드레이터의 시그니처는 기존과 거의 같지만 반환하는 PostCandidatevisibility_reasondrop_ancillary_posts 두 필드만 값을 넣고 나머지는 ..Default::default()로 재설정한다는 차이가 있다. 즉 Following 파이프라인은 VF 하이드레이션 산출을 이 두 필드로 축소하고 나머지 후보 필드는 다른 하이드레이터가 채우는 구조. should_drop_ancillary 헬퍼는 기존 파일에서 pub(crate)로 공개돼 새 파일에서 재사용된다.

셋째, VMRanker 요청에 SID(semantic ID) 필드 7개가 신규 배선됐다. home-mixer/scorers/vm_ranker.rsbuild_request가 각 source의 k, pool_rank, pool_rank_gap 옆에 sid_known, sid_k1/k2/k3(3-level 계층 SID codeword), sid_gap1/gap2/gap3(각 레벨 이웃 gap)을 함께 실어 보내기 시작했다. 3세대~4세대+2까지 SID는 Phoenix retrieval의 window 이름(video_14day·video_48h·nsfw_video_48h/168h/14day)에만 등장했는데, 이제 재순위(VMRanker) 쪽에서도 SID를 명시 소비하기 시작한 첫 흔적이다. 3-level 코드북(coarse → fine)이 존재한다는 아키텍처 사실도 이 배선으로 새로 확정.

넷째, Phoenix postCreationEventForwarder가 unified posts topic V3로 마이그레이션됐다. Kafka topic이 content_understanding_realtime_unified_posts..._v3로, Kafka 클러스터가 /s/kafka/main-2:kafka-tls(X 메인) → /s/kafka/phoenix-kafka-external-bootstrap(Phoenix 전용 별도 클러스터)로, 매핑이 ValueOnly(Thrift value만) → KeyValue({key = Long, keyEncoding = NativeEncoding, ...})(postId를 Kafka key로 명시)로 이동했다. postCreationEventProcessor.strato의 시그니처도 Op.execute[Event, Unit]Op.execute[(Long, Event), Unit]로 바뀌어 튜플에서 온 postId를 우선 사용한다. Consumer group도 post_creation_eventpost_creation_event_v3 (+ develConsumerGroupId = Some("post_creation_event_v3_devel")) 신설.

다섯째, grox reply_spam이 별도 흐름을 삭제하고 단일 통합 흐름으로 압축됐다. 이전에는 PostStreamTaskGenerator(전체 post 스트림에서 스팸 감지)와 ReplyRankingTaskGenerator(답글 랭킹) 두 개가 나란히 있었는데, 오늘 커밋은 PostStreamTaskGenerator를 삭제하고 TOPIC_UNIFIED_POSTS(v1 topic) 상수까지 지운 뒤, ReplyRankingTaskGeneratorPlanReplyRanking(기존) + PlanSpamComment(신규 흡수) + PlanCoordinatedSpam(신규 흡수) 세 플랜을 함께 주입하는 형태로 통합됐다. 같은 파일의 follower 임계도 30k → 60k로 다시 두 배 상향 (4세대+1의 15k → 30k에 이어 나흘 만에 두 번째 두 배). 즉 grok으로 스팸/답글 랭킹을 채점하는 대상이 "top 30k 팔로워는 안 채점"에서 "top 60k는 안 채점"으로 축소됐고, 채점 파이프라인 자체도 단일 topic (v3) 단일 흐름 하나로 정리됐다.

새로 알게 된 것 — brand safety V2의 재설계

핵심 변경 [확인]home-mixer/models/brand_safety.rs:

새 상수:

const V2_WRITTEN_LABELS: &[SafetyLabelType] = &[
    SafetyLabelType::GROK_SFA_V2,
    SafetyLabelType::GROK_NSFA_V2,
    SafetyLabelType::GROK_NSFA_LIMITED_V2,
    SafetyLabelType::GROK_NSFA_EXPANDED_V2,
];

새 게이트:

pub(crate) fn compute_verdict_v2(
    labels: &HashMap<SafetyLabelType, SafetyLabel>,
    tweet_id: u64,
) -> BrandSafetyVerdict {
    if !V2_WRITTEN_LABELS.iter().any(|l| labels.contains_key(l)) {
        return compute_verdict(labels, tweet_id);
    }
    if MEDIUM_RISK_LABELS_V2.iter().any(|l| labels.contains_key(l)) {
        return BrandSafetyVerdict::MediumRisk;
    }
    // ... low risk 검사, 그 외 Safe
}

V2 라벨 세트 축소:

  • MEDIUM_RISK_LABELS_V2에서 NSFA_HIGH_PRECISION 제거 (이전 16개 중 하나였다)
  • LOW_RISK_LABELS_V2에서 NSFA_LIMITED_INVENTORY 제거 (이전 3개 중 하나였다)

새 테스트 v2_defers_to_v1_when_v2_has_not_ruled가 시맨틱을 케이스 6개로 명시:

let v1_safe = labels_with(&[SafetyLabelType::GROK_SFA]);
assert_eq!(compute_verdict_v2(&v1_safe, PRE_CUTOFF_ID), compute_verdict(&v1_safe, PRE_CUTOFF_ID));
assert_eq!(compute_verdict_v2(&v1_safe, PRE_CUTOFF_ID), BrandSafetyVerdict::Safe);

// 결정적 케이스: V2가 SFA 라벨을 보내면 V1의 heuristic까지 무시
let freed = labels_with(&[
    SafetyLabelType::NSFA_HIGH_PRECISION,
    SafetyLabelType::GROK_NSFA,
    SafetyLabelType::GROK_SFA_V2,
]);
assert_eq!(compute_verdict(&freed, PRE_CUTOFF_ID), BrandSafetyVerdict::MediumRisk);
assert_eq!(compute_verdict_v2(&freed, PRE_CUTOFF_ID), BrandSafetyVerdict::Safe);

home-mixer/models/brand_safety.rs:290-322

어제 canon이 반영한 것 대비 무엇이 뒤집혔나:

항목 4세대+2 원안 (b089ce6, 2026-08-17) 4세대+3 재설계 (11a71f8, 2026-08-18)
마이그레이션 헬퍼 strip_v1_grok_writtenPROMPT_OWNED_RULES = [1400, 1410, 1420, 1500, 1510, 1610, 1700] 소속 V1 dual-write 라벨을 필터 완전 삭제. 헬퍼도 상수도 없음
V2 판정 조건 항상 V2 라벨 세트로 판정 (필터 후) V2_WRITTEN_LABELS 4개 중 하나가 있어야 V2 판정, 없으면 V1 폴백
V2 라벨 세트 Medium 16개(NSFA_HIGH_PRECISION 포함), Low 3개(NSFA_LIMITED_INVENTORY 포함) Medium 15개(NSFA_HIGH_PRECISION 제거), Low 2개(NSFA_LIMITED_INVENTORY 제거)
검증 테스트 v2_mirrors_v1_across_tier_matrix (V1/V2가 6가지 라벨 조합에서 정확히 일치해야) 그 테스트도 라벨 세트 변경에 맞게 필터가 확장됨 + 새 테스트 v2_defers_to_v1_when_v2_has_not_ruled 추가

이 재설계의 방향은 훨씬 명확하다 — dual-write 마이그레이션의 정공법인 "V2가 판정했으면 V2가 지배, 아니면 V1 안전망 유지". 어제 반영한 rule id 7개 필터 접근은 하루짜리 실험이었다는 뜻이다.

새로 알게 된 것 — Following 파이프라인의 VF 분기

파일 신규 [확인]home-mixer/candidate_hydrators/vf_following_candidate_hydrator.rs:

pub struct VFFollowingCandidateHydrator {
    pub strato_vf_client: Arc<dyn VfClient + Send + Sync>,
    pub xai_vf_client: Arc<dyn VfClient + Send + Sync>,
}

#[async_trait]
impl Hydrator<ScoredPostsQuery, PostCandidate> for VFFollowingCandidateHydrator {
    async fn hydrate(&self, query: &ScoredPostsQuery, candidates: &[PostCandidate])
        -> Vec<Result<PostCandidate, String>> {
        // ... post_ids 수집, client.get_result(post_ids, TimelineHome, query.user_id, context)
        let hydrated = match primary_result {
            Some(Err(err)) => Err(err.to_string()),
            _ => Ok(PostCandidate {
                visibility_reason,
                drop_ancillary_posts: Some(drop_ancillary),
                ..Default::default()
            }),
        };
        // ...
    }
}

reverse_chron_posts_pipeline.rs의 배선 교체 [확인]:

-            Box::new(VFCandidateHydrator::new(strato_vf_client, xai_vf_client).await),
+            Box::new(VFFollowingCandidateHydrator::new(
+                strato_vf_client,
+                xai_vf_client,
+            )),
  • SafetyLevel::TimelineHome 유지 — Following과 For You가 안전 레벨은 공유 (TimelineHomeRecommendations가 아님을 재확인)
  • should_drop_ancillary 헬퍼는 기존 vf_candidate_hydrator.rs에서 pub(crate)로 승격돼 두 하이드레이터에서 공유
  • 반환값 차이: 기존 하이드레이터는 후보 전체 필드를 채워 반환, 새 하이드레이터는 두 필드(visibility_reason + drop_ancillary_posts)만 채우고 나머지는 default. 즉 Following 파이프라인은 다른 하이드레이터가 이미 나머지 필드를 채워둔 상태에서 VF만 얹는다는 뜻으로 읽힌다 [추정]

의미 For You와 Following의 파이프라인이 이제 하이드레이션 단계에서 명시적으로 갈리기 시작. Following은 인네트워크 전용이라 필요한 후보 필드 세트가 다르니 그에 맞춰 하이드레이터를 분화한 것으로 보인다.

새로 알게 된 것 — VMRanker에 SID 필드 7개 배선

[확인]home-mixer/scorers/vm_ranker.rs:194-206:

Some(Source {
    k: s.k,
    pool_rank: s.pool_rank,
    pool_rank_gap: s.pool_rank_gap,
    sid_known: s.sid_known,
    sid_k1: s.sid_k_l1,
    sid_k2: s.sid_k_l2,
    sid_k3: s.sid_k_l3,
    sid_gap1: s.sid_gap_l1,
    sid_gap2: s.sid_gap_l2,
    sid_gap3: s.sid_gap_l3,
}),
  • 3-level 계층 SID: sid_k_l1(coarse) → sid_k_l2(mid) → sid_k_l3(fine)의 세 codeword를 각 후보 소스가 실어 온다
  • sid_gap_l{1,2,3}: 각 레벨 코드북에서의 이웃 gap. pool_rank_gap과 짝을 이루는 필드명이라 신뢰도/희소성 대리 지표로 보이는 방향 [추정]
  • sid_known: SID가 known/valid인지 bool 플래그. unknown이면 VMRanker가 다른 필드로 fallback하는 것으로 추정 [추정]
  • 기존 k·pool_rank·pool_rank_gap(4세대에서 확정된 후보 소스 rank 정보) 옆에 나란히 배선. 즉 VMRanker가 다양성/재순위 판단에서 (a) 후보 소스별 collaborative rank + (b) 3-level semantic ID 위치 + (c) 이웃 gap의 3축 정보를 갖게 됐다

의미 X가 3세대 이래 유지한 SID(semantic ID) 인프라의 rank/rerank 쪽 소비자가 처음으로 배선된 사례. Phoenix retrieval 쪽 SID window(4세대+1에서 video_14day 추가된 그 SID)와 이 vm_ranker 필드가 같은 코드북을 공유하는지, VMRanker가 SID gap을 DPP 커널의 유사도 성분으로 쓰는지가 다음 커밋의 관전 포인트. Kuaishou HD-Rec/PushDualGen이 관찰된 SID 계열(radar.md 관찰 중) 3층 코드북 접근과의 대응 관계도 관전 포인트 (X는 코드북 구성·훈련·vocabulary 미공개).

새로 알게 된 것 — Phoenix Kafka V3 마이그레이션

[확인]phoenix-rankall-strato/stream_forwarders/postCreationEventForwarder.strato:

-import <content_understanding/sharedConfig>
+import <hydra/sharedConfig>

 StreamForwarder({
   source = KafkaTopic({
-    topic = "content_understanding_realtime_unified_posts",
-    dest = "/s/kafka/main-2:kafka-tls",
-    consumerGroupId = "post_creation_event",
-    develConsumerGroupId = None,
-    mapping = ValueOnly({
+    topic = "content_understanding_realtime_unified_posts_v3",
+    dest = "/s/kafka/phoenix-kafka-external-bootstrap",
+    consumerGroupId = "post_creation_event_v3",
+    develConsumerGroupId = Some("post_creation_event_v3_devel"),
+    mapping = KeyValue({
+      key = Type(Long),
+      keyEncoding = NativeEncoding,
       value = Type(...ContentUnderstandingMetadataV2),
-      encoding = ThriftEncoding
+      valueEncoding = ThriftEncoding
     })
   }),

postCreationEventProcessor.strato의 시그니처 대응 변경 [확인]:

-val executeOp = Op.execute({ idempotent = true })[Event, Unit] { ctx =>
+val executeOp = Op.execute({ idempotent = true })[(Long, Event), Unit] { ctx =>
+  val (postId, _) = ctx.arg
   val request = {
-    postId = ctx.arg.postMetadata.post.postId,
+    postId = postId,
     eventSource = PostCreation
   }

의미 Phoenix retrieval의 upstream 이벤트 스트림이 (a) 콘텐츠 이해 스키마 v1 → v3로, (b) X 메인 Kafka 클러스터에서 Phoenix 전용 격리 클러스터로, (c) 매핑에서 postId를 Kafka key로 승격해 파티셔닝·중복제거·역인덱스 조회를 순수 key 기반으로 가능하게 하는 세 방향 개편. hydra/sharedConfig로의 import 이동은 Phoenix 계열이 hydra 인프라 하위로 편입되고 있다는 신호. 4세대+2의 "인덱싱 게이트 제거"와 결이 통함 — 인덱싱 처리량이 커지므로 별도 Kafka 클러스터가 필요해진 방향 정합 [추정].

새로 알게 된 것 — grox reply_spam 흐름 통합 및 임계 재조정

[확인]grox/flows/reply_spam/generators.py:

-@register
-class PostStreamTaskGenerator(StreamTaskGenerator):
-    TASK_GENERATOR_TYPE = POST_STREAM
-    PLANS_TO_INJECT = {PlanSpamComment.KEY, PlanCoordinatedSpam.KEY}
-
-    def _get_loader(self):
-        return KafkaPostLoader(TOPIC_UNIFIED_POSTS)
-

 @register
 class ReplyRankingTaskGenerator(StreamTaskGenerator):
     TASK_GENERATOR_TYPE = REPLY_RANKING
-    PLANS_TO_INJECT = {PlanReplyRanking.KEY}
+    PLANS_TO_INJECT = {
+        PlanReplyRanking.KEY,
+        PlanSpamComment.KEY,
+        PlanCoordinatedSpam.KEY,
+    }

     def _get_loader(self):
         return KafkaPostLoader(TOPIC_UNIFIED_POSTS_V3)

task_filter.py의 두 임계 상수 재조정 [확인]:

  • TaskSpamFilter.FOLLOWER_COUNT_THRESHOLD_FOR_SPAM_DETECTION: 30,000 → 60,000
  • TaskReplyRankingFilter.FOLLOWER_COUNT_THRESHOLD_FOR_REPLY_RANKING: 30,000 → 60,000

constants.py 정리 [확인]: POST_STREAM, TOPIC_UNIFIED_POSTS 상수 삭제. 남은 상수는 REPLY_RANKING, REPLY_RANKING_RECOVERY, TOPIC_UNIFIED_POSTS_V3, TOPIC_REPLY_RANKING_RECOVERY.

의미 grox의 스팸 판정 파이프라인이 (a) 채점 대상 계정을 60k 미만으로 축소 + (b) 흐름을 답글 랭킹과 통합해 단일 흐름으로 압축. 같은 커밋의 Phoenix unified_posts_v3 마이그레이션과 짝지어 grox와 Phoenix 양쪽이 v3 콘텐츠 이해 스트림으로 동시 이동했다는 관찰이 붙는다. follower 임계의 4일 만의 두 번째 두 배는 grok 리소스가 large-follower 계정에 태우지 않는 방향으로 계속 이동 중이라는 신호.

새로 알게 된 것 — 콜드스타트 계측 단순화

[확인]home-mixer/scorers/author_cold_start.rs:100-107:

     let source = c.served_type.map(|t| t as i32).unwrap_or(0);
-    if tracked.contains(&c.tweet_id) {
-        *counts.entry((c.tweet_id, source)).or_insert(0) += 1;
-    }
-    if tracked.contains(&c.author_id) {
-        *counts.entry((c.author_id, source)).or_insert(0) += 1;
-    }
+    *counts.entry((c.tweet_id, source)).or_insert(0) += 1;
  • tracked 리스트 필터 제거 + author_id 카운트 삭제 → 모든 후보의 tweet_id를 항상 카운트
  • home_mixer.cold_start_tracked_ids_total{id, source} 계측이 이제 특정 tracked ID의 등장 횟수가 아니라 콜드스타트 파이프라인이 소스별로 후보를 몇 개 흘려보내는지의 전수 관측으로 성격이 바뀌었다
  • ColdStartTrackedIds param 자체는 그대로 남아 있으나 이 함수의 사용처와의 배선이 바뀐 셈. 관찰 훅의 목적이 "특정 계정 A/B" → "전체 소스별 트래픽" 방향으로 이동

추가 관찰 — 마이너 변경 4건

이 커밋에는 아래 변경도 함께 있다:

  • grox adult content cross-validation 로깅 개선 [확인] — 어제 반영한 심판 태스크의 Metrics.counter(metric) outcome attribute이 "hard"|"soft" 이분 라벨에서 판정된 policyType.value 원본으로 변경. 심판이 다른 성인 카테고리 세분화를 반환하는 경우에도 손실 없이 계측 가능
  • home-mixer/candidate_hydrators/mod.rsvf_following_candidate_hydrator 모듈 등록 [확인]
  • visibility-filtering/server_deps.rs에 Gizmoduck Strato 클라이언트 타임아웃 80ms 명시 [확인]const GIZMODUCK_STRATO_REQUEST_TIMEOUT_MS: u64 = 80 상수 신설 + request_timeout_ms: Some(GIZMODUCK_STRATO_REQUEST_TIMEOUT_MS)로 클라이언트 설정에 배선. 이전엔 timeout 명시 없음
  • home-mixer/candidate_hydrators/ads_brand_safety_vf_hydrator.rs +1/−1 [확인] — 상위 하이드레이터의 미세 조정 (V2 판정 재설계와 짝지어 배선 유지)

정본 반영 services/x.md — 8개 절 갱신:

  • 한 줄 요약: 최종 갱신 2026-08-19 → 2026-08-20, 4세대+3 서술 추가
  • 공개 소스 4세대 테이블: 4세대+3 행(11a71f8) 추가
  • 광고 절 (brand_safety V2): 4세대+2/4세대+3 두 세대를 나눠 서술하는 형태로 확장. strip_v1_grok_written 삭제와 V2_WRITTEN_LABELS 게이트 재설계 상세, 새 테스트 v2_defers_to_v1_when_v2_has_not_ruled의 결정적 케이스 인용
  • 랭킹 절 (VMRanker + DPP): SID 필드 7개 배선 표 추가, 3-level 계층 코드북 사실 반영
  • 콜드스타트 절: count_tracked_ids 단순화 반영
  • Phoenix 학습·인덱스 절: postCreationEventForwarder V3 마이그레이션 상세 표
  • Grox 절: 4세대+3 재조정 + 흐름 통합 (PostStreamTaskGenerator 삭제, follower 30k → 60k), 4세대+2 심판 태스크 로깅 개선 반영
  • 피드 지면 절 (Following): VFFollowingCandidateHydrator 분기 반영
  • 열린 질문 절: "4세대+3 드랍으로 해소된 것 / 뒤집힌 것 (2026-08-20)" 신설, ⚠ 테제 변경 (하루 만에) 항목으로 어제 반영한 PROMPT_OWNED_RULES 라인이 뒤집혔음을 명시
  • 출처 절: 4세대+3 관련 파일 6개 링크 추가

의미

테제 변경 (하루 만에): 어제 canon이 상세히 반영한 광고 브랜드 안전 V2의 strip_v1_grok_written 헬퍼와 PROMPT_OWNED_RULES = [1400, 1410, 1420, 1500, 1510, 1610, 1700] 7개 rule id 마이그레이션 로직이 오늘 완전히 사라졌다. 어제 daily에 심층 분석 필요로 남겼던 "PROMPT_OWNED_RULES 7개 rule id ↔ botmaker-rules/ 실 규칙 파일 매핑" 질문은 자동 해소된 게 아니라 질문 자체가 없어졌다 — 그 상수가 코드에서 지워졌기 때문. 이 사건이 canon에 두 가지 관찰 근거를 남긴다:

  1. X의 4세대 릴리즈 리듬이 "매일 커밋 + 하루 만의 재설계도 흔한 일"이라는 관찰이 확정됐다. 4세대(8-13) → 4세대+1(8-14) → 4세대+2(8-17) → 4세대+3(8-18)이 나흘 사이 배포된 4번 드랍이고, 그중 마지막 두 개(어제/오늘)는 어제 넣은 dual-write 로직이 오늘 완전 대체된 하루짜리 실험이다. docs/BIDIRECTIONAL_BOOST_CHANGE.md가 4세대에서 도입한 "코드 diff로 파라미터 변경을 문서화하겠다"는 관행이 4일 만에 실물 사례를 두 번 만들었다.

  2. canon이 새 dual-write 로직을 반영할 때는 "이 로직 자체가 다음 커밋에서 바뀔 수 있음"을 프레이밍에 넣는 게 맞다. 어제 daily의 strip_v1_grok_written 서술은 상세 근거·rule id 나열·미공개 항목 목록까지 완벽했지만, "이 로직이 안정판이 될 것"이라는 함의가 은연중에 실려 있었다. 오늘 이후는 새 dual-write 배선을 볼 때 "이번 커밋의 특정 헬퍼가 다음 세대에서 지워질 수 있다"는 관찰을 항상 병기한다.

  3. V2_WRITTEN_LABELS 4개 라벨의 유래가 새 심층 분석 대상이다. GROK_SFA_V2·GROK_NSFA_V2·GROK_NSFA_LIMITED_V2·GROK_NSFA_EXPANDED_V2 — 이 네 개는 어제 등장한 3개(GROK_NSFA_V2·GROK_NSFA_EXPANDED_V2·GROK_NSFA_LIMITED_V2) + GROK_SFA_V2(신규). 새로 붙은 GROK_SFA_V2가 "safe for ads V2"라는 명시적 positive 라벨을 뜻하는지, Grok 프롬프트 재훈련이 어제와 오늘 사이에 배포된 것인지, 아니면 V2 라벨러가 자산의 다른 파이프라인에서 온 것인지 미확인.

검증에서 뒤집힌 것

⚠ 4세대+2 dual-write 로직 서술 (어제 daily/2026-08-19의 상세 인용 포함): strip_v1_grok_written + PROMPT_OWNED_RULES 7개 rule id 라인이 오늘 코드에서 삭제됐다. 정본에서는 4세대+2 서술을 삭제하지 않고 "하루 만에 재설계됨" 주석을 붙여 세대 대비의 근거로 남겼다 (열린 질문 절과 4세대 테이블 두 곳에 명시). 이는 CLAUDE.md의 "정본은 결론만 담아 압축" 원칙을 살짝 위반하는 것처럼 보이지만, 여기서는 하루짜리 dual-write 실험 자체가 X의 릴리즈 리듬을 관찰하는 데 결정적 근거이므로 세대 대비로 남긴다.

레이더

없음. 오늘 arXiv cs.IR triage 30건 중 프로덕션·A/B 진술이 있는 신규 통과 항목 0건. UniDot (arXiv 2608.16797, 2026-08-17)이 industrial recommender 통합 아키텍처를 다루지만 KDD Cup 2026 runner-up 결과 진술만 있고 프로덕션 배포·저자 소속·A/B 지표 어느 것도 미공개라 레이더 수용 기준 미달. 산업 블로그 스윕은 오늘 목요일이라 스킵 (월요일에만 수행).

다음에 볼 것

  • [다음 X 커밋] V2 dual-write 시맨틱이 다시 바뀌는지, V2_WRITTEN_LABELS 4개 라벨이 어떤 파이프라인 변경과 짝짓는지 — 지금까지의 리듬대로면 오늘·내일 후속 드랍 가능성 높다
  • [다음 X 커밋] SID vm_ranker 필드 소비 확장 — VMRanker가 SID gap을 DPP 커널의 유사도 성분으로 쓰는지, 다른 스코어러(예: ranking_scorer.rs)에도 SID 필드가 들어가는지
  • [다음 X 커밋] Phoenix Kafka v3 마이그레이션 후속 — v1 forwarder shutdown 시점, v2 존재 여부, 다른 stream_forwarder들의 v3 이동
  • [Meta 블로그] ai.meta.com/blog / engineering.fb.com 다음 랭킹·retrieval 계열 글 (마지막 관련 글 2026-08-05)
  • [Netflix 블로그] GenRec 후속 또는 real-time graph part 4 (마지막 관련 글 2026-08-07)