Lab:Anti-Ouroboros pipeline
前三課都在拆零件。這一課把零件接起來,讓你看到資料邊界真的可以變成一條可執行的管線。
Hook
前面你已經有三塊拼圖:
- Lesson 02 的 provenance
- Lesson 03 的 principal / auth 心智模型
- Lesson 04 的 namespace 寫入白名單
如果這三塊一直分開看, 你很容易覺得: 「懂概念了,但還是不知道要怎麼落地。」 所以這堂 lab 的目標很直接。 我們要做一條大約 70 行等級的最小 consolidation pipeline。 它不接真 DB。 不接真 API server。 不做完整的 JWT 或 ACL 系統。 它只驗四件事:
- ingest 時會檢查 principal 能不能寫目標 namespace
- 每筆 entry 會帶 provenance hash
- upstream graph 有循環時會拒絕
- consolidate 時不吃
llm_derived
也就是說, 這不是「做一個完整記憶平台」。 而是把資料邊界最核心的決策, 壓成一段你真的看得懂、 真的跑得起來、 真的能測的 Python。
Learn
這個 lab 在驗什麼
先把成功標準講清楚。 這個 lab 不是在證明:
- 你已經有 production-ready memory service
- 你做出了完美 lineage graph
- 你解完所有 auth 與 namespace 問題
它要證明的是更小、 但更關鍵的四件事:
| 驗證點 | 你要看到什麼 |
|---|---|
| provenance hash | 同一筆內容的 content + tier + upstream_ids 會被綁在一起 |
| write allowlist | 不是任何 principal 都能寫 shared |
| circular upstream | upstream graph 有環時會直接拒絕 |
| tier gate | consolidate 只吃 raw_source / human_confirmed |
做到這四步, 你就已經把「資料邊界很重要」, 變成一條可以被 review、 被測試、 被未來自己重複使用的工程骨架。
我們要做的三個 function
這堂 lab 只做三個核心函式:
def ingest(content: str, tier: str, upstream_ids: list[str], principal: str) -> Entry: ...
def has_circular(entry: Entry, all_entries: dict) -> bool: ...
def consolidate(entries: list[Entry]) -> Summary: ...
每個 function 各自有很清楚的責任:
| Function | 工作 |
|---|---|
ingest(...) | 驗 tier、驗寫白名單、算 provenance hash、拒絕壞 upstream |
has_circular(...) | 從 upstream_ids 一路往回追,看 graph 裡有沒有環 |
consolidate(...) | 只整合 raw_source 和 human_confirmed,跳過 llm_derived |
這個切法的好處是: 你之後想接真 DB、 真 API、 真 namespace 參數, 都還有地方擴充。
Reference code:最小 Anti-Ouroboros pipeline
下面這段 code 是完整可跑的。
它只用 Python 標準函式庫。
沒有假 import。
沒有偷留 dev-local fallback。
你會看到四個步驟:
- ingest 三筆
raw_source - ingest 三筆
llm_derived,它們都引用前面的 raw entries - 手動放入一組帶環的壞資料,再試圖 ingest 一筆引用它的內容,應該 raise
- consolidate raw + derived,結果只保留 raw 內容
如果一切正常, 你應該看到:
cycle_error = circular upstream reference detected
summary_sources = ['entry-1', 'entry-2', 'entry-3']
summary_content = Raw note A | Raw note B | Raw note C
這就是 Anti-Ouroboros 的最小證明。 因為 pipeline 明確拒絕:
- 壞 ancestry graph
- 不該回流的
llm_derived
ingest(...) 真正做了哪四件事
別把 ingest(...) 只看成資料寫入。
在這份 lab 裡,
它其實同時做了四個 guard:
- 驗
tier是否合法 - 驗
principal是否可以寫shared - 驗每個
upstream_id是否存在 - 建立 entry 後再跑 cycle 檢查
這個順序有意義: 先擋最便宜的錯, 再做較貴的 graph 檢查。
為什麼 provenance_hash 在 lab 裡仍然重要
這份 lab 沒做完整 API server,
但我們還是把 provenance_hash 放進去了。
原因是它讓你在資料結構層先養成這個習慣:
provenance 不只是 metadata;它要能被驗。
現在這份 reference code 是 server 端自己算 hash。
所以不存在外部偽造。
但一旦你把資料從外部系統或檔案回匯進來,
你就會需要驗:
這個 entry 宣稱自己是 human_confirmed,
到底是不是被人偷偷改 tier。
等一下練習題,
就是要你補這個防線。
has_circular(...) 為什麼不只檢查自己 id
這份 lab 刻意做了一個小陷阱。
新 entry 本身還沒寫入,所以它不可能直接在 upstream 裡看到自己的 id。
如果你只檢查 entry.id in upstream_ids,你會以為自己安全。
但這不夠。
真正危險的是 upstream graph 本身已經有環。
像示範裡的:
loop-a -> loop-bloop-b -> loop-a
任何引用這組壞資料的新增 entry, 都應該被拒絕。 這就是為什麼我們要往回 DFS, 而不是只看第一層字串。
consolidate(...) 的重點不是摘要品質,而是 tier gate
很多人看到 consolidation,第一反應是: 「那摘要怎麼寫得更好?」 這堂 lab 刻意不走那條路。 我們先把最重要的事釘住:
llm_derived不得直接回流。
所以這裡的 consolidate(...) 很笨。
它只會把 eligible entries 串起來。
但這正好有教育價值。
因為你會看到,
Anti-Ouroboros 的核心不是 fancy summarizer。
而是 tier gate。
你把 gate 做對,
之後要不要換成更好的整理函式,
那是第二階段的事。
這個 lab 還沒做哪些事
這份 code 刻意做得小, 所以它還沒處理很多現實問題:
- 沒有真實的 request signature 驗證
namespace先固定寫死成shared- 沒有 persistent store
- 沒有 tenant filter
- 沒有把
claimed provenance_hash回寫驗證做進ingest
但這不代表它不夠用。 它剛好是你可以在 10 分鐘內帶團隊讀完、 30 分鐘內抄去 staging 小工具裡改的尺寸。
你回到真系統時,最先該升級哪裡
把這份 lab 帶回真專案時, 我會建議你先升級三個地方:
ingest(...)增加真實 request auth 與namespace參數provenance_hash支援 claimed value 驗證與 key rotationconsolidate(...)改成明確只吃 allowlist 來源,而不是把候選清單全收
這三個升級做完, 你就已經從 demo 跨到一個很像樣的內部資料邊界管線。
這一課你應該帶走的三個函式
如果只能記三個名字, 就記:
ingest(...)has_circular(...)consolidate(...)
前者守寫入入口。 中間那個守 lineage graph。 最後那個守回流出口。 三個一起工作, 才是 Anti-Ouroboros。 不是靠一句「請勿餵回 AI 生成內容」的口號。
AI 協作:學了這個,跟 AI 怎麼配合?
這個技能在 AI 協作中的定位,是讓你要求 AI 幫你把資料邊界做成可跑的最小程式,而不是只生成概念圖或口號式規範。 你的人類優勢:
- 只有你能決定哪一些 source tier 在你的實際工作流裡算高 signal
raw_source,哪些其實應該被排除。 - 只有你能判斷哪個 namespace 是 demo 時可以先固定、哪個一上線就必須立刻參數化並綁到真正的 principal policy。
可以這樣跟 AI 說:
請幫我做一個最小 Anti-Ouroboros pipeline,必須包含
ingest、has_circular、consolidate三個函式。ingest要驗 tier、principal 寫入 allowlist,並計算HMAC(content + tier + upstream_ids)型態的 provenance hash;consolidate只能吃raw_source和human_confirmed。請附一段可直接執行的 demo,證明llm_derived不會被餵回,而且循環 ancestry 會 raise。
Do
互動示範
挑戰任務
請修改 ingest(),讓它多接受一個 claimed_hash 參數。規則是:如果有傳 claimed_hash,就必須先驗它是否等於 HMAC(content + tier + upstream_ids);不相等就 raise ValueError("hash mismatch")。請用一個測試證明攻擊者把同一段內容從 llm_derived 偷改成 human_confirmed 時會被擋下,最後只印出 hash mismatch。