Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Tests/AkashicMCPTests/NameClassificationB39Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,10 @@ final class NameClassificationB39Tests: XCTestCase {
let names = (0..<40).map { "Typo Inst \($0)" }
var entries = [TemporalValue(value: "Keep Inst")]
for n in names {
entries += (0..<20).map { TemporalValue(value: n, range: DateRange(start: String(1800 + $0), end: String(1800 + $0)), source: long, note: long) }
for i in 0..<20 {
let year = String(1800 + i)
entries.append(TemporalValue(value: n, range: DateRange(start: year, end: year), source: long, note: long))
}
}
var o = Organization(key: "obig", names: Timeline(entries))
o.references = names.map(withdrawn)
Expand Down
6 changes: 4 additions & 2 deletions Tests/AkashicMCPTests/NameClassificationB41Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,10 @@ final class NameClassificationB41Tests: XCTestCase {
func org(_ key: String) -> Organization {
var entries = [TemporalValue(value: "Keep Inst")]
for name in ["Typo A", "Typo B"] {
entries += (0..<5).map { TemporalValue(value: name, range: DateRange(start: String(1800 + $0), end: String(1800 + $0)),
source: control, note: control) }
for i in 0..<5 {
let year = String(1800 + i)
entries.append(TemporalValue(value: name, range: DateRange(start: year, end: year), source: control, note: control))
}
}
var o = Organization(key: key, names: Timeline(entries))
o.references = ["Typo A", "Typo B"].map { record("authorized", $0, .withdraw, "打錯") }
Expand Down
2 changes: 1 addition & 1 deletion docs/rulings/zero-instance-guards.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
| 85 | **零實例,而它守的是一道分類規則與它造出的記錄之間的一致——合併接上去的記錄若與名字的分類矛盾,之後按位置讀記錄的兩道閘會得出相反的結論**(#564 R2 verify:b29 V1 第 0/2/6 列。保險:合併後倖存者上若有名字的最後一筆與分類矛盾——撤回而仍在分割裡,或指定/確認而不在——而那是這次合併帶進來的,preview 與實跑都拒絕。) | ✅ **寫(拒絕:`DivergenceResolveError.wouldContradictClassificationTail`,住在 `validatePersonPreconditions`/`validateVenuePreconditions`,與實跑同一個 `mergedPersonKeeper`/`mergedVenueKeeper` 算;delta——倖存者原本就有的不擋)** | 第 78 列守的是「合併不替人改判定」(降級帶記錄的對外形);這一列守的是**合併接上去的記錄本身說的話**:刪名字閘(`latestAction`:最後一筆是撤回才可刪)與寫入面的去重(只比最後一筆)都按位置讀,尾端一旦與分類矛盾,前者會放行刪一個仍是對外形的名字、或叫人去「撤回」一個不在分割裡的名字。主修在搬法(按位置接);保險擋的只剩來源本身就不一致的被併者——手改,或修正輪之前的 binary(那時寫入面的去重比整份歷史,「指定 R → 撤回 → 再以 R 指定」的第三筆會被丟),零實例而且不靠人記得。**觸發條件可檢查**(腳本見量測文件):矛盾的(分割, 名字)應恆為 0;非零時那筆是手改或舊 binary 寫的,出口是在那筆記錄上重下一次與分類一致的判定。b39 起「原始倖存者在同一個(分割, 名字)上就有的不擋」收緊成要同一個方向(`heldKey`,與第 98 列共用 `heldTailContradictions`)——在任何載得進來的 store 上不改變結論(b40 Z1 第 10 列:錨不到 names 的記錄載入時隔離,方向就是成員資格)。(歷輪補記與量測見量測文件第 85 列) |
| 86 | **零實例,而三種實體裡只有它刪得到沒有名字——兩個鄰居早就擋了**(#564 R2 verify:b29 V1 第 18 列。`update-person --remove-name`/MCP `remove_names` 刪完 person 沒有任何名字時整批拒絕、零寫入,乾跑同樣拒絕;organization 的 `remove_names` 與 venue 的 `edit_name_segment`(`.noNamesLeft`)在修正輪就擋了。) | ✅ **寫(入口拒絕;不改 `Person.validate()`)** | 第 25 列把名字的不變式裝在 store 邊界,因為入口有五個;這一列的形~~只有一個入口造得出~~有兩個入口——`remove_names` 與 `fields.names` 的空替換,所以擋在入口、與兩個鄰居同一句話。**不改 validate** 的理由:沒有名字的 person 讀得進來、不毀資料(`doctor` 已經報「no authorized name」),升成 error 會讓手改出來的那一筆擋住它自己所有的寫入;這一格要的只是工具面不再造出它。**觸發條件可檢查**(腳本見量測文件):沒有名字的 person 數應恆為 0;非零時那筆是手改或 R2 verify 之前的 binary 寫的,`update-person --fields` 的 names 補一個名字即可(歷輪補記與量測見量測文件第 86 列) |
| 87 | **零實例,而它關掉的是一個把「讀不懂」折成「空的」的回退——接在後面的是整份替換**(#700 b31 W5 MEDIUM 0、1:`ensureLayout` 對 store 根目錄 `.gitignore` 的處置先前是 `(try? String(contentsOf:encoding:.utf8)) ?? ""`,讀不出來(非 UTF-8、UTF-16、權限 000)就當成空檔,再以只含 sources 區塊的內容原子替換——使用者原有的規則(擋 raw 逐字稿的 `*.srt` 之類)全部消失、結束碼 0、沒有任何訊息;symlink 被換成一般檔。) | ✅ **寫(讀不懂的不改寫:`file add` 在建立任何東西之前具名拒絕、`doctor` 與兩個匯入照常建佈局並印 warning(匯入是使用者 2026-10-05 的裁決:匯入不寫 `sources/`,防線在 `store-source`);要加區塊時只附加位元組;`LibraryStore.inspectSourcesIgnore`/`applySourcesIgnore`,`SourcesIgnorePolicy`。b36 verify 起「區塊已在」照 git 的判讀:規則行照 git 的切法、symlink 不論指向的內容都不算已在——git 不讀 symlink 的 `.gitignore`;b38 verify 起規則之後的否定規則(`!` 開頭)放回 `sources/` 時不算已在——git 以最後一條符合的規則為準;b40 verify 起只算碰得到 akashic 寫的路徑(`sources/index.jsonl`、存檔子目錄)的否定規則(`sources/*` 加 `!sources/.gitkeep` 照樣已在)、一行在第一個 NUL 截斷、三個以上的 `*` 與兩個相同,規則只排除內容而 `sources/.gitignore` 在時不算已在。判成已在 ⇒ git 排除 akashic 寫進 `sources/` 的每一個路徑(範圍:根目錄與 `sources/` 的 `.gitignore`、未追蹤的路徑;akashic 沒寫的路徑不在其中);反方向有幾種保守的形狀(量測文件第 87 列))** | 第 3 列是「沒被檢查」與「檢查過且乾淨」在輸出上分不開;這一列是同一種折疊落在**輸入**上:「讀不懂」與「空的」在程式裡是同一個值,而接在後面的動作是整份替換,所以一個錯的折疊把一次附加變成一次刪除。零實例不降低它的份量——失敗是安靜的(結束碼 0),被刪的是隱私邊界的規則,`git add -A` 之後外流不可逆。裁決有三半:**標記以位元組比對**(已有區塊時不必讀懂整個檔,尾端有非 UTF-8 位元組也不動);**加區塊只附加位元組**(原有位元組不經解碼、不重寫);**讀不懂就不動**——`file add` 拒絕、診斷面與兩個匯入改報(診斷工具最該說話的時候不是最該掛掉的時候,`doctor` 不因此失敗)。**觸發條件可檢查**(腳本見量測文件):註冊的 store 裡 `.gitignore` 落在量測文件第 87 列列出的拒絕狀態之一的數應恆為 0——那份清單是封閉集合(含 b38 起的 `rule-negated`、b40 起的 `nested-gitignore`),這裡不複述(b42 verify INFO 10:先前這一句只說「不是 UTF-8 一般檔、又沒有區塊」,與腳本的清單分岔);非零時那個 store 的 `file add` 會拒絕、`doctor` 與兩個匯入會報 warning,處置照訊息做(轉成 UTF-8、自己加那段、改掉放回 `sources/` 的否定規則之類)(歷輪補記與量測見量測文件第 87 列) |
| 88 | **零實例,而守衛只對它認得出的單位求值——認不出的寫法讓量測與它的閘一起消失、守衛照樣綠**(#711 R3 verify:b31 W6 第 0、2–7、20 列。`zero-instance-rows-audit` 的自證閘自 R2 起只收一種寫法,但「哪些文字是一個單位、哪些單位是量測」沒有寫出來:R2 的單位是 fence 內一個實體行、fence 外同一實體行裡的一段 inline code,binary 與計數拆在兩行(`\`、行尾的豎線或 `&&`、跨行的 inline code)、寫在 fence 與 inline code 以外(縮排區塊、`<pre>`、引用區塊裡的 fence)、或計數的寫法沒被認出(`grep '-c'`、樣式含豎線而 `-c` 在後、`uniq -c`、預設值展開),一條量測就不成單位、不被計入;把 34 條裡的 33 條改寫成預設值展開,閘全部消失而守衛 rc=0。) | ✅ **寫(辨識補寬+條數地板+退場區塊個數;認不出的三類照實列出、由地板兜底)**;R4:認不出時偏向「是量測」與「同一個單位」、地板依閘去重、退場標記拿掉、認不出的寫法改寫成開放清單 | 第 21 列的謂詞對那一類成員結構上不可能為真,換一個謂詞就好;第 37 列連正確的謂詞都不存在。這一列在兩者之間:謂詞(模板)是對的,錯在它只對**認得出的單位**求值,而「哪些文字是一個單位」是一個沒寫出來的前提——R2 寫「封閉的模板沒有旁路可找」,對模板為真、對辨識為假。所以裁決有兩半:把那個前提寫出來並補寬(邏輯行、引用區塊、CommonMark 配對、fence 外的文字、不切註解、引號與位置變化的計數選項),**以及**承認辨識不會完整——以變數或檔案當來源、`awk` 自己加總、binary 名與計數不在同一個邏輯行,這三類仍然認不出——所以條數要有一個不靠辨識的地板(同第 16 列:裁決依賴守衛;地板過期只擋得住整批、擋不住少量,新增量測後由人調高)。**R4 更正兩處**:認不出的不只三類(開放的,見 [`measurement-commands-self-prove`](measurement-commands-self-prove.md));退場區塊的個數釘得住、內容釘不住(加一行、換一行都綠),所以 R4 拿掉退場標記、照掃那個區塊。**觸發條件可檢查**(指令見量測文件):棘輪的訊息數應恆為 0;標題列的「合模板」比下限多出許多時調高下限(歷輪補記與量測見量測文件第 88 列) |
| 88 | **零實例,而守衛只對它認得出的單位求值——認不出的寫法讓量測與它的閘一起消失、守衛照樣綠**(#711 R3 verify:b31 W6 第 0、2–7、20 列。`zero-instance-rows-audit` 的自證閘自 R2 起只收一種寫法,但「哪些文字是一個單位、哪些單位是量測」沒有寫出來:R2 的單位是 fence 內一個實體行、fence 外同一實體行裡的一段 inline code,binary 與計數拆在兩行(`\`、行尾的豎線或 `&&`、跨行的 inline code)、寫在 fence 與 inline code 以外(縮排區塊、`<pre>`、引用區塊裡的 fence)、或計數的寫法沒被認出(`grep '-c'`、樣式含豎線而 `-c` 在後、`uniq -c`、預設值展開),一條量測就不成單位、不被計入;把 34 條裡的 33 條改寫成預設值展開,閘全部消失而守衛 rc=0。) | ✅ **寫(辨識補寬+條數地板+退場區塊個數;認不出的三類照實列出、由地板兜底)**;R4:認不出時偏向「是量測」與「同一個單位」、地板依閘去重、退場標記拿掉、認不出的寫法改寫成開放清單 | 第 21 列的謂詞對那一類成員結構上不可能為真,換一個謂詞就好;第 37 列連正確的謂詞都不存在。這一列在兩者之間:謂詞(模板)是對的,錯在它只對**認得出的單位**求值,而「哪些文字是一個單位」是一個沒寫出來的前提——R2 寫「封閉的模板沒有旁路可找」,對模板為真、對辨識為假。所以裁決有兩半:把那個前提寫出來並補寬(邏輯行、引用區塊、CommonMark 配對、fence 外的文字、不切註解、引號與位置變化的計數選項),**以及**承認辨識不會完整——以變數或檔案當來源、`awk` 自己加總、binary 名與計數不在同一個邏輯行,這三類仍然認不出——所以條數要有一個不靠辨識的地板(同第 16 列:裁決依賴守衛;地板過期只擋得住整批、擋不住少量,新增量測後由人調高)。**R4 更正兩處**:認不出的不只三類(開放的,見 [`measurement-commands-self-prove`](../../.claude/rules/measurement-commands-self-prove.md));退場區塊的個數釘得住、內容釘不住(加一行、換一行都綠),所以 R4 拿掉退場標記、照掃那個區塊。**觸發條件可檢查**(指令見量測文件):棘輪的訊息數應恆為 0;標題列的「合模板」比下限多出許多時調高下限(歷輪補記與量測見量測文件第 88 列) |
| 89 | **零實例,而防線先前住在一份抄本裡——跑的是模型照文件抄寫的程式,測試釘的是文件**(#692 R4 verify:讀頁面的五道閘——(a) Safari 回報的網址在解析器會分岔的形狀(反斜線、帳密、百分比編碼、IPv6 字面值、空白與控制字元)一律認不出主機;(b) `LAND` 預設 `-`(不比對落地主機)時主機仍要形狀合格;(c) 頁面自報的 `rawLength` 限 0–1,000,000,000;~~(d) 主機不合而換到的是已知的驗證服務時整批暫停(結束碼 2),不當成不可達~~ → (d) 分頁在已知的驗證服務上——不論換到或直接落在——不當內容讀,以頁面文字分等人驗證 3/整批暫停 2(b35,使用者 2026-10-05);(e) 輸出檔與落地主機檔在第一個可能失敗的瀏覽器指令之前刪,讀回的 JSON 由 `trap … EXIT` 刪。) | ✅ **寫(在 `akashic web-read`,剔除集合是 `UnsafeToEmitScalar`(b33 verify 起加 noncharacter,與 `escapesInLLMDocument` 同一個加項);清理順序在區塊本身)** | 第 84 列守的是一則裁決的邊界、第 83 列的零是磁碟區種類的零;這一列的零是**取得路徑從沒跑過**的零,而它的特別之處在防線的**位置**:R3 把這類閘寫成文件裡要照抄執行的程式,跑的是抄本、測試釘的是文件,兩者之間沒有任何東西核對;剔除集合也是 `UnsafeToEmitScalar` 的第二份描述,在 macOS 的系統 Python(Unicode 13)上安靜地少了九個碼位。所以裁決有兩半:寫這五道閘,**以及把它們放進 CLI**——放在抄本裡的閘,通過時證明的是文件,不是實際跑的那一份。**觸發條件可檢查**:verify-venue SKILL 的「沒有實跑過」一段改寫的那一天(對出版商頁實跑過 safari-browser),回來看哪一道閘在真 Safari 上有實例、哪一道的前提(例如 WebKit 回報正規化網址)量過。(歷輪補記與量測見量測文件第 89 列) |
| 90 | **零實例,而擋它的寫法看起來已經擋住了——`${V:?…}` 只停在管線的一段,計數照印 0**(#711 R4 verify:b33 X6 第 1、3、5、14、15 列。R3 把第 18、46、49、73、77 列的 `--library "$STORE"` 改成 `--library "${STORE:?…}"`,散文寫「沒設時 shell 停下」。) | ✅ **寫(模板加前置條件;fence 與 inline code 裡任何單位把 `${V:?…}` 寫在管線裡即紅)** | 第 13 列的理由是「沒被檢查」與「檢查過且乾淨」在輸出上分不開,自證閘擋的成因是舊 binary;這一列是同一個分不開換一個成因——binary 根本沒跑。它比第 13 列多一層:擋它的寫法**看起來已經擋住了**(`${V:?…}` 的字面意思是「沒設就停」),文字照著寫,三輪 verify 才量出它只停管線的一段。所以不能只改散文——散文改對了,下一條量測照樣可以寫成同一個形狀。裁決是把前置條件寫進模板(`: "${V:?…}"` 在 `&&` 串最前面,整條不跑;`<參數>` 用到的變數都要列在那裡),`--library "$V"` 另要 `test -f "$V/store.yaml"`(打錯路徑時 `doctor` 會建 store),並在 fence 與 inline code 的**任何**單位把管線裡的 `${V:?…}` 判成錯誤——不只量測:第 73 列的 `find …` 接 `wc -l` 不提 binary。**觸發條件可檢查**(指令見量測文件):兩種訊息數應恆為 0(歷輪補記與量測見量測文件第 90 列) |
| 91 | **零實例,而揭露與寫入在同一次執行裡——附註的補救到達時寫入已經發生**(#709 第四次 verify MEDIUM 5、LOW 6/13/15、LOW 22,使用者 2026-10-05 裁決 2:三個 bootstrap 的計畫讀到同一筆記錄的 legacy 拷貝時,拷貝裡的 literal 也算一次出現——一篇 work 加它的拷貝讓只被用一次的名字以 ×2 越過 `--min-occurrences`。先前開頭印一行附註「先刪掉拷貝再跑,計數才準」,`--apply` 照寫;真 binary 重現建出 `doe-a`、`journal-of-alpha-things`。) | ✅ **寫(`LibraryLoad.refuseApplyWithPlanBlockers`:計畫讀到拷貝時 `--apply` 整批拒絕、零寫入,拒絕說筆數與處置、清單在乾跑;只數計畫讀到的種類——`BootstrapPlanSource`,2026-10-09 起 `bootstrap-people` 也讀 person 拷貝;`authorize-names` 的寫入集合裡有任何一筆的 id 還有 legacy 拷貝時同樣整批拒絕)** | 第 3 列的理由是「守衛沒說話」;這一列的守衛說了話、卻沒有擋——揭露只有在它到達與動作之間還有一個人時才有用,而 `--apply` 把兩者放在同一次執行裡。所以裁決不是把附註寫得更清楚,是在寫入之前擋(`two-kinds-of-edits` 程式編輯欄的「可預期失敗整批擋零寫入」);乾跑照常列出並說明,那是人還來得及讀的地方。**誠實邊界**:拒絕比計數嚴——拷貝與 entities/ 那份內容相同、計數只是乘二而不越過門檻時也擋;那是裁決本身的取捨(「有拷貝就拒絕」,不是「計數越過門檻才拒絕」)。**觸發條件可檢查**:計畫讀到的種類裡有被 load 標成拷貝的記錄時(`akashic validate` 的「同一筆記錄的 legacy 拷貝還在」警告;只有 legacy 檔、沒有 entities/ 那份的不算拷貝——那一類歸第 102 列),該命令的 `--apply` 以結束碼 1 拒絕——`ShadowedLegacyCopyReadersCLITests`/`ShadowedPairCensusCLITests`/`BootstrapPlanBlockersCLITests` 釘住,對照組是只有 person 拷貝時 venues 照寫。~~legacy 殘留數 > 0 時三個 bootstrap 都拒絕~~(b36 verify LOW 11、18:殘留數 > 0 不蘊含拷貝,也不蘊含每個 bootstrap 都讀到它)(歷輪補記見量測文件第 91 列) |
Expand Down
Loading