net-kotoba | Datomic query × IPFS / IPLD / Prolly Tree kotoba ≝ Datom[CID/T] × EAVT × Prolly × Datalog[Δ]
CONTENT-ADDRESSED DISTRIBUTED DATALOG DB

Datomic query を
IPFS / IPLD / Prolly Tree で動かす

net-kotobase (kotobase.net) ← etzhayyim/kotoba | Rust + WASM

Datom log
(E,A,V,T,Added)
Prolly Tree
4-index
CommitDag
= WAL
blocks
DAG-CBOR/CID

▶ 自動再生中 … 下のバーで一時停止・シーン移動できます

① 全体像 — 1本の content-addressed チェーン

「正本」は Datom ログ。IPFS と B2 は export tier

canonical spine: Datom log → ProllyTree → CommitDag → blocks

正本(system of record) — kotoba プロセス内蔵ストア
Datom log
immutable
ProllyTree
索引
CommitDag
WAL
blocks
CID
EXPORT ①
Kubo IPFS bitswap + DHT で公開・複製。封印した commit を非同期で export
EXPORT ②
Backblaze B2 (CAR) off-host の cold pin。DataLad + git-annex で全 block をミラー
ANCHOR
IPNS / Base L2 署名ヘッドで per-graph root を固定、L2 に DAG root を tamper-evidence

hot な内蔵ストアが µs–ms で速く、IPFS/B2 は「壊れない・配れる」ための輸出層。

② 書き込み — Datom を積む

kg.ingest / datomic.transact → 5-tuple を pending へ

QuadStore::assert_datom(kotoba-graph/src/quad_store.rs)

Datomic 流の不変 5-tuple(retract は tombstone)
Datom {
  e: CID  — entity
  a: ":role" — attribute
  v: "admin" — value
  t: tx    — 論理時刻(basis-T)
  added: true — 追加/撤回
}
pending_datoms
少し溜めてから commit()

ここでは 正確な 5-tuple をそのまま記録するだけ。索引化は commit で行う。

③ commit — 4本の Prolly Tree を作る

EAVT / AEVT / AVET / VAET を 並列ビルド

kotoba-query/src/arrangement.rs + kotoba-core/src/prolly.rs

EAVT
entity 引き
(SPO)
AEVT
attribute 走査
(PSO)
AVET
値で entity 引き
(POS)
VAET
逆参照
(OSP)
確率的チャンキング(履歴非依存)
is_boundary(key):
  h = blake3(key)
  return (h[0..4] & 0x0000_00FF) == 0

≈ 1/256 で境界が立つ → 平均 256 entries/chunk。同じデータなら必ず同じ形 = deterministic / hash-consistent。

path-copy(差分だけ書く)
root'
A
B'
C
leaf'

変わった枝だけ新ノード(緑)。残りは旧 CID を共有 → commit ごとに Δ だけ書く。

④ content-addressing — CID を付ける

各ノード → DAG-CBOR → sha2-256 → CIDv1

put_node()(prolly.rs)/ KotobaCid::from_bytes(cid.rs)

ノードを正準 DAG-CBOR で直列化
Internal children: [(max_key, child-CID)…] 子リンクは IPLD CID tag-42
cid[0]=01 CIDv1
cid[1]=0x71 dag-cbor
cid[2]=0x12 sha2-256
cid[3]=32   + 32B digest

→ IPFS 互換 CIDv1。block の中に自分の CID は持たない(= block の hash そのもの)。

commit を CommitDag に append(= WAL)
Commit root / index_roots{eavt,aevt,avet,vaet,tea} prev → 親 commit / seq / tx_cid / sig
c0
c1
HEAD

親リンクで連なる不変チェーン。耐久境界は head-ref の原子的更新(git / Datomic 流)。再起動は head + checkpoint から差分 walk。

全 block は CARv1 1束にまとめて単一 PUT(commit を atomic に複製)。

⑤ 輸出 & ピン留め — durability

kotoba 自身が IPFS block store + pinner

kotoba-store: Memory / Kubo / Tiered / Distributed・BlockStore trait

hot tier(内蔵)
直接ディスク µs–ms
pin はストア内のフラグ
(pin/add RPC 不要 → ~35× 速い)
Kubo IPFS — bitswap + DHT。block put / get(CIDで取得)
B2 cold pin (CAR) — 全 block を S3 にミラー、restore で再 import
IPNS 署名ヘッド — per-graph root を可変ポインタで公開

封印済み commit を ホットパスの外で非同期 export。pin = named commit CID = 恒久・アドレス可能。

⑥ クエリ — datomic.q を実行する(本命)

[?e :role "admin"] を AVET で引く

q / pull / datoms / asOf / history | keycodec で hot=cold 同順

① parse
EDN → AST
② 索引選択
bound a,v → AVET
③ hot?
Arrangement
④ cold scan
ProllyTree
HOT — in-memory Arrangement

索引がホットなら直引き。
EAVT は O(1) hash(≈180ns)、AVET/VAET は O(log N) BTreeMap。これは キャッシュ最適化に過ぎない。

COLD — ProllyTree を辿る(IPFS基盤の上)
root = commit.index_roots["avet"]
prefix = keycodec(":role")+keycodec("admin")
ProllyTree::scan_prefix(root, prefix, store)
root (1 get)
child
child (1 get)
child
leaf → [E…]

各レベル = block 1回 get(ceil(log₂₅₆ N) ホップ)。leaf から Datom 復元 → filter/project。asOf/since は TEA で時間遡行。

⑦ provenance — クエリ結果も content-addressed

emit_cid で result_cid を返す

ADR-2606060000 | 3つの DAG-CBOR エンベロープ

kotoba.queryspec.v1 lang / query(正準EDN) / inputs query_spec_cid
kotoba.queryjob.v1 spec + basis_t + asOf/since + engine query_job_cid
kotoba.result.v1 job + basis_t + rows_edn(正準) result_cid
✔再現
同じ query + 同じ basis-T ⇒ 同じ結果
✔改ざん検知
1 行変えれば result_cid が動く
✔キャッシュキー
query_job_cid が安定キー
✔provenance DAG
「なぜこの答え」を辿れる

result_cid は JSON 応答ではなく正準 DAG-CBOR block の CID。検証は CID で block を再取得して行う。

まとめ — end to end

クエリは「索引化された Prolly Tree」を辿るだけ

①書込 QuadStore::assert_datom で 5-tuple (E,A,V,T,Added) を pending に。
②commit で EAVT/AEVT/AVET/VAET(+TEA) の Prolly Tree を並列ビルド(blake3(key)&0xFF==0 境界・path-copy)。
③各ノードを DAG-CBOR → sha2-256 → CIDv1(0x71)、子リンクは IPLD tag-42。Commit{index_roots,prev,seq} を CommitDag(=WAL) に append、CARv1 で単一 PUT。
④封印 commit を Kubo IPFS / B2 へ非同期 export・pin、IPNS で head 公開。
⑤クエリは索引選択 → hot Arrangement か ProllyTree::scan_prefix で cold scan(各レベル=block 1 get)→ Datom 復元。
⑥emit_cid で結果も result_cid として content-address(再現・改ざん検知・キャッシュ)。

主要コード: prolly.rs / arrangement.rs / keycodec.rs / quad_store.rs / commit.rs / cid.rs

net-kotobase/docs/explainer
…
1.0× 0 / 0