這段程式碼把「編譯期（compile）」跟「執行期（runtime）」拆成兩條管線：compile 會把 SchemaNode 展平成 `ctx.pathSpecNodes`（每個 path 一個 specNode），runtime 則用多個 pass 去跑 type / constraints / branches / additionalProperties。整體可跑，但目前有幾個**語意不一致 / 會誤判 / 會漏跑**的點很明顯。

---

## 1) **最嚴重：applyTypesAndValidate 的語意跟 ValueSpec health check 完全衝突**

你在 `valueSpecHealthCheck` 明確定義 `types` 是 **AND Semantic**（同一個值要同時符合所有 type，且 baseType 必須唯一）。

但 `applyTypesAndValidate()` 現在是：

* 迴圈跑 types
* 只要某個 typeChecker 回 `res.ok === true` 就 **立刻 return ok**
* 全部失敗才回 TYPE_INVALID（而且訊息寫 “match any of the allowed types”）

這是 **OR Semantic**，而且還是「第一個通過就結束」。

結果：compile-time 會要求 baseType 唯一 + AND，但 runtime 卻把它當 OR，用法會讓整套規格語意崩掉（例如 `['StrictNumber','Positive']` 只要 StrictNumber ok 就通過，根本不會檢查 Positive）。

---

## 2) types 命名大小寫 / normalize 不一致，會造成「runtime UNKNOWN_TYPE」與 compile 通過互相打架

`_resolveValueSpecAndConstraintIndex()` 會把 type push 成 `t.toLowerCase()`，並用 `isObjectType(types) => types.includes('strictobject')`，這表示 runtime 期待「全小寫 type key」。

但你的 globalTypes 是 `new Map([...CoreValueTypeCheckers, ...CoreStructuralTypeCheckers])`，這些 key 是否已經是小寫？（從你前面 ValueSpec 文件看起來像 `StrictNumber` / `StrictString` 那種大寫）
如果 globalTypes key 是原樣（如 `StrictNumber`），那 runtime `detectUnknownTypes()` 會把 `strictnumber` 判成未知，然後 PASS1 直接 UNKNOWN_TYPE。

同時 valueSpecHealthCheck 那邊的 registry key normalization 是 `normalizeKey = lowerCase`，所以 compile/health check 可能會說 OK（因為 registry 也是 normalize），但 runtime 會報 UNKNOWN_TYPE（因為 runtime 取不到同樣 key）。
這會讓使用者覺得系統精神分裂。

---

## 3) branch validation 的資料來源策略做了 canonical-first fallback，但會掩蓋 PASS1 的錯誤語意

`handlePass3Branches()` 你做了這個策略：

* branchesOnly：用 rawValue
* valueThenBranches：優先 canonical（acc.values），若 canonical undefined 才 fallback rawValue，並記一筆 info `BRANCH_VALUE_FALLBACK_TO_RAW`

這個 fallback 在「PASS1 失敗導致 canonical 沒寫入」時會讓 branches 仍然跑，可能造成：

* PASS1 已報 type/constraint error，但 branches 又跑出另一套錯誤（或甚至通過）
* 使用者看到兩套診斷，且很難知道哪個才是「主原因」

你在註解已意識到「應產生 diagnostics 告知為何沒跑/為何 fallback」，目前確實有做 info，但語意要很清楚：
**branch precondition 是用 canonical 的結果做分支判定**，如果 canonical 不存在，其實分支跑出來的結果不一定可信（因為 precondition 可能改變值）。

---

## 4) additionalProperties / apMode 的實作只對「strictobject」啟用，但 compile 時 apMode 會被塞到所有節點

`handlePass4AdditionalProps()` 只在 `isObjectType(types)` 才處理，表示：

* specNode 的 type 一定要包含 `strictobject` 才會啟用 unknown key 策略
* 如果 root / object 節點 rules 沒宣告 type（例如純分支節點或 precondition+branches 但 types 最終空），apMode 設了也不會生效
* 而 `specNode.meta.apMode` 又預設 `'forbid'`，但如果不走到 pass4，這個 forbid 等於沒作用

另外你 `knownChildKeys` 的取得方式是「掃 ctx.pathSpecNodes keys」，包含 `prefix.*` 等 wildcard；你有過濾掉 `'*'`，但 `extractFirstChildKey(prefix)` 這段對 prefix 的 slice 方式若 prefix 是 `'a.'`，childPath `'a.bb.cc'` 切出來的 tail 會以 '.' 開頭，`split('.')[0]` 會得到 `''`，然後被 filter 掉；這會讓 knownChildKeys 少掉一些 key（取決於 prefix 末尾是否含 '.'，你現在 prefix 是 `${path}.`，確實含 '.'）。
你後面又有 `ctx.pathSpecNodes.has(childPath)` 當補救，但它只補救「已編譯出完整 childPath」的情境，對只存在更深層（a.bb.cc）但沒直接節點（a.bb）時，仍可能判成 unknown。

---

## 5) constraintIndex 的分類與 health check 的 registry model 仍有落差

你在 compile 期 `_resolveValueSpecAndConstraintIndex()` 依照三個 Set（singleValue/crossField/collection）分類：

* single/collection → 同時 push 到 `valueSpec.constraints`
* crossField → 只進 `specNode.constraintIndex.crossField`，**不進 valueSpec.constraints**

這跟你 ValueSpec 定義「不包含 crossField」一致，是好事。
但你 health check 目前只檢查 `valueSpec.constraints` 的 registry 相容性，因此 crossField 的 registry 相容性完全不在 valueSpecHealthCheck 的保護範圍內（而 runtime 才會在 PASS2 發現 unknown crossField）。
如果你希望 compile-time 就擋掉 crossField token（至少未知名稱），要在 schema compile/PathSpecNode 層做另一個健康檢查（你 code 裡也留了 TODO）。

---

## 6) PASS1 跑 entries 的順序是 Map insertion order，可能導致 parent/child 的 canonical 建立時機不穩

你用 `entries = Array.from(ctx.pathSpecNodes.entries())`，然後四個 pass 都用這個 order reduce。
如果 `setByPathMut` 需要 parent object 已存在才能寫 child（或會自動建），那就還好；但你在 pass4 additionalProps 會讀 `parentVal = getByPath(acc2.values, path)`，如果 parent 的值尚未被寫入（因為 parent path 的 PASS1 沒值、或順序後跑），`apMode === 'allow'` 時你會用 `{}` 當 baseParent，再 merge unknown keys，最後再 set 回去。這可能把「本來應該由其他路徑建立的 canonical parent」用 `{}` 提前建立，造成後面 setByPathMut 的寫入行為與預期不同（視你的 setByPathMut 實作而定）。

---

整體一句話：**compile-time（ValueSpecHealthCheck）與 runtime（applyTypesAndValidate）在 type Semantic、key normalization 上目前不一致**，會導致「規格檢查說 OK，但 runtime 仍 UNKNOWN_TYPE / 或 type 判定走 OR」這種最難 debug 的錯。這兩點是現在最需要優先對齊的結構性問題。
