AI 科技

沒有 Palantir:一頁契約、一支測試、一份核准清單,各做到了 Ontology 的哪一格

不買 Palantir,只用一頁 MODEL.md、一支測試和一份核准清單,能把資料守到哪裡?本文以作者自己的三個公開 repo 為例,看哪些事程式真的會擋、哪些仍靠人,以及什麼情況下該考慮更重的平台或授權引擎。

2026.09.02 · 作者 dvdmaru · 約 9 分鐘 · 3,301 字
Read in English

basketball-tools 的 MODEL.md 裡有兩條規則。一條寫在第 28 行:NBA 排名的每一列不能有 division 欄位。另一條在檔案後段的交接條款:「驗收判準:正文裡每一個欄位級事實,都要指得出來源欄位。指不出來就是模型知識 → 砍。」

寫下來之後,會有程式擋嗎?第一條會:有一支測試,欄位多出 division 就變紅。第二條不會,它是人工審稿時要遵守的規則。

同一份文件裡,一條被機器執行,一條只是寫著。這篇要問的是同一件事:不用 Palantir,一個人拿一頁文件、一支測試、一份核准清單,能把 Ontology 的哪些事做到哪裡?

例子是作者自己維護的三個公開 repo:basketball-tools、health-tools、racing-tools(GitHub 的 dvdmaru/ 底下,都是 public)。沒有平台,三個靜態站。答案先講:局部的守門做得到,每個 repo 都有一件程式真的會擋的事。但「文件寫了」和「程式在執行」是兩回事,健康站追到的是建置時的值出處,不是平台說的決策血緣。要比的不是有沒有這個功能,而是誰在什麼時候強制什麼。

先交代範圍。本文用語意、動作、權限、血緣四個角度整理,這是為了對照三個 repo 自訂的分法,不是 Palantir 的官方分類;官方文件的分法是 data、logic、action、security,系列第一篇和第二篇已經拆過。三個 repo 的描述依 2026 年 9 月 2 日檢視時的檔案,這次修訂沒有重讀今天的 repo。

契約寫了,是誰在擋:basketball-tools

先看 Palantir 這邊怎麼處理「定義」。SuperRepo 的 Ontology-as-code 讓你用 TypeScript 宣告 object type、link、action。文件說這些定義在產品部署後,會被編譯、具現化成 enrollment 上的真實實體(“compiled and materialized into real entities”),OSDK 也會隨定義更新重新生成。定義改了,平台上的東西跟著改。

(這一頁在 Palantir 文件中標示為 beta,可能不是每個 enrollment 都能用;原稿 9 月查閱時頁面已有這個標示。)

MODEL.md 做不到這件事。它什麼都不會生成。守門的是 tests/test_snapshot_schema.py。它把 MODEL.md §1 的表另存成一份 Python 集合,例如 NBA_ROW_FORBIDDEN = {"division"},然後檢查三件事:必有欄位不能消失,出現未登記欄位要紅燈,NBA 列不能有 division。測試還帶陽性對照,往副本注入一個假的 division,確認檢查器真的會叫;拿掉一個必有欄位,也一樣。找不到任何快照檔案,本身就判失敗,不是悄悄通過。這支測試在 PR、push 和每日抓資料前各跑一次。

它的界線也在這裡。測試對 MODEL.md 本身只確認檔案存在、含「資料契約」「division」「final_four」三個錨字,不解析那張表,也不比對表和 Python 集合是否一致。所以契約有兩份:MODEL.md 的表,和測試檔的集合。CI 抓得到「快照和測試集合」不一致,抓不到「MODEL.md 的表和測試集合」彼此不同步,那要靠同一個 PR 人工維護。

測試也只驗鍵集合:必有、可有、禁止三個集合的差集運算。它不驗值的型別,不宣告 object identity 或 primary key,不驗 link type,也不驗資料來源到 property 的對應。Palantir 官方分開定義了 object type、property、link type、primary key、backing datasource 五個構件,並沒有說每個 object type 必須同時具備這五個。對照之下,這支測試守的只是 property schema 裡很小的一片。陽性對照證明這支測試遇到被注入的錯誤會叫,不證明資料的語意都對。

引用收據能證明到哪:health-tools

