Skip to content

異體字落地替換 S3:人物子資源與 BIOG_MAIN 全面串接,並補上兩形並存查重 - #1273

Merged
sudoghut merged 1 commit into
developfrom
feat/variant-replace-subresources
Aug 21, 2026
Merged

異體字落地替換 S3:人物子資源與 BIOG_MAIN 全面串接,並補上兩形並存查重#1273
sudoghut merged 1 commit into
developfrom
feat/variant-replace-subresources

Conversation

@sudoghut

Copy link
Copy Markdown
Member

這一步做了什麼

docs/CHAR_VARIANT_MAP_TEXT_COLUMN_ROLLOUT_PLAN.mdS3:異體字落地替換從「手掛少數姓名欄」改為由 VariantReplaceScope 按「表.欄位型別」決定的整列替換,涵蓋 21 個人物子資源 v2 handler、體系外三個例外 handler 與 BIOG_MAIN 三處寫入。

  • 通用掛鉤(新 Concerns\AppliesVariantReplacement)插在兩個抽象基底類別的 handle() 內,早於 preprocess、PK 計算與查重 ⇒ 文本型 PK 成員(ALTNAME_DATA.c_alt_name_chnASSOC_DATA.c_text_titleBIOG_SOURCE_DATA.c_pages)替換後的值成為實際落庫的 PK。noticeshandle() 統一掛上,成功與 409/422 都帶。
  • 移除 Altname 子類重複的 replaceStrict() + assign(會讓別名替換通知靜默消失),基底改用 merge。
  • BIOG_MAIN 改為整列 replaceRow()c_notesc_tribe 等原本全漏),但 v2 路徑把範圍收窄到「使用者本次實際變更的欄」——它的 payload 是「原列 ∪ changes」,整列替換等於回溯改寫使用者沒碰過的欄(D6 明說不做)。

Review 過程中補上的防護(都不在原計畫的 S3 清單裡)

問題 為什麼嚴重
v2 create/改鍵缺 D7 兩形並存查重 既有列可能存變體形(D6 不回溯校正),只比對替換後的值會鑄出兩列語義相同、字形不同的資料,而唯一鍵擋不住——比完全不替換更糟。抽成 App\Support\VariantEquivalentLookup 並接到 create、改鍵、以及 ASSOC_DATA 鏡像同步的三條寫入分支與反向碼哨兵 0 的盲插
VariantReplaceScope::loadColumnTypes() 快取瞬時錯誤 一次連線抖動就讓該表在整個 process 的剩餘生命週期都不替換,資料靜默留變體形。改為只有「表確實不存在」才降級
char_variant_map 的守衛只在 Codes UI 用 API 或核准歷史提案就能寫入成環的對照,之後 dropCycleEdges() 把整組邊丟掉、該組字的替換全站靜默停止。守衛下移到落庫層(含通用核准的 create/update)
meta.__approving_operation_id 客戶端可控 眾包使用者塞上自己那筆待審提案的 id 就能關掉待審重複防呆。API 入口統一剝掉 __ 前綴鍵
姓名清空(既有 bug) 沒有明確姓氏的歷史人物(分欄 NULL、c_name_chn 存完整姓名)更新任何一欄都會被靜默清空。三處寫入現在一致

查重只在真的改鍵時執行——否則歷史上就已兩形並存的資料會變成任何更新都做不了(假 409)。

驗證

  • ./vendor/bin/phpunit 全綠:2886 tests / 15299 assertions
  • php-cs-fixer(清 cache、dist config):0 fixes
  • 新測試檔 tests/Feature/ApiV2MutateVariantReplacementTest.php(29 條)+ 既有測試檔補強;每一條都以「把機制中和掉會紅」逐一驗證過鑑別力,唯二例外在測試註解裡明寫(有第二道保護擋著)
  • Review:2 組 review agent 兩輪 + codex 五輪,最後一輪回「沒有需修正的實質問題」

🤖 Generated with Claude Code

