feat(ingest-hash-trigger): collector R2 content-addressed 原稿上傳(SDD task 3)
- collector upload 子命令=scan+把 added/modified 原稿傳 R2(CF REST API Bearer token,key=raw/<sha256hex>,對齊 collector-trigger.v1 的 r2_key) - 冪等:存在檢查命中=skipped_exists 不 PUT;CF API objects 端點不支援 HEAD(live 實測 405)→ 改 GET+Range: bytes=0-0 - 完整性:上傳前重算 hash 核對 key,不符=failed 不上傳 - 失敗語意:failed → exit 1,ingested_hash 不動=下輪自動重試; 回寫鉤子 Manifest.MarkIngested 留給 task 4 - 設定只走環境變數 CF_ACCOUNT_ID/CF_API_TOKEN/R2_BUCKET,不落 repo - go test 14/14 綠(httptest mock 對齊真 API:HEAD 405);live e2e 全通 (arcrun-rag-raw-demo:真上傳→重傳 no-op→下載 diff 一致+sha256==key) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -11,7 +11,8 @@
|
||||
## Go 版:hash 偵測 collector(SDD ingest-hash-trigger)
|
||||
|
||||
```
|
||||
collector scan --root <知識資料夾> --manifest <manifest.json> [--max-removed-ratio 0.4] [--dry-run]
|
||||
collector scan --root <知識資料夾> --manifest <manifest.json> [--max-removed-ratio 0.4] [--dry-run]
|
||||
collector upload --root <知識資料夾> --manifest <manifest.json> [--max-removed-ratio 0.4] [--dry-run]
|
||||
```
|
||||
|
||||
一次掃描:走訪資料夾(先只認 .md/.markdown/.txt/.docx/.pptx/.pdf)→ mtime+size fast-path
|
||||
@@ -22,12 +23,44 @@ collector scan --root <知識資料夾> --manifest <manifest.json> [--max-remove
|
||||
(只更新路徑映射,不 retire、不重萃);再分 added / modified / removed。
|
||||
- **大量刪除防呆(R6)**:removed 數 > manifest 條目 × 40%(`--max-removed-ratio` 可調)→
|
||||
removed 全部不執行、manifest 條目保留、輸出 `mass_delete_guard` 警告。
|
||||
- **重試語意**:`ingested_hash` 只會在(未來的)上傳/ingest 成功後回寫;本階段永不寫它,
|
||||
所以「偵測過但未成功 ingest」的檔每輪都會重發 added/modified——這是設計(design §2),不是 bug。
|
||||
- **本階段不接網路**:daemon 常駐(launchd)、R2 上傳、打 arcrun named-webhook=之後的 task
|
||||
(SDD task 3/4、journeys/user-onboarding 環 6)。
|
||||
- **重試語意**:`ingested_hash` 只會在整條 ingest 鏈成功後回寫(回寫鉤子=`Manifest.MarkIngested`,
|
||||
由 task 4 觸發鏈呼叫);掃描與 R2 上傳都不寫它,所以「偵測過但未成功 ingest」的檔每輪都會
|
||||
重發 added/modified——這是設計(design §2),不是 bug;R2 端靠存在檢查 no-op,不會重複上傳。
|
||||
- 打 arcrun named-webhook 觸發 ingest、daemon 常駐(launchd)=之後的 task
|
||||
(SDD task 4、journeys/user-onboarding 環 6)。
|
||||
|
||||
測試:`go test ./...`(added / modified / removed / renamed / 大量刪除防呆五情境+fast-path+manifest 往返)。
|
||||
### `upload`:R2 content-addressed 原稿上傳(SDD task 3,design §4)
|
||||
|
||||
`upload`=`scan`+把本輪 **added/modified** 的原稿上傳 R2(renamed/removed 內容未變/已留底,不上傳)。
|
||||
|
||||
- **走 Cloudflare REST API**(`PUT /accounts/{account_id}/r2/buckets/{bucket}/objects/{key}`,
|
||||
Bearer token)——不用 S3 sigv4,token 模型跟產品其他部分一致(客戶本來就有 CF API token)。
|
||||
- **key=`raw/<sha256hex>`**(不含 `sha256:` 前綴,對齊 `schemas/collector-trigger.v1.schema.json` 的 `r2_key`)。
|
||||
- **冪等**:每 key 先做存在檢查,已存在=`skipped_exists` 不重傳(content-addressed 天然去重)。
|
||||
⚠️ CF REST API 的 objects 端點**不支援 HEAD**(2026-07-19 live 實測回 405),
|
||||
存在檢查走 `GET`+`Range: bytes=0-0`(存在=200/206 只讀 1 byte,404=不存在)。
|
||||
- **完整性**:上傳前重算 sha256 核對事件 hash;檔案在掃描後被改動=該筆 `failed` 不上傳
|
||||
(不能把新內容塞進舊 hash 的 key),下輪重掃自然帶新 hash。
|
||||
- **失敗語意**:任一筆 `failed` → exit code 1;manifest 照存(content_hash 反映現況、
|
||||
`ingested_hash` 不動)=下輪自動重試。上傳成功也**不**標 ingested——上傳只是鏈的第一環。
|
||||
- `--dry-run`:不碰網路、不寫 manifest,只列 `planned` 上傳清單。
|
||||
- 輸出 JSON:`{"trigger": <collector-trigger payload>, "uploads": [{path, r2_key, status, error?}]}`,
|
||||
status=`uploaded`/`skipped_exists`/`failed`/`planned`。
|
||||
|
||||
設定(**只走環境變數,絕不落 repo/code**):
|
||||
|
||||
| 變數 | 說明 |
|
||||
|---|---|
|
||||
| `CF_ACCOUNT_ID` | Cloudflare 帳號 ID |
|
||||
| `CF_API_TOKEN` | 有該 bucket R2 read+write 權的 API token |
|
||||
| `R2_BUCKET` | 目的 bucket 名(demo=`arcrun-rag-raw-demo`) |
|
||||
| `CF_API_BASE` | 選填,API 基底覆蓋(測試用;預設 `https://api.cloudflare.com/client/v4`) |
|
||||
|
||||
測試:`go test ./...`——掃描七項(五情境+fast-path+manifest 往返)+上傳七項
|
||||
(新檔上傳/同 hash 重傳 no-op/上傳失敗不標 ingested+重試/非內容事件不上傳/
|
||||
hash 不符不上傳/env 缺漏報錯/MarkIngested 鉤子),httptest mock 對齊真 API 行為(HEAD 405)。
|
||||
live e2e(2026-07-19):uncle6 帳號 `arcrun-rag-raw-demo` bucket 真上傳→重傳 no-op→
|
||||
`wrangler r2 object get --remote` 下載 diff 一致、sha256 與 key 相符,全通。
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user