health-tools 的規則寫在 MODEL.md:頁面上的每個數字都必須回指到某一列,該列的 quote 要 grep 得回該來源的快照。scripts/check-receipts.py 對 data/criteria/*.json 的每一列做四件事:doc_id 是否存在於 manifest、引句是否非空、快照存在時引句是否 grep 得到、快照不存在時印 SKIP。

比對只吃掉字串裡的空白,全形半形、大小寫、≧ 與 ≥ 這些差異刻意不處理,因為它們正是回頭查原文的線索。比對不中就是 FAIL,不能塞進例外清單,也不准反過來改 quote 去遷就快照。

這個 gate 證明了一件具體的事:已登記的判準列,它的引句在對應的快照裡找得到。它沒證明的有幾件。它不反向檢查頁面上每個數字是不是都有一列撐著;快照不在磁碟時,程式的行為是「快照檔不存在時印 SKIP,不是 PASS。SKIP 代表『還沒驗過』,退出碼仍為 0」,也就是不擋 CI;空白正規化後的字串命中,不是語意驗證,引句找得到不代表頁面對它的解讀正確。

再看 Palantir 文件描述的 decision lineage:一個決策是什麼時候做的、依據哪個版本的資料、透過哪個應用程式,會被自動記錄。這是執行期的「決策到資料版本再到應用」。健康站是建置期的「值到出處」。層次不同,不能互相代替。

這個 repo 還有一個 history 檔,用 status 枚舉讓未證實的列不被渲染,這是機器強制的。但誰在什麼時候把一列從「已證實(需補充)」改成「已證實」,沒有結構化的 actor 或 timestamp 欄位,所以不能稱它是完整的稽核紀錄。

發布核准與身分的落差:racing-tools

racing-tools 的 config/approved.json 開頭寫著:「default-deny 發布核准清單。只有 article_sha256 完全命中的文章才會進輸出。」一筆核准有七個欄位,但 build-articles.py 只比對 article_sha256。facts_sha256 和 check_report_sha256 是留存的對帳證據,不是准入條件。

sha256 不符時,建置腳本印一句警告就跳過該篇,不算 build failure。如果只讀這一層,會以為不符只是被安靜略過。實際上前面還有一層:PR 與週更管線先跑單元測試,ApprovedArticlesAreStillApproved 把同一個 mismatch 判成測試失敗,測試檔的註解寫得很直接:「sha 對不上就讓測試紅,PR 合不進去。」兩層要一起看,才是這個 gate 真正的樣子。

身分這一關就沒有程式在管。approved.json 的註解寫著核准者不得與產稿者相同,但這句話只存在註解裡,建置腳本從頭到尾只取 article_sha256,沒有讀 approved_by,也沒有比對。勘誤則是另一個獨立檔案 data/errata.json,人工維護。核准、發布、勘誤三段分在三個地方,靠 slug 與 sha256 的命名慣例對齊,沒有共用的型別把它們綁成一個動作。

把它放回 Palantir 的 action 旁邊看。Palantir 的 action type 在同一個定義下安排送出資格(submission criteria)、參數驗證、Ontology edits、可選的 side effects,以及配置後才有的 action log。不是每個 action type 都同時具備這些;外部 webhook 不保證與 Ontology 變更一起成敗。racing-tools 沒有 Ontology edits,沒有機械檢查送出者的身分,也沒有每次通過或拒絕就自動產生的 action log;approved.json 和 errata.json 都是人工維護的狀態檔。所以這個例子只能說:文章發布這一件事,有一道 default-deny 的准入。它不能證明一般的外部寫回也能這樣自建。

權限呢

Palantir 文件說,object security policy 可以在 object type 上設定,獨立於底層資料源的權限。我在三個 repo 限定的範圍內搜尋了 permission、role、acl、auth,檔案範圍是 .py、.json、.md、.yml 等,沒有找到存取控制的實作。命中的只有 workflow 的 permissions: 設定、HTTP 標頭、HTML 的 role="img" 和 author 這類字串。

這只是限定範圍的負向結果。它不表示整個 repo 沒有權限機制,更不表示自建做不到。三個 repo 也都沒有接外部的授權引擎。

三個 repo 並排

程式真的會擋的一件事只靠人或沒覆蓋到的跟 Palantir 哪一格不同
basketball-tools快照欄位和測試集合不一致就紅燈MODEL.md 表和測試集合要人工同步;「指不出來源就砍」是人工規則定義不會生成任何東西,只驗鍵集合,不驗型別、主鍵、link
health-tools已登記判準列的引句,在快照存在時必須命中缺快照的列 SKIP;不反查頁面每個數字;history 缺 actor 與時間建置期的值到出處,不是執行期的決策血緣
racing-toolsarticle_sha256 不符就跳過該篇,測試也會擋 PR核准者與產稿者不同只是註解;核准與勘誤是人工狀態檔沒有 Ontology edits,沒有身分檢查,沒有自動 action log

什麼時候該換更重的做法

三個 repo 做到的,是各自用一份文件加一支程式或一份清單,守住一小片。值不值得再往前走,看這幾個條件:有沒有多個流程共用同一批實體、要不要做會寫回的動作、權限或合規是不是硬需求、要不要能回答「這個數字從哪來」、資料是不是散在多個系統。條件成立得越多,把這幾格交給同一套執行期強制的價值越高。

只讀、schema 自己說了算、沒有強制授權需求的靜態站,作者的取捨是做到本文這三格:欄位形狀契約、引用收據、發布准入 gate。這是依這個場景的建議,不是對所有靜態站的安全保證。

另一方面,這三個例子只能說明它們各自守住的那一格可以分別實作。把它們整合起來、長期維護、一致強制,是不是只是工程問題,三個 repo 沒有回答這件事。

所以回到開頭那條規則:「指不出來源就砍」寫在 MODEL.md 裡,程式沒有擋。你手上的規則是哪一種?下次寫契約,可以先分三格:誰來執行、什麼時候執行、擋不住的部分由誰負責。

本系列下一篇:同叫 ontology:W3C 拿來推論,Palantir 把動作寫進定義,看 W3C、dbt、Cube、LookML 與 Palantir 各自說的 ontology 或 semantic layer 是什麼。

常見問題

一頁 MODEL.md 算不算 Palantir 的 object type 定義?

是類比,不是等價。在 SuperRepo 的 Ontology-as-code 路徑,TypeScript 的 object type 定義會在產品部署後編譯、具現化成平台實體,OSDK 隨定義更新重新生成;SuperRepo 在 Palantir 文件中標示為 beta,不一定每個 enrollment 都能用。MODEL.md 什麼都不會生成,測試檔另存一份 Python 鍵集合,對 MODEL.md 只驗檔案存在與三個錨字,兩份副本靠同一個 PR 人工同步。

核准清單能替代 Palantir 的 action 嗎?

不能這樣說。racing-tools 的核准清單只守「文章發布」這一件事:article_sha256 完全命中的文章才會進輸出。它沒有修改 Ontology 物件,沒有機械檢查送出者的身分,approved_by 欄位發布 gate 也不比對。所以這個例子不能證明一般的寫回動作也能這樣自建。

自建的血緣跟 Palantir 的 decision lineage 差在哪?

health-tools 的收據 gate 檢查的是建置時「值到出處」:對已登記的判準列,若快照在磁碟上,引句必須 grep 得到;缺快照的列顯示 SKIP,且不擋 CI。Palantir 文件描述的 decision lineage 是決策到資料版本再到應用,記錄決策發生的時間、依據的資料版本、經過的應用程式。兩者在不同層次。

什麼情況值得做更重一點的守門機制?

多個流程共用同一批實體、需要會寫回的動作、權限或合規是硬需求、需要回答數字的出處、資料散在多個系統,這些條件成立得越多,把機制整合進同一套執行期強制的價值越高。只讀、schema 由自己控制、沒有強制授權需求的靜態站,作者的取捨是做到欄位契約、引用收據和發布准入 gate 三格;這是依本文場景的建議,不是對所有靜態站的安全保證。

來源與範圍

資料截至 2026 年 9 月 2 日(原稿資料日),2026 年 10 月 16 日編輯修訂。

  • Palantir 官方文件(2026 年 9 月查閱):SuperRepo Core concepts、SuperRepo Overview(beta 標示)、Object types、Object security policies、Action types overview、Why Ontology 各頁。
  • 三個 repo(GitHub dvdmaru/basketball-tools、dvdmaru/health-tools、dvdmaru/racing-tools):依 2026 年 9 月 2 日檢視的檔案,原稿未記 commit SHA。主要檔案:MODEL.md、tests/test_snapshot_schema.py、scripts/check-receipts.py、data/criteria/history-schema.json、config/approved.json、scripts/build-articles.py、tests/test_site_facts.py、data/errata.json。