在兩個抽象基底類別(AbstractPersonSubresourceCreateHandler/
AbstractPersonSubresourceMutationHandler)的 handle() 內插入通用掛鉤(新
Concerns\AppliesVariantReplacement),21 個人物子資源 handler 自動生效;體系外
三個例外(PossessionCreateHandler/PostingCreateHandler/SourceMutationHandler)
各自補掛。掛鉤點一律早於 preprocess、PK 計算與查重,所以文本型 PK 成員
(ALTNAME_DATA.c_alt_name_chn、ASSOC_DATA.c_text_title、BIOG_SOURCE_DATA.c_pages)
替換後的值會成為實際落庫的 PK。notices 由 handle() 統一掛上,成功與 409/422 都帶。

移除 Altname 兩個子類重複的 replaceStrict() + assign(通用掛鉤先跑後它的 replaced
恆為空,會讓別名替換通知靜默消失),基底改用 merge 收集。

BIOG_MAIN 三處手掛姓名欄改為整列 replaceRow('BIOG_MAIN')(c_notes/c_tribe 等文本欄
原本全漏),但 v2 路徑把替換範圍收窄到「使用者本次實際變更的欄」——它的 payload 是
「原列 ∪ changes」的整列,整列替換等於對使用者沒碰過的欄做回溯校正(D6 明說不做),
還會讓沒改任何欄的存檔變成一筆真實 UPDATE。store() 的回傳 key 統一為 variant_replaced。

D7「兩形並存」查重從 CodesController 抽成 App\Support\VariantEquivalentLookup 並接到
v2 的 create 與改鍵(update)兩側、以及 ASSOC_DATA 鏡像同步的三條寫入分支(既有列
更新、backfill 補建、#70 force 收斂)與反向碼為哨兵 0 的盲插。既有列可能存變體形
(D6 不回溯校正),只比對替換後的值會鑄出兩列語義相同、字形不同的資料,而唯一鍵
擋不住——那比完全不替換更糟。查重只在真的改鍵時執行,否則歷史上就已兩形並存的資料
會變成任何更新都做不了。

其他一併修掉的缺口:
- VariantReplaceScope::loadColumnTypes() 只在「表確實不存在」才降級並快取,其餘
  Throwable 一律 rethrow——原本一次連線抖動就會讓該表在整個 process 的剩餘生命週期
  都不做替換,資料靜默留變體形。
- char_variant_map 的結構驗證與快取重置下移到落庫層(新 Concerns\GuardsCharVariantMapWrites
  掛在代碼表 create/update/proposal,通用核准的 applyCreateProposal/applyUpdateProposal
  也補上)。原本只掛在 Codes UI,用 API 或核准歷史提案就能寫入成環的對照,之後
  dropCycleEdges() 會把整組邊丟掉、該組字的替換在全站靜默停止。
- Api\MutationController 剝掉客戶端 meta 的 __ 前綴內部鍵:__approving_operation_id
  原本可由客戶端帶進來,用來關掉待審重複提案的防呆。
- BIOG_MAIN 的 422「未偵測到任何修改內容」帶上 notices;PostingMutationHandler 的
  地址衝突 409 也帶。
- 修好既有的姓名清空 bug:updateById()/prepareProposalPayload() 原本無條件
  c_name_chn = 姓 . 名,沒有明確姓氏的歷史人物(分欄 NULL、c_name_chn 存完整姓名)
  更新任何一欄都會被靜默清空。store() 早有等價保護,現在三處一致。

測試 harness(CodesControllerTest 的假 driver)補 Schema::getColumns()/getForeignKeys()
與 QueryBuilder::lazyById(),並修好 matchCondition() 把 '!=' 當等值比較(語義正好反過來)。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sudoghut
sudoghut merged commit 45e701a into develop Aug 21, 2026
6 checks passed
@sudoghut
sudoghut deleted the feat/variant-replace-subresources branch August 21, 2026 19:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant