跳到主要內容

arc space

arc space 管理本地 DID Space 資料:檢視存了什麼、用一條 sync 命令在兩棵子樹之間搬資料,以及對 folder-backed 的 space 做 claim、檢查和修復。

arc space 管理本地 DID Space 資料——支撐 DID Space 的那份 per-app、per-user 儲存。十個面向使用者的子命令:三個讀、一個搬資料、一個刪、五個管理 folder-backed 的 space——外加第十一個內部命令;見本頁底部的 sync-bench

對照版本:arc 2.0.0-beta.48,commit 5a5316bde(main,2026-09-10)——釘的是 commit,不是 semver:另一份構建也曾帶著同一個 2.0.0-beta.48 字串。複製 dump 前先跑 arc --version

bash
arc space <subcommand> [options]

全域 flag(見總覽):--json--view--instance / -i(哪個本地 ARC 例項;省略即 default)、--home(例項根;要指定作用在哪個例項,用 --instance)。

檢查類子命令(listtreepathrm)接受 --scope 來指定作用在哪個 DID Space,外加 --root-path/--user-did 覆蓋資料實際存在哪裡。只有 list 接受 --scope alltreepathrm 只接受 instance|user。資料夾類子命令(initcheckrepairsetmigratesync)直接對著一個目錄操作——一個路徑本身就說明了它屬於哪個 space。

每個子命令的預設(非 TTY)檢視各不相同——這是重抓這一頁時發現的本頁自身的漂移:list 的預設檢視已經就是下面這種完整的人類可讀塊;checkmigrate 的預設是 JSON,不是文字;sync 的預設是壓成一行的拒絕理由摘要,不是長版解釋。--view human 是唯一一個在所有子命令上都穩定一致的 view,本頁往下的每一條示例命令都用它——顯式加上它,別指望預設檢視,因為預設按子命令各不相同,也不是本頁的契約。--json 不受這一切影響——它永遠是完整的結構化 payload。

下面的例子裡,demo space 路徑簡化成了 ~/notes;其餘部分都是真實執行的原樣。

檢視存了什麼

arc space list

列出 app space,帶它們的檔案數和 root。

用法

bash
arc space list [options]
  • --scope <instance|user|all>:作用在哪個 DID Space(預設 all
  • --root-path <path>:DID Space 儲存的 root path,覆蓋 config
  • --user-did <did>:user DID,覆蓋 config;root path 仍退回 config 裡的預設值
  • --all:顯示全部條目(預設上限 100)

root 下沒有 app space 時,list 每個 scope 打一個塊(Instance DID SpaceUser DID Space),各說一句 (no app spaces)——沒有單行的「這裡什麼都沒有」空態;每個響應都是按 scope 分塊的:

bash
$ arc space list --root-path ~/spaces
Instance DID Space  z1bp5ncJyUiKBv8BVCBH1RVnKks6u9VkW7G
  Root: ~/spaces
  (no app spaces)

User DID Space  z1bp5ncJyUiKBv8BVCBH1RVnKks6u9VkW7G
  Root: ~/spaces
  (no app spaces)

至少有一個 app 存在時,每個塊打的是一張小表,不再是 (no app spaces)

bash
$ arc space list --root-path ~/spaces --view human
Instance DID Space  z1bp5ncJyUiKBv8BVCBH1RVnKks6u9VkW7G
  Root: ~/spaces
  DID                  Files  Last Modified
  ───                  ─────  ─────────────
  did:abt:notes-app        0  —

User DID Space  z1bp5ncJyUiKBv8BVCBH1RVnKks6u9VkW7G
  Root: ~/spaces
  DID                  Files  Last Modified
  ───                  ─────  ─────────────
  did:abt:notes-app        0  —

list 的預設(非 TTY)檢視本身就是這同一種人類可讀、按 scope 分塊的樣子——它是本頁裡唯一一個 --view human 加不加都一樣的子命令。--json 把同樣兩個組打成一個 groups 陣列,每個元素帶 roleuserDidrootPathappstotal

arc space tree

顯示一個 app space 的檔案樹。

用法

bash
arc space tree <app-did> [options]
  • --app-did <did>(必填,也是第一個 positional):要檢視的 app DID
  • --scope <instance|user>:哪個 DID Space(預設 instance
  • --path <path>:app fragment 內的路徑(預設 /
  • --depth <n>:樹的最大深度(預設 99
  • --limit <n>:最多條目數,除非 --all(預設 100
  • --all:顯示全部條目,忽略 --limit
  • --root-path <path> / --user-did <did>:跟 list 一樣的覆蓋項

--app-did 必須以 positional 方式給出——只寫 arc space tree --app-did <did>、不帶 positional,會被當成缺引數拒絕,即便 --app-did 同時也出現在上面的 flag 表和 --helpOptions: 列表裡。tree 對著一個完全沒有 space 的 app DID 跑,是一次真實的拒絕(退出碼 5App space not found: <did>)——accept-path 的對應項是下面的 arc space path,它對著同一個缺失的 DID 不會報錯,因為它只是計算資料存在哪裡。

arc space path

列印一個 app DID Space fragment 對應的 AFS 路徑,方便直接餵給 arc afs 命令用。

用法

bash
arc space path <app-did> [path] [options]
  • --app-did <did>(必填,也是第一個 positional):要檢視的 app DID
  • --scope <instance|user>:哪個 DID Space(預設 instance
  • --path <path>:app fragment 內的路徑(預設 /
  • --root-path <path> / --user-did <did>:跟 list 一樣的覆蓋項

tree 不一樣,path 不要求這個 app space 真的存在——它照樣列印算出來的 AFS 路徑,退出碼 0

bash
$ arc space path did:abt:no-such-app --root-path ~/spaces
/spaces/z1bp5ncJyUiKBv8BVCBH1RVnKks6u9VkW7G/blocklets/no-such-app/system

搬資料

arc space sync

在兩棵子樹之間搬資料——本地資料夾、遠端 host,或者一邊一個。讓 --to--from 收斂。

這是「把資料從 A 搬到 B」的唯一命令,方向隨意,任意兩端之間都行。方向和兩端都是引數,從來不是命令名——想反向拉,把 --from--to 換個位置就行。

它按路徑來定位 space,這就是為什麼它沒有 --server--app-did--scopelayout=files 的 space 在磁碟上就是真實目錄,一個路徑本身已經說清楚了你指的是哪個 space、哪個 fragment。(--scope 仍然作用於按 DID 定位 space 的 list/tree/path/rm。)全域的 --instance / -i 仍然是選這條命令跟哪個本地 ARC 例項說話;它不是 DID Space 的 id。

用法

bash
arc space sync --from <ref> --to <ref> [options]

每個 <ref> 既可以是本地資料夾,也可以是 https://<host>[/<sub>] 表示某個遠端 app 的 user fragment。兩種形式只靠 scheme 區分,所以一個打錯的 URL 永遠不會被悄悄當成相對目錄名重新解釋。

  • --from <ref>(必填):source
  • --to <ref>(必填):destination
  • --mirror:刪掉只在 destination 存在的檔案(預設:additive,什麼都不刪)
  • --dry-run:報告差異,不寫任何東西
  • --engine <auto|changelog|cursor|manifest>(預設 auto):強制指定一個 engine。auto 會協商雙端都支援的最強那個。點名一個不可用的 engine 是報錯,絕不靜默降級
  • --verify:sync 完之後,把每個 destination 檔案的 CID 跟 source 重新核對一遍。跟 --dry-run 一起用時,它是在斷言 destination 已經匹配,不匹配就以非零碼退出
  • --full:忽略任何已記錄的 cursor,重新讀一遍所有東西——不用 cursor,也不讀任何一端的索引;兩棵樹都直接走一遍。第一次 sync 進一個 destination 反正都是 full 的;用這個 flag 重建一個你不再信任的 destination,或者 sync 一個故意在 AFS 之外被改動過的 claimed 資料夾(它的索引預期會落後,不加這個會報 index-diverged 錯誤——見第三個例子後面的說明)
  • --init:把一個資料夾 destination 宣告為 layout=files(source 已 claimed 時預設這樣做)
  • --external-writes <none|possible>(預設 possible):給一個已 claimed 的 destination 宣告的查詢新鮮度,只有 destination 真的被 claim 了才會用到
  • --token <token>:遠端端點的 access token,瀏覽器認證的替代方案
  • --browser:為認證流程開啟瀏覽器(預設 true);無人值守場景配 --token--no-browser

下面每一次執行還會在 stderr 上打一段簡短的進度追蹤(Scanning source N files, destination M (…ms) / Comparing (…ms) / Syncing X/Y (…ms) / Total: …ms,下面 stdout 的 dump 裡省略了),以及一行 File list:,說明兩邊分別是從索引快照回答的還是退回到掃描——這兩條本頁別處都沒寫過,是這次重抓才發現的新東西。

預設是 additive。 第一次 sync 會報告它搬動了什麼,並說出它協商到的 engine,以及其它幾個為什麼被拒絕。--view human 給出每條拒絕理由的長版;預設(非 TTY)檢視給的是壓成一行的摘要——為什麼本頁統一用 --view human,見上面 檢視存了什麼 那條說明:

bash
$ arc space sync --from src --to dst --view human
src → dst
Base path: /
Mode:      additive

Engine:    manifest — the manifest engine works between any two endpoints
  not changelog: the changelog engine is a device↔cloud engine — it needs exactly one local folder (the device) and one remote host (the cloud); neither folder↔folder nor remote↔remote fits it
  not cursor: the cursor engine mirrors (it deletes destination-only paths); this run is additive, so use --mirror to allow deletions or let the manifest engine handle it
File list: source scan (Operation not supported: paginated snapshot), destination scan (Operation not supported: paginated snapshot)

Added:     2
Modified:  0
Deleted:   0
Unchanged: 0

Transferred: 2 files
Verified:    no
Duration:    348ms

這裡的 src/dst 是兩個從沒被 arc space init 過的普通資料夾——這就是為什麼 File list: 兩邊都說 scan (Operation not supported: paginated snapshot):誰都沒有索引可以拿來做快照,manifest engine 只能退回直接走一遍樹。只存在於 destination 的檔案不會被動。想讓 destination 變成精確副本就加 --mirror——這第二次執行還先把 src 裡的 b.txt 刪了,所以 Deleted: 1 反映的是一次真實的刪除,不是編出來的數字:

bash
$ arc space sync --from src --to dst --mirror --view human
src → dst
Base path: /
Mode:      mirror (deletes destination-only files)

Engine:    manifest — the manifest engine works between any two endpoints
  not changelog: the changelog engine is a device↔cloud engine — it needs exactly one local folder (the device) and one remote host (the cloud); neither folder↔folder nor remote↔remote fits it
  not cursor: source is not a claimed DID Space, so it keeps no changelog ledger to take a cursor from
File list: source scan (Operation not supported: paginated snapshot), destination scan (Operation not supported: paginated snapshot)

Added:     0
Modified:  0
Deleted:   1
Unchanged: 1

Transferred: 0 files
Verified:    no
Duration:    322ms

source 本身是一個已 claim 的 layout=files space(arc space init)時,同一條拒絕理由會寫得不一樣——這句話值得記住,因為它點名了真正的解法:

  not cursor: source is layout=files with externalWrites="possible" — the changelog cannot see edits made outside AFS; run `arc space set <from> external-writes=none` (only if the folder really is exclusively AFS-managed) to enable cursor-incremental sync
File list: source snapshot, destination snapshot

(真實捕獲,source/destination 都已 claim——File list: 現在兩邊都說 snapshot,沒有 fallback 理由,因為兩邊都有索引可讀。)

--dry-run 只看,--verify 斷言。 兩個一起用可以核實一個 destination 而不碰它——下面這次執行發現一個被改動過的檔案,以 1 退出:

bash
$ arc space sync --from src --to dst --dry-run --verify --view human
src ⇢ (dry-run) dst
Base path: /
Mode:      additive

Engine:    manifest — the manifest engine works between any two endpoints
  not changelog: the changelog engine is a device↔cloud engine — it needs exactly one local folder (the device) and one remote host (the cloud); neither folder↔folder nor remote↔remote fits it
  not cursor: the cursor engine mirrors (it deletes destination-only paths); this run is additive, so use --mirror to allow deletions or let the manifest engine handle it
File list: source scan (Operation not supported: paginated snapshot), destination scan (Operation not supported: paginated snapshot)

Added:     0
Modified:  1
Deleted:   0
Unchanged: 0

Transferred: 0 files
Verified:    no
Duration:    357ms

--dry-run 要求兩端都已經存在;只有真正跑一次才會建立 destination 目錄。

這次重抓之後才有的新行為:--dry-run --verify 對著一個在 AFS 之外被手改過的、已 claim 的 destination 跑,會直接拒絕,不會把這個漂移報成 Modified——--help--full 自己的說明文字就點名了這個失敗:

$ arc space sync --from src-claimed --to dst-claimed --dry-run --verify --view human
ERROR: Provider "did-space-files" cannot reach its "query-freshness" dependency at "/": index has diverged from disk in 1 directory — /: a.txt (size-mismatch) — run `arc space repair --folder dst-claimed` to repair. Or re-run with --full to read both trees directly and ignore the indexes (slower, but it is the right choice for a folder that is deliberately written outside AFS).

(退出碼 5。)加 --full(或者先跑一次 arc space repair --folder <dst>)是這條拒絕的 accept-path 對應項——同一個被改動過的檔案、同一條命令、多加一個 flag,報出來的是 Modified: 1,退出碼 1--verify 不匹配的退出碼,不是拒絕),跟上面純資料夾的例子一模一樣。

編輯

arc space rm

刪除本地 app space 裡的一個檔案或目錄,遞迴刪。它會走 space 的 ledger——直接 rm -rf 會在索引裡留下一條只有 arc space repair 才能清掉的幽靈記錄。

用法

bash
arc space rm <app-did> <path> [options]
  • --app-did <did>(必填,也是第一個 positional):目標 app DID
  • --path <path>(必填,也是第二個 positional):app fragment 內的路徑
  • --scope <instance|user>:哪個 DID Space(預設 user
  • --root-path <path> / --user-did <did>:跟 list 一樣的覆蓋項

兩個 positional 都必填——只寫 arc space rm <app-did>(不帶 path)會被當成「非 option 引數不夠」拒絕,跟上面 tree/path--app-did 撞上的是同一道 yargs 級守衛。

資料夾形態的 space

一個已 claim 的資料夾,是一個由普通、可手改的檔案樹加一個 .did-space/ 索引撐起來的 DID Space,跟 layout=cas(內容定址)形態相對。這五條命令負責 claim 一個資料夾、檢查它的索引是否還跟磁碟一致、不一致時重建索引、以及把一個 CAS space 轉成資料夾形態。

arc space init

把一個資料夾初始化成可寫的 DID Space——只建立 .did-space/

用法

bash
arc space init <dir> [options]
  • dir(必填):要 claim 成 DID Space 的資料夾(設了 --source 時是一個 ledger)
  • --external-writes <none|possible>(預設 possible):新 space 的查詢新鮮度宣告。possible——人手初始化的資料夾隨時可能被手改,所以每次查詢都重新掃盤。none——只有當別的東西(一個 daemon)會獨佔管理這個資料夾時才傳這個;之後查詢直接從索引裡答。外部 ledger 形態(--source)要求是 possible
  • --source <path>:外部 ledger 形態的只讀真相層;ledger(.did-space/config + space.db)建在 dir,source 本身永遠不會被寫
bash
$ arc space init ~/notes
Initialized DID Space at ~/notes (layout=files, externalWrites=possible)

init 的預設檢視就是這同一行文字;--json 給的是 { "dir", "layout", "externalWrites" }

claim 一個已經有檔案的資料夾不會把這些檔案建進索引——init 只建立 .did-space/。之後跑 arc space repair --folder <dir>,或者先跑 arc space check --folder <dir> 看看索引缺了什麼。

arc space check

審計本地 DID Space:layout(已遷移 / 需要遷移 / 讀不了)以及索引跟磁碟是否一致。只讀——它從不寫索引或 config。有需要遷移的、讀不了的、或者漂移了的,就以非零碼退出。

用法

bash
arc space check [options]
  • --folder <dir>:只檢查這一個已 claim 的資料夾的新鮮度,完全跳過 layout 掃描
  • --root <dir>(可重複):layout 掃描的 root 目錄。預設:這個例項的 space root 加上它的兄弟目錄 <home>/.afs/work——host 的 work ledger 就住在那裡,在 root_path 之外,掃描漏了它會報出一個假的「全部乾淨」
  • --root-path <path>:掃描用的 root path 覆蓋項

check 的預設(非 TTY)檢視是 JSON,不是下面這段文字——這是重抓這一頁才發現的新情況,之前那版把這段文字寫得像是預設檢視。下面每條例子都加了 --view human 才拿到可讀形式;不加就拿到跟 --json 一樣的資料,形狀是 { "folder", "layout", "externalWrites", "directoriesChecked", "fresh", "dirty" }(不管你是走預設檢視還是顯式 --json,payload 都一樣)。

漂移按目錄報告,附帶說明修法:

bash
$ arc space check --folder ~/notes --view human
Check freshness: ~/notes (layout=files, externalWrites=possible)
Directories checked: 2
Result: DRIFT DETECTED — 2 of 2 directories disagree with disk

    /
      ! b.txt [missing-in-index]
      ! notes [missing-in-index]
    /notes
      ! a.md [missing-in-index]

Run `arc space repair --folder ~/notes` to repair (review the entries above first — it rebuilds the WHOLE index from disk).

乾淨的 space 會照實說,退出碼 0

bash
$ arc space check --folder ~/notes --view human
Check freshness: ~/notes (layout=files, externalWrites=possible)
Directories checked: 2
Result: CLEAN — index agrees with disk

arc space repair

通過從真實檔案樹重建索引來修復一個已 claim 的資料夾 space。用在手改過已 claim 資料夾下的檔案之後,或者在裡面直接 rm -rf 之後。

用法

bash
arc space repair --folder <dir> [options]
  • --folder <dir>(必填):要修復的已 claim 資料夾(必須是 layout=files
  • --index(預設 true):從磁碟重建 folder-backend 索引——這是這條命令今天唯一會做的修復
bash
$ arc space repair --folder ~/notes
Repaired index for ~/notes (layout=files)
Added:     2
Modified:  0
Deleted:   0
Unchanged: 0
Duration:  43ms

repair 的預設檢視就是這同一段文字;--json 給的是 { "folder", "layout", "added", "modified", "unchanged", "deleted", "elapsed" }(注意是 elapsed,不是 Duration——兩種檢視對同一個數字用了不同的鍵名)。

arc space set

在一個已存在layout=files DID Space 上設定一條宣告。

用法

bash
arc space set <folder> <assignment>
  • --folder <dir>(必填,也是第一個 positional):要更新的已 claim 資料夾(必須已經是 layout=files——見 arc space init
  • --assignment <key>=<value>(必填,也是第二個 positional):要設定的宣告。external-writes=none——這個資料夾處在獨佔(比如 daemon)管理下,查詢直接從索引裡答,不掃盤。external-writes=possible——資料夾仍可能被手改,每次查詢前都重新掃盤。兩個方向都是冪等且可逆的;.did-space/config 裡其它已有的鍵都原樣保留
bash
$ arc space set ~/notes external-writes=none
Set externalWrites=none for ~/notes (layout=files)

arc space migrate

把一個 layout=cas 的 DID Space 原地遷移成 layout=files(copy → verify → 原子替換;舊的 CAS 目錄留著以便回滾)。

用法

bash
arc space migrate <dir> --to-layout files [options]
  • dir(必填):要遷移的已 claim DID Space 資料夾(必須是 layout=cas
  • --to-layout <files>(必填):目標 layout——只支援 files
  • --dry-run:報告會發生什麼(space 數、檔案數、位元組數、預期結果)——磁碟上零改動
  • --external-writes <none|possible>(預設 none):遷移結果 layout=files space 的查詢新鮮度宣告。預設 none,因為 layout=cas space 的真相層(objects/<cid> + _metadata.db)本來就不是人會手改的東西,所以「遷移時沒有外部寫入者」是一個可以斷定的事實;只有當這個 space 確實被當成普通可手改資料夾用過,才傳 possible

migrate 的預設(非 TTY)檢視也是 JSON,跟 check 一樣;--view human 給的是短句形式的摘要。

對一個已經layout=files 的資料夾跑 migrate 不是錯誤——它是一條真實的、冪等的 accept path,退出碼 0

bash
$ arc space migrate ~/notes --to-layout files --view human
Migrate ~/notes (layout=cas -> layout=files)
Already layout=files — nothing to migrate.
External writes: none

對一個完全沒有 .did-space/ 的資料夾跑 arc space migrate 一個錯誤——跟上面「已經遷移過」不是同一個(退出碼 5):

bash
$ arc space migrate ~/plain-dir --to-layout files
ERROR: arc space migrate: ~/plain-dir is not a claimed DID Space (no .did-space/). Run `arc space init ~/plain-dir` first.

要撤銷一次遷移,回滾步驟:

bash
rm -rf "<dir>"
mv "<parent-of-dir>/.cas-backups/<dir-name>" "<dir>"

遷移前的 layout=cas 目錄留在 <parent-of-dir>/.cas-backups/<dir-name>,從不自動刪除。它故意放在 space 自己 parent 的下一級,一個本身不是已 claim space 的容器裡,這樣 space 列舉——以及因此 daemon 啟動——就永遠不會重新發現這個備份。

sync-bench(內部)

這個構建上存在第十一個子命令:arc space sync-bench,一個跑在 afs-rpcafs-rpc-batch P6 driver)上的 SyncEngine 吞吐量 benchmark。它要求 --server <url>--token <token>——一個真實的遠端端點,不是本地資料夾——並在 --prefix(預設 e2e-batch/bench)下播種/跑 benchmark/清理一批一次性檔案。它不像另外十個那樣在 space 的資料模型裡承擔讀/寫/檢查的角色;flag(--blocklet--files--size--single-op--header--cleanup)見 arc space sync-bench --help。本頁不再進一步展開。