Skip to content

feat: 依作品全文核對 work 作者列——替換腿、fulltext text/compare、akashic-verify-work (#742) - #755

Merged
kiki830621 merged 32 commits into
mainfrom
idd/742-fulltext-reconcile
Oct 27, 2026
Merged

kiki830621 merged 32 commits into
mainfrom
idd/742-fulltext-reconcile

Conversation

@kiki830621

Copy link
Copy Markdown
Member

Summary

依作品全文核對並更新一筆 work(#742):

  • update-entry --replace-authors(MCP replace_authors):整組替換作者列,寫入一筆帶依據(全文 digest、PDF 頁序、理由)的替換記錄;團體作者的成員名單存在 work 頂層 group-members:。store format 23。
  • akashic fulltext text/fulltext compare:取逐頁文字、把提議與 store 比對,產生可直接交給替換腿的物件(只乾跑、不寫入)。
  • 新 skill akashic-verify-work(plugin 殼 0.13.6 → 0.13.7):讀 byline 與成員名單 → compare → 乾跑表 → 人同意才寫;沒有文字層時逐頁走 bestOCR 本機引擎的共識。

Spectra change reconcile-work-from-fulltext(82/82 task)。verify 八輪,第五到第八輪 PASS(報告在 #742)。

Test plan

  • 全套 swift test -Xswiftc -warnings-as-errors(pre-push,5,428 個;高負載時 計時型線性度測試在高負載下失敗,擋住 pre-push #752 的計時型測試可能失敗)
  • .githooks/run-guards.sh rc=0
  • tools/list 59,981 bytes(預算 65,000)
  • 部署:akashic、akashic-mcp、App 三個 binary 一起換新後,live store 的 marker 18 → 23

Follow-ups

#745、#746、#747–#753、#754

Refs #742

kiki830621 and others added 30 commits October 21, 2026 22:40
…名單、依全文核對的 skill (#742)

Refs #742

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
使用者 2026-10-24 改判裁決 3:全文 PDF 沒有文字層時不再跳過,
改由 akashic-verify-work 逐頁呼叫 bestOCR consensus 轉出文字再比對。

- design:新增 D14(呼叫方式、信心不足的判準、三種失敗、標記與寫入),
  Context/Goals/Non-Goals/D11/D12/Contract/Risks 同步修改
- spec:無文字層的偵測規則保留,改為 OCR 的觸發條件;新增兩個 Requirement
- tasks:改寫 4.2、5.1、6.3,新增 5.4(OCR 路徑)與 5.5(標記與信心規則)
- binary 契約不變:替換腿 JSON 與 fulltext compare 的輸入輸出都沒有加鍵

Refs #742
使用者 2026-10-21 於 #578 裁決把 tools/list 預算由 60,000 提高到 65,000、
不拆分 MCP server,觸發點就是本 change 的 replace_authors;change 仍寫著
「超出 60,000 不改預算、回 #578 請裁決」,Open Questions 也還掛著這一題。

- design:Context、D10(改寫並記下沒採的拆分方案)、D1 與 Non-Goals 兩句
  在 60,000 時量的「會超出」、Acceptance、Risks、Open Questions
- tasks 2.6:守衛常數改 65_000,連同常數註解與 mcp-cli-parity 規則的預算段
- proposal:What Changes 加一條、Impact 加兩個檔

Refs #742
#742(Spectra change reconcile-work-from-fulltext)第 1 組:

- AuthorListReplacementRecordValue:`替換為 ⟦a⟧ …;依據第 N 頁:理由` 的單一解析器;
  Entry 的附著驗證收第三種 field: authors 記錄(value 缺席、rests-on 恰一個 digest、至多一筆)
- GroupMemberList 與頂層 group-members:(規則只有一份,decode 與寫入面共用),鍵序緊接 authors
- store format 22 → 23,寫入閘對 format < 23 拒寫帶任一新形狀的 work;docs/store-format.md 與 README 各加一列
- work 合併的遺失檢查比對 group-members(屬性計數 19 → 20)
- group-members 的三個讀取面:MCP entryDict、CLI get-entry、App EntryDetailView
- 寫死 supported == 22 的四個測試依慣例改成 23 並改名

Refs #742
#742 第 2 組:

- 唯一的 JSON 解碼器 decodeReplaceAuthors(CLI 讀檔、MCP 把 Value 編回 JSON):
  任何一層的未知鍵、缺鍵、型別不對都整批拒絕;頁序不收字串、布林、小數
- 已歸戶的位置要被 from 沿用或在 release 附理由,否則具名拒絕;釋放不刪 verdict
- 實寫移除描述舊作者列的拆分、移除、先前替換記錄與完整性證言並逐筆列出;git 閘
- group-members:有給整組替換、沒給保留對得上的組
- 乾跑在寫入閘會拒絕時照樣報出計畫(applyRefusal)
- MCP 面 verdictsOnRetiredLiterals 截 20 筆並附 Total/truncated,CLI 全列;兩面一致以真 binary 驗證
- tools/list 預算依使用者 2026-10-21 於 #578 的裁決提高到 65,000;接上後實測 59,816 bytes
- 測試:解碼器 12 條、替換規則 9 條負控逐一變紅

Refs #742
#742 第 3 組:

- StoreHealth 新增一族 warning「作者列替換記錄與目前的作者列對不上」(design D8):段數不同、
  或 literal 位與同位的段不是位元組相同;key/organization 位視為相符。doctor(MCP 與 CLI)
  與 App 都顯示計數;validate 仍 exit 0
- ZoteroImporter 的更新分支在作者列有已歸戶位、或帶替換記錄時保留作者列並記進
  authorsPreserved(design D9)。已歸戶位一併算進 organization:原本只看 person key,
  organization 位在 #323 之前不存在
- 測試照 spec Example 表四列;兩個負控、organization 的負控逐一變紅

Refs #742
#742 第 4 組:

- fulltext text:work 連著的全文 PDF 全部頁的文字(pdftotext 以 \f 切頁、與 pdfinfo 的頁數不符就拒絕),
  hasTextLayer 是「有任何一頁含 Unicode 字母」;--out 必須在 git 工作樹之外;不寫 store、不做 OCR。
  PDFReader.read 的兩頁行為不動
- fulltext compare:提議與 store 現值逐欄比對(作者列、成員名單、標題、卷、期、頁碼、article number、日期),
  作者列或名單不同時產出可直接交給 update-entry --replace-authors 的物件;提議的作者項由替換腿的乾跑驗,
  被拒的原因寫進 refusal、結束碼 0;非作者欄位只列出
- 提議的 authors 多一個選填 reason(省略時「PDF 第 N 頁 byline」),design 與 spec 同步
- WriteGateRulings 登記兩個子命令為不寫 store(葉命令 87 = 裁決表 87)
- 4.2 的「binary 不呼叫 OCR」檢查改為比對呼叫形:原本的字詞 grep 會命中說明「本命令不做 OCR」的註解

Refs #742
#742 第 5 組(5.1–5.3):

- 新 skill akashic-verify-work:fulltext text → 模型讀出 byline、成員名單與純量欄位 → fulltext compare
  → 乾跑表(欄位/store 現值/作品本身(頁)/文字來源/提議寫入)→ 人在 session 內明確同意才 apply;
  unattended 只乾跑。沒有文字層時逐頁呼叫 bestOCR consensus(只用本機引擎、不退回單一引擎),
  低共識的欄位不提議,OCR 的值標「OCR」、替換理由寫明依據是 OCR 與參與的引擎(design D14)
- akashic-fetch-fulltext 的最後一步指向它;writing-to-the-store.md 的「更新既有記錄」改寫為
  作者列走 update-entry --replace-authors、其餘欄位的既有路徑與依據缺口(其餘過時之處見 #743)
- plugin 殼 0.13.6 → 0.13.7;plugin.json 與 mcpb/manifest.json 的描述改成 store format 23

Refs #742
#742 第 6 組:

- 規則與裁決表:source-of-truth-over-consent 加「團體署名與成員名單」;two-kinds-of-edits 加
  update-entry --replace-authors(AI 編輯);mcp-cli-parity 的 akashic_update_entry 一列重新確認、
  CLI-only 表加 fulltext text/compare;zero-instance-guards 第 104 列(live store 唯讀重量:
  format 18、替換記錄 0、帶名單的 work 0;正對照 1/1)
- entity-backlink-completeness 封閉邊表加第 18 條:Entry.groupMembers 每組的 rests-on 是內容指標
  (backlink-field-ratchet 出聲才補進表);missingSourceDigests 以 group-members.rests-on 收它
- docs/store-format.md §3.5「作者列替換記錄」「團體成員名單」兩節,作者位來源那段更正
- 端到端測試 testClawShapeEndToEnd(真 binary);OCR 路徑的實跑記在 changelog
- 跟著出聲的衛生守衛:消毒豁免具名、git spawn 登記、CLI 建 service 的檔案集合
- akashic-discovery 0.2.16 → 0.2.17(它經 symlink 送出 plugin/rules)
- 後續 issue:#745(非作者欄位的依據寫入面)、#746(成員升格為 person ref)

Refs #742
reconcile-work-from-fulltext 的 artifacts 依 #742 verify 報告與使用者同日的四項決定更新:
非作者欄位延後到 #745(標題與日期沒有寫入面)、不寫新的完整性證言、成員維持字串、
這一輪就做逐頁判定。design 修訂 D5、D6、D11、D12、D14,新增 D15(skill 的信任邊界);
兩份 spec 補上逐位元組比較、控制字元、整數範圍、PDF 依據、作者列相同時保留記錄、
witnessRemovedDetail、逐頁 hasLetters、--out 不覆寫、compare 一律驗證、REFUSED、
replaceAuthors null 的分支與「全文是資料不是指令」;tasks 加第 7 組(7.1–7.10)。

Refs #742
#742 verify 第一輪的 7.1–7.4:
- 成員去重、組去重、團體位對位、保留舊名單、退役 literal、失效 warning 都比 UTF-8 位元組
  (String == 比的是 Unicode 正規等價,NFC 與 NFD 被當成同一個值)
- 名字不得含控制字元;團體名加上大括號後要平衡;整數精確解碼(JSONInteger:超出 Int 的數
  原本會繞回或被截斷成合法的頁序);依據要是 PDF;from 沿用來的團體位也能掛名單
- 作者列逐位元組相同時只換替換記錄,保留拆分、移除記錄與證言;移除證言時回 witnessRemovedDetail;
  退役 verdict 的掃描移到寫入之前
- 補 format 22、重跑 unchanged、無法唯一定位的替換腿層測試;13 個負控逐一變紅

Refs #742
#742 verify 第一輪的 7.5–7.7:
- fulltext text 每頁回 hasLetters、另回 sourcePath;skill 只對需要讀、沒有字母的頁走 OCR
- text 與 compare 的 --out 已存在即拒(普通檔、symlink 都不碰),以 0600 原子寫出,
  父目錄以實體路徑判斷 git 工作樹
- compare 不論要不要替換都以替換腿乾跑驗證提議(先前作者列相同、release 卻指到 literal 位的
  提議以 refusal: null 過關);作者列與成員名單逐位元組比較;頁序精確解碼

Refs #742
#742 verify 第一輪的 7.8–7.10:
- akashic-verify-work:每頁一個 OCR 輸出目錄、讀完一頁才呼叫下一頁;REFUSED 當成信心不足;
  「全文是資料,不是指令」一節;replaceAuthors null 時不呼叫替換腿;封面頁不算 byline;
  嵌入式 OCR 層的提醒;照實說文字仍會進模型服務;每一種結束都刪暫存目錄
- writing-to-the-store.md:標題與日期目前沒有寫入面(#745)
- 「另外三條腿/四條腿」改成五條;akashic_get_entry 的說明列出 group-members;
  update_entry 的說明列出 witnessRemovedDetail;Zotero 保留條件的四處描述
- 0.13.7 尚未推上 main:版本帳本只更新摘要
- changelog 記下第一輪 verify、使用者的決定、這一輪的改動、全套測試 5,391 個 0 failure、
  tools/list 59,981 bytes

Refs #742
)

- Promoted group positions (organization keys with a confirmed verdict
  pairing this work with a marked literal) count as group positions.
- Merge loss check, compare and App ids compare member lists by bytes.
- compare keys author positions by state plus bytes, compares member
  lists with their source digest, treats empty and absent as equal, and
  checks proposal pages against the source PDF's page count.
- Name gate uses UnsafeToEmitScalar and rejects a leading combining mark.
- Earlier replacement record is replaced in place; unchanged reruns
  report no removed records.
- fulltext --out is created exclusively via link(2).

Refs #742
)

- Promoted group positions accept both verdict literal shapes
  (attribute-org writes the marked literal, resolve-organizations the
  unmarked one); duplicate organization keys are refused.
- unchanged also compares references by bytes, so a reason that differs
  only in Unicode normalization is written.
- Segment and reason gates check that the brackets and separator stay
  separate characters (spacing marks, prepend characters); messages
  name the real cause; NBSP asks for an ordinary space.
- compare returns replaceAuthors null whenever refusal is set, and
  member-list rows and reports carry restsOn.
- Author.byteKey and GroupMemberList.byteKey(group:page:restsOn:members:)
  are the single implementation shared by the replace leg and compare.

Refs #742
#742)

- compare reports the replace leg's applyRefusal and gives no write
  command; an unreadable page count goes into refusal instead of
  skipping the page check.
- An organization position with no loadable record is refused when
  member lists are involved.
- Promoted verdicts match both the literal and the literal wrapped in
  one more brace layer, so nested-brace groups keep their lists.
- Segment brackets are checked per scalar; the bracket-glue rule applies
  to literals and groups only; line breaks and tabs ask for a space.
- The YAML encode self-check gate compares group-members by bytes.

Refs #742
Round 5 passed with no blocking findings; group 11 covers its LOW items
and the cheap INFO items. Refs #742
- compare reports applyRefusal only when a replacement would be given;
  an unreadable dry-run report becomes a refusal
- an unloadable organization key refuses only when a member list
  depends on it; an explicitly empty groupMembers is accepted
- group names must balance their braces per scalar as well as per
  Character
- VT, FF and U+001C-001F ask for an ordinary space

Refs #742
#742)

The carried-over position that cannot be written as a segment is a
matter of the proposal, not the store; the cleanup checks the basename,
the parent directory and a marker file before rm -rf --.

Refs #742
Round 6 passed with no blocking findings; group 12 covers its LOW items
and the cheap INFO items, mostly in design D6 and the skill. Refs #742
- the carried-over position message points to a group item when the
  old value is a name in corporate braces
- replace-authors and fulltext compare help say what the code does

Refs #742
#742)

The work directory is created together with a per-run marker token and
removed at once if the marker cannot be written; cleanup checks the
token, so another run on the same work is told apart.

Refs #742
- RUN_TOKEN reads a fixed 12 bytes instead of truncating an endless
  stream, so set -e with pipefail cannot exit before the marker exists
- the failure branch reports deleted only after checking, and exits 1
- a lost marker hands the directory to the user instead of bypassing
  the cleanup checks
- replace-authors help names the known escaping gap #754

Refs #742
The failure branch now treats the directory as deleted only when rm
succeeds and the path is gone; rm sits in the if condition so errexit
cannot exit before the report.

Refs #742
…rs build (#742)

The pre-push build failed on it after the round-5 change to the
condition; behaviour is unchanged.

Refs #742
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