docs: fix start/end → start_frame/end_frame in API docs

This commit is contained in:
Accusys
2026-05-14 17:57:00 +08:00
parent 0491c39d3f
commit 11f690ca35
44 changed files with 4672 additions and 9 deletions

View File

@@ -0,0 +1,919 @@
---
document_type: "design"
service: "MOMENTRY_CORE"
title: "MarkBase 設計文件 V2.0"
date: "2026-05-14"
version: "V2.0"
status: "active"
owner: "M4"
created_by: "OpenCode"
tags:
- "markbase"
- "display-engine"
- "virtual-tree"
- "group-share"
- "storage-tier"
- "file-uuid"
- "sqlite"
- "design"
ai_query_hints:
- "查詢 MarkBase 設計文件 V2.0 的內容"
- "MarkBase 虛擬檔案樹如何設計"
- "MarkBase Group Share 怎麼實現"
- "MarkBase file_uuid 規則"
- "MarkBase 儲存層級 Hot Warm Cold 設計"
- "MarkBase 與 Momentry Core 整合方式"
- "MarkBase Display Mode trait 架構"
- "MarkBase 檔案操作 API 設計"
related_documents:
- "REFERENCE/MARKBASE_DESIGN_v1.0.0.md"
- "REFERENCE/file_uuid_spec.md"
- "REFERENCE/SPATIAL_COORDINATE_REGISTRY.md"
---
# MarkBase 設計文件 V2.0
| 項目 | 內容 |
|------|------|
| 建立者 | M4 / OpenCode |
| 建立時間 | 2026-05-14 |
| 文件版本 | V2.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-05-12 | 初版設計Demo Display + Knowledge Graph | M4 / OpenCode | DeepSeek V4 Pro |
| V2.0 | 2026-05-14 | 加入檔案樹、Group Share、儲存層級、技術棧、file_uuid 整合 | M4 / OpenCode | DeepSeek V4 Pro |
---
## 概述
MarkBase 是 Momentry 生態系的 Display Engine 與檔案管理平台。從 V2.0 起MarkBase 不再只是 Demo Runner 的 presentation layer而是升級為具備虛擬檔案樹、跨用戶群組分享、多層級儲存管理、檔案操作 API 的完整平台。
**核心設計原則:**
| 原則 | 說明 |
|------|------|
| 展示層先行 | Demo Display 功能保留,作為 demo runner 的固定顯示視窗 |
| 檔案層次化 | 虛擬檔案樹Virtual Tree讓用戶管理自己的資料結構 |
| 儲存層級化 | Hot/Warm/Cold 三級儲存,讓用戶掌控成本 |
| 群組協作 | Group Share 讓團隊內的檔案可讀寫 |
| 單一使用者隔離 | One user = one SQLite不混用 |
---
## 關鍵術語定義
| 術語 | 定義 |
|------|------|
| Virtual Tree | 用戶管理的邏輯檔案樹,非實體路徑 |
| FileNode | 虛擬樹中的節點,包含 label、別名、圖示、顏色 |
| Display Mode | 使用者選擇的檔案展示方式List / Tree / Small Icon / Large Icon |
| Group Share | 跨用戶的群組檔案分享(選項 A: Group SQLite |
| Storage Tier | 三級儲存層級Hot / Warm / Cold |
| file_uuid | 32 字元十六進制檔案出生識別符,由 Momentry Core 計算 |
| Exit Record | 檔案移出管理時的留存記錄 |
| Mount | 實體儲存掛載點NAS、外接硬碟、LTO |
---
## 1. 架構總覽
### 1.1 模組化 Rust 設計
```
markbase/
├── src/
│ ├── main.rs # CLI entry point
│ ├── server.rs # axum HTTP server (port 11438)
│ ├── display/ # Display engine (from V1.0)
│ │ ├── mod.rs
│ │ ├── render.rs # .md → HTML (pulldown-cmark)
│ │ ├── highlight.rs # syntax highlighting (syntect)
│ │ ├── mermaid.rs # Mermaid rendering
│ │ └── page.html # core HTML template
│ ├── filetree/ # Virtual file tree (NEW V2.0)
│ │ ├── mod.rs # FileTree struct, init_from_sqlite
│ │ ├── node.rs # FileNode struct
│ │ ├── mode.rs # DisplayMode trait
│ │ ├── modes/
│ │ │ ├── list.rs # list module (trait impl)
│ │ │ ├── tree.rs # tree module (trait impl, Phase 1)
│ │ │ ├── grid_sm.rs # small icon grid (trait impl)
│ │ │ └── grid_lg.rs # large icon grid (trait impl)
│ │ └── auto_layer.rs # auto-layer rules
│ ├── operations/ # File operations (NEW V2.0)
│ │ ├── mod.rs
│ │ ├── compress.rs # zip / tar
│ │ ├── transfer.rs # copy / move between tiers
│ │ ├── archive.rs # auto-archive logic
│ │ ├── restore.rs # restore from archive
│ │ ├── exit.rs # exit record management
│ │ └── registry.rs # file_registry table
│ ├── groups/ # Group share (NEW V2.0)
│ │ ├── mod.rs
│ │ ├── db.rs # Group SQLite create/open
│ │ ├── merge.rs # ATTACH + cross-DB merge
│ │ └── roles.rs # owner/editor/viewer
│ └── mount/ # Mount management (NEW V2.0)
│ ├── mod.rs
│ ├── tier.rs # Hot/Warm/Cold tier defs
│ └── history.rs # location_history table
```
**DisplayMode Trait 設計:**
```rust
/// 展示模式的統一介面。
/// 每個模式List, Tree, Grid實作此 trait。
#[async_trait]
pub trait DisplayMode: Send + Sync {
/// 模式名稱(前端使用)
fn name(&self) -> &'static str;
/// 將 FileTree 轉換為此模式的前端資料
fn render(&self, tree: &FileTree, user_id: &str) -> Result<Value>;
/// 此模式支援的排序方式
fn sort_options(&self) -> Vec<SortOption>;
/// 此模式支援的過濾器
fn filter_options(&self) -> Vec<FilterOption>;
}
```
### 1.2 One User = One SQLite
```
data/
├── users/
│ ├── demo.sqlite # 用戶 demo 的虛擬樹 + 操作記錄
│ ├── warren.sqlite # 用戶 warren 的虛擬樹 + 操作記錄
│ └── alice.sqlite # 用戶 alice 的虛擬樹 + 操作記錄
├── groups/
│ ├── groups.sqlite # 群組註冊表group_id → path
│ ├── 1.sqlite # 群組 1 的共用資料
│ └── 2.sqlite # 群組 2 的共用資料
└── system.sqlite # 系統層級資料(掛載點、全域設定)
```
| 原則 | 說明 |
|------|------|
| **用戶隔離** | 每個用戶獨立的 SQLite 檔案user.sqlite |
| **簡單部署** | 不需 PostgreSQL server單檔即可 |
| **易於備份** | 複製 `.sqlite` 檔案即可 |
| **Portable** | 隨身碟帶著走,離線可用 |
### 1.3 Momentry Core 整合A+B 混合模式)
```
┌──────────────────────────────────────────────────────┐
│ MarkBase │
│ │
│ ┌─────────────────┐ ┌─────────────────────────┐ │
│ │ 模式 A: Crate │ │ 模式 B: HTTP API │ │
│ │ (momentry_core │ │ (localhost:3003) │ │
│ │ 作為依賴) │ │ │ │
│ │ │ │ • file_uuid 驗證 │ │
│ │ • file_uuid 計算 │ │ • chunk 查詢 │ │
│ │ • 向量嵌入 │ │ • identity 查詢 │ │
│ │ • 本地處理 │ │ • trace data │ │
│ └─────────────────┘ └─────────────────────────┘ │
│ │
│ 選擇策略: │
│ • 輕量運算 → Crate 模式(不啟動 server
│ • 重查詢/伺服器操作 → HTTP API需 server 運行) │
└──────────────────────────────────────────────────────┘
```
| 操作 | 模式 | 理由 |
|------|:----:|------|
| file_uuid 計算/驗證 | Crate | 純函數,不需 server |
| SHA256 | Crate | 本地計算 |
| Chunk 查詢by file_uuid | HTTP | 需存取 PostgreSQL |
| Identity 查詢 | HTTP | 需存取 PostgreSQL |
| Trace data時序片段 | HTTP | 需存取 PostgreSQL |
| 向量搜尋ANN | HTTP | 需 Qdrant server |
| 文件轉換soffice | Crate/CLI | 本地處理 |
---
## 2. 技術棧
### 2.1 Crate 依賴
| Crate | 用途 | License |
|-------|------|---------|
| axum 0.7 | HTTP serverport 11438 | MIT |
| tokio 1.0 | 非同步 runtime | MIT |
| rusqlite 0.32 | SQLite 客戶端bundled | MIT |
| r2d2 / r2d2_sqlite | SQLite 連接池 | MIT/Apache |
| serde / serde_json 1.0 | JSON 序列化 | MIT/Apache |
| sha2 0.10 | SHA256file_uuid 驗證) | MIT/Apache |
| notify 6.0 | 檔案系統監控Hot tier | CC0/MIT |
| zip 2.0 | ZIP 壓縮 | MIT |
| tar 0.4 | TAR 打包LTO 歸檔) | MIT/Apache |
| walkdir 2.0 | 目錄掃描 | MIT/Unlicense |
| chrono 0.4 | 日期時間 | MIT/Apache |
| tracing 0.1 | 結構化日誌 | MIT |
| pulldown-cmark | Markdown → HTML | MIT |
| syntect | 程式碼語法高亮 | MIT |
| anyhow / thiserror | 錯誤處理 | MIT/Apache |
| once_cell | 延遲初始化 | MIT/Apache |
| async-trait | async trait 支援 | MIT/Apache |
### 2.2 SQLite 查詢策略
| 項目 | 決策 |
|------|:--:|
| Crate | rusqlite同步 API |
| 非同步包裝 | `tokio::task::spawn_blocking` |
| 連接池 | r2d2_sqlite |
| WAL 模式 | 啟用(預設) |
```rust
// axum handler 中的使用模式
async fn get_tree(State(pool): State<DbPool>) -> Result<Json<Value>> {
let tree = tokio::task::spawn_blocking(move || {
let conn = pool.get()?;
let tree = FileTree::load(&conn, user_id)?;
Ok::<_, anyhow::Error>(tree)
}).await??;
Ok(Json(tree))
}
```
### 2.3 檔案系統監控
| 項目 | 決策 |
|------|:--:|
| Crate | notify 6.0CC0/MIT |
| 監控範圍 | 僅 Hot tier |
| 不監控 | Warm / Cold tier變更頻率低 |
| 實作 | `notify::Watcher` + `mpsc::channel` → async stream |
### 2.4 壓縮引擎
| 格式 | Crate | 用途 |
|------|-------|------|
| `.zip` | `zip` crate | 一般壓縮(用戶下載、備份) |
| `.tar.gz` | `tar` + `flate2` crate | LTO 歸檔Cold tier |
不使用外部 CLIditto、hdiutil全部以 Rust crate 實作。
### 2.5 檔案傳輸Transfer Engine
#### 雙引擎策略
```
TransferEngine:
├── Direct 模式std::fs::copy
│ 適用:小檔案 (<50MB)、fallback
│ 特點:無外部依賴、簡單可靠
└── Rsync 模式rsync CLI
適用:大檔案 (>=50MB)、tier 遷移、NAS 鏡像
特點:增量傳輸、續傳、校驗和
```
#### 自動選擇邏輯
```rust
fn select_mode(file_path: &Path) -> TransferMode {
let size = std::fs::metadata(file_path).map(|m| m.len()).unwrap_or(0);
if size < 50 * 1024 * 1024 { // <50MB
TransferMode::Direct
} else if Command::new("rsync").arg("--version").output().is_ok() {
TransferMode::Rsync
} else {
TransferMode::Direct // rsync 不存在時 fallback
}
}
```
#### rsync 適用性分析
| 場景 | 工具 | 理由 |
|------|------|------|
| 單小檔複製 (<50MB) | `std::fs::copy` | rsync protocol overhead > 效益 |
| 大檔案遷移 (tier move) | **rsync** | 增量、續傳、校驗和,三合一 |
| Hot ↔ Warm 同一機器 | **rsync** | 大檔案 delta transfer 效益 |
| NAS ↔ NAS 鏡像 | **rsync** | `--delete` 鏡像模式 |
| 打包 .zip/.tar.gz | `zip` / `tar` crate | rsync 不做壓縮打包 |
| 寫 LTO 磁帶 | `tar` crate | rsync 無法寫磁帶 |
#### rsync CLI 參數
| 參數 | 用途 |
|------|------|
| `-a` | archive mode保留權限、時間戳 |
| `-v` | verbose進度顯示 |
| `-P` | 等同 `--partial --progress`(續傳 + 進度) |
| `-c` | checksum modeSHA256 驗證,非 time/size |
| `-n` | dry-run遷移前預覽 |
| `--delete` | 鏡像模式NAS 同步用) |
### 2.6 Group Share 跨 DB 查詢
使用 SQLite `ATTACH DATABASE`
```sql
ATTACH DATABASE '/path/to/groups/1.sqlite' AS g;
SELECT f.*, gf.permission
FROM file_registry f
JOIN g.file_registry gf ON f.file_uuid = gf.file_uuid;
```
**優勢:** 一行 SQL 解決Rust 端不需額外合併邏輯。
### 2.7 非同步策略
```
axum handler (async)
├── 快速操作(直接 await
│ ├── serde_json 序列化
│ ├── 驗證
│ └── 記憶體操作
└── 阻塞操作spawn_blocking
├── rusqlite 查詢
├── std::fs 檔案操作
├── SHA256 計算
└── 壓縮/解壓
```
**原則:** axum handler 本身是 async遇到 rusqlite 或 std::fs 時,一律用 `tokio::task::spawn_blocking` 包裝。
---
## 3. file_uuid 規範
### 3.1 計算公式
```
file_uuid = SHA256(mac_address | birthday | physical_path_at_birth | filename)[0:32]
```
詳細規範參見 `REFERENCE/file_uuid_spec.md`
### 3.2 MarkBase 中的使用
| 欄位 | 來源 | 說明 |
|------|------|------|
| file_uuid | Momentry Core | MarkBase 不重新計算,直接復用 |
| 驗證 | `is_birth_uuid()` | 長度 32不含 `_` |
| 關聯 | 主鍵 | `file_registry.file_uuid``file_nodes.file_uuid` |
### 3.3 整合流程
```
Momentry Core MarkBase
(檔案註冊) (匯入)
┌──────────┐ ┌──────────┐
│ compute_ │ │ INSERT │
│ birth_ │──── file_uuid ───▶│ INTO │
│ uuid() │ 32 hex │ file_ │
│ │ │ registry │
└──────────┘ │(file_uuid)
└──────────┘
```
---
## 4. 虛擬檔案樹
### 4.1 FileNode 結構
```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileNode {
/// 節點唯一 IDUUIDv4
pub node_id: String,
/// 顯示名稱
pub label: String,
/// 多語言別名
pub aliases: Aliases,
/// 關聯的 file_uuidMomentry Core 來源)
pub file_uuid: Option<String>,
/// 父節點 node_idroot 為 None
pub parent_id: Option<String>,
/// 子節點列表
pub children: Vec<String>,
/// 節點類型
pub node_type: NodeType,
/// 自訂圖示emoji 或 SVG 路徑)
pub icon: Option<String>,
/// 文字顏色CSS hex
pub color: Option<String>,
/// 背景顏色CSS hex
pub bg_color: Option<String>,
/// 建立時間
pub created_at: String,
/// 最後修改時間
pub updated_at: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Aliases {
/// 繁體中文
pub zh_tw: Option<String>,
/// 英文
pub en_us: Option<String>,
/// 日文
pub ja_jp: Option<String>,
/// 韓文
pub ko_kr: Option<String>,
/// 法文
pub fr_fr: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum NodeType {
/// 虛擬資料夾(用戶建立,不對應實體路徑)
Folder,
/// 實體檔案(指向 file_uuid
File,
/// 動態層級auto-layer 產生)
DynamicLayer,
}
```
### 4.2 SQLite Schemauser.sqlite
```sql
CREATE TABLE IF NOT EXISTS file_nodes (
node_id TEXT PRIMARY KEY,
label TEXT NOT NULL,
aliases_json TEXT NOT NULL DEFAULT '{}',
file_uuid TEXT,
parent_id TEXT,
children_json TEXT NOT NULL DEFAULT '[]',
node_type TEXT NOT NULL DEFAULT 'file',
icon TEXT,
color TEXT,
bg_color TEXT,
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
sort_order INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY (file_uuid) REFERENCES file_registry(file_uuid)
);
CREATE TABLE IF NOT EXISTS file_registry (
file_uuid TEXT PRIMARY KEY,
original_name TEXT NOT NULL,
file_size INTEGER,
file_type TEXT,
registered_at TEXT NOT NULL,
last_seen_at TEXT,
status TEXT NOT NULL DEFAULT 'active'
);
```
### 4.3 Display Modes
用戶可切換四種展示模式(儲存在 `localStorage.display_mode`
| 模式 | 枚舉值 | 說明 | 實作模組 |
|------|--------|------|----------|
| **List** | `list` | 列表檢視:名稱、大小、日期 | `modes/list.rs` |
| **Tree** | `tree` | 樹狀檢視:展開/折疊層級 | `modes/tree.rs`Phase 1 |
| **Small Icon** | `grid_sm` | 小圖示網格:適合縮圖檢視 | `modes/grid_sm.rs` |
| **Large Icon** | `grid_lg` | 大圖示網格:適合影片預覽 | `modes/grid_lg.rs` |
每種模式實作 `DisplayMode` trait參見 §1.1)。
### 4.4 多語言別名
| 欄位 | 語言 | 用途 |
|------|------|------|
| `zh_tw` | 繁體中文 | 預設語言 |
| `en_us` | 英文 | 國際使用 |
| `ja_jp` | 日文 | 日本用戶 |
| `ko_kr` | 韓文 | 韓國用戶 |
| `fr_fr` | 法文 | 法國/國際用戶 |
用戶在前端選擇語言後系統自動顯示對應別名。若該語言的別名不存在fallback 到 `label`
### 4.5 自動分層規則
系統根據預設規則自動為檔案建立虛擬層級:
| 規則 | 條件 | 層級結構 |
|------|------|----------|
| **by_type** | 相同副檔名 | `Videos/``Images/``Documents/``Audio/``Other/` |
| **by_date** | 按建立日期 | `2026/``2026/05/``2026/05/14/` |
| **by_size** | 按檔案大小 | `<10MB``10100MB``100MB1GB``>1GB` |
`auto_layer.rs` 實作,使用 `NodeType::DynamicLayer` 標記。
---
## 5. 群組分享
### 5.1 Group SQLite 架構(選項 A
```
data/groups/
├── groups.sqlite # 群組註冊表(全域)
│ └── groups(
│ group_id INTEGER PRIMARY KEY,
│ group_name TEXT,
│ db_path TEXT, # 指向 1.sqlite
│ created_by TEXT, # 建立者 user_id
│ created_at TEXT
│ )
├── 1.sqlite # 群組 1 的共用資料
└── 2.sqlite # 群組 2 的共用資料
```
### 5.2 Group SQLite Schema
```sql
-- groups/1.sqlite
CREATE TABLE group_members (
user_id TEXT NOT NULL,
role TEXT NOT NULL DEFAULT 'viewer', -- owner / editor / viewer
joined_at TEXT NOT NULL DEFAULT (datetime('now')),
PRIMARY KEY (user_id)
);
CREATE TABLE group_files (
file_uuid TEXT NOT NULL,
added_by TEXT NOT NULL,
added_at TEXT NOT NULL DEFAULT (datetime('now')),
PRIMARY KEY (file_uuid),
FOREIGN KEY (added_by) REFERENCES group_members(user_id)
);
```
### 5.3 跨 DB 查詢ATTACH
```rust
pub fn get_group_files(conn: &Connection, group_id: i64) -> Result<Vec<GroupFile>> {
let group_db = format!("/data/groups/{}.sqlite", group_id);
conn.execute_batch(&format!("ATTACH DATABASE '{}' AS g", group_db))?;
let mut stmt = conn.prepare("
SELECT f.file_uuid, f.original_name, gm.role
FROM main.file_registry f
JOIN g.group_files gf ON f.file_uuid = gf.file_uuid
JOIN g.group_members gm ON gf.added_by = gm.user_id
")?;
// ...
}
```
### 5.4 角色權限
| 角色 | 讀取 | 寫入 | 刪除 | 邀請成員 |
|------|:----:|:----:|:----:|:----:|
| owner | ✅ | ✅ | ✅ | ✅ |
| editor | ✅ | ✅ | ❌ | ❌ |
| viewer | ✅ | ❌ | ❌ | ❌ |
---
## 6. 儲存層級
### 6.1 三級定義
| 層級 | 符號 | 延遲 | 速度 | 成本 | 典型媒體 |
|------|:----:|------|------|------|----------|
| **Hot** | 🔥 | <10ms | 高速 | 高 | NVMe SSD / 內建硬碟 |
| **Warm** | 🌡️ | 10500ms | 中等 | 中 | NAS網路掛載 |
| **Cold** | ❄️ | >1s | 低速 | 低 | LTO 磁帶 / 外接 HDD |
### 6.2 掛載點設定
管理員可設定每個層級的掛載路徑:
```json
{
"tiers": {
"hot": ["/Users/accusys/sftpgo/data", "/Volumes/RAID5/projects"],
"warm": ["/Volumes/NAS_Archive"],
"cold": ["/Volumes/LTO_Archive"]
}
}
```
### 6.3 自動歸檔規則
管理員可設定自動歸檔觸發條件:
```json
{
"auto_archive": {
"enabled": true,
"rules": [
{
"condition": "idle_days > 90",
"action": "move_to_warm",
"schedule": "0 2 * * 0"
},
{
"condition": "idle_days > 365",
"action": "move_to_cold",
"schedule": "0 3 * * 0"
},
{
"condition": "tier_hot_usage > 80%",
"action": "move_oldest_to_warm",
"schedule": "0 * * * *"
}
]
}
}
```
### 6.4 file_uuid 層級遷移
file_uuid **在遷移過程中不變**。檔案從 Hot 移到 Cold
1. 複製檔案到 Cold tier 路徑
2. 驗證完整性SHA256
3. 寫入 `location_history` 記錄新位置
4. 移除 Hot tier 的原始檔案
5. `file_registry.last_seen_at` 更新
file_uuid 永遠指向 birth 時的 `physical_path_at_birth`Hot 路徑),不因遷移而改變。
### 6.5 location_history 表
```sql
CREATE TABLE IF NOT EXISTS location_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
file_uuid TEXT NOT NULL,
location TEXT NOT NULL, -- 實際檔案路徑
tier TEXT NOT NULL, -- hot / warm / cold
moved_at TEXT NOT NULL DEFAULT (datetime('now')),
reason TEXT,
moved_by TEXT,
verified INTEGER DEFAULT 0, -- 完整性驗證通過
FOREIGN KEY (file_uuid) REFERENCES file_registry(file_uuid)
);
CREATE INDEX idx_location_history_file_uuid ON location_history(file_uuid);
```
查詢目前位置:
```sql
SELECT location, tier
FROM location_history
WHERE file_uuid = ?
ORDER BY moved_at DESC
LIMIT 1;
```
---
## 7. 檔案操作 API
### 7.1 操作總覽
| 操作 | API | 說明 |
|------|-----|------|
| **Compress** | `POST /api/v2/files/compress` | 壓縮為 .zip 或 .tar.gz |
| **Transfer** | `POST /api/v2/files/transfer` | 複製/移動到 target tier |
| **Archive** | `POST /api/v2/files/archive` | 歸檔到 Cold tier |
| **Restore** | `POST /api/v2/files/restore` | 從 Cold tier 還原到 Hot tier |
| **Exit** | `POST /api/v2/files/exit` | 從 MarkBase 移除(保留記錄) |
### 7.2 壓縮
```rust
// Compress 請求
{
"file_uuids": ["uuid1", "uuid2"],
"format": "zip", // "zip" | "tar.gz"
"output_path": "/path/to/output.zip"
}
// Compress 回應
{
"status": "completed",
"output_path": "/path/to/output.zip",
"file_count": 2,
"compressed_size": 1048576
}
```
### 7.3 Transfer層級遷移
#### 請求/回應
```rust
// Transfer 請求
{
"file_uuids": ["uuid1"],
"target_tier": "cold",
"target_path": "/Volumes/LTO_Archive/2026/",
"delete_source": false
}
// Transfer 回應
{
"status": "completed",
"file_uuid": "uuid1",
"new_location": "/Volumes/LTO_Archive/2026/uuid1.mp4",
"new_tier": "cold"
}
```
#### Transfer Engine 實作流程
```
TransferEngine::execute(source, target, opts)
├── 1. select_mode(source)
│ │
│ ├── size < 50MB ──→ DirectMode
│ └── size >= 50MB ──→ RsyncMode (fallback: DirectMode)
├── 2. preflight (RsyncMode)
│ ├── rsync -an --checksum source/ target/
│ └── 回傳變更清單,供用戶確認
├── 3. transfer
│ │
│ ├── DirectMode: std::fs::copy + progress callback
│ │
│ └── RsyncMode: rsync -avP --checksum source target
│ ├── -a archive mode
│ ├── -v verbose (進度)
│ ├── -P --partial (續傳) + --progress (進度)
│ └── -c checksum mode (SHA256 驗證替代 time/size)
├── 4. verify (RsyncMode)
│ └── rsync -acn source target (dry-run checksum應為空)
├── 5. update location_history
│ └── INSERT INTO location_history (file_uuid, location, tier, ...)
└── 6. cleanup
└── if delete_source: remove source file
```
#### Rsync vs Direct 選擇
| 條件 | 模式 | 原因 |
|------|:----:|------|
| `file_size < 50 MB` | Direct | rsync overhead > 效益 |
| `file_size >= 50 MB` 且 rsync 存在 | Rsync | 增量、續傳、校驗和 |
| `file_size >= 50 MB` 且 rsync 不存在 | Direct | 優雅 fallback |
### 7.4 Archive / Restore
Archive 為 Transfer 到 Cold tier 的便捷包裝。
Restore 為從 Cold tier 還原到 Hot tier 的便捷包裝。
```rust
// Restore 請求
{
"file_uuid": "uuid1",
"target_path": "/Users/demo/restored/" // 選填,預設為原始 birth path
}
// Restore 回應
{
"status": "completed",
"file_uuid": "uuid1",
"restored_to": "/Users/demo/restored/uuid1.mp4"
}
```
### 7.5 Exit 記錄
檔案移出 MarkBase 管理時,保留記錄以供審計:
```sql
CREATE TABLE IF NOT EXISTS exit_records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
file_uuid TEXT NOT NULL,
original_name TEXT NOT NULL,
exited_at TEXT NOT NULL DEFAULT (datetime('now')),
exited_by TEXT NOT NULL,
reason TEXT,
last_location TEXT,
FOREIGN KEY (file_uuid) REFERENCES file_registry(file_uuid)
);
```
```rust
// Exit 請求
{
"file_uuid": "uuid1",
"reason": "Project completed, moved to long-term archive"
}
// Exit 回應
{
"status": "completed",
"file_uuid": "uuid1",
"exited_at": "2026-05-14T10:00:00Z"
}
```
---
## 8. API 參考
### 8.1 Tree API
| 方法 | 路徑 | 說明 |
|------|------|------|
| `GET` | `/api/v2/tree/:user_id` | 取得用戶的完整虛擬樹 |
| `GET` | `/api/v2/tree/:user_id?mode=list` | 以特定模式取得樹 |
| `POST` | `/api/v2/tree/:user_id/node` | 建立新節點 |
| `PUT` | `/api/v2/tree/:user_id/node/:node_id` | 更新節點label、icon、color、aliases |
| `DELETE` | `/api/v2/tree/:user_id/node/:node_id` | 刪除節點 |
| `PUT` | `/api/v2/tree/:user_id/node/:node_id/move` | 移動節點(變更 parent |
| `PATCH` | `/api/v2/tree/:user_id/node/:node_id/alias` | 更新特定語言的別名 |
### 8.2 File API
| 方法 | 路徑 | 說明 |
|------|------|------|
| `GET` | `/api/v2/files/:file_uuid` | 取得檔案資訊 |
| `POST` | `/api/v2/files/compress` | 壓縮檔案 |
| `POST` | `/api/v2/files/transfer` | 轉移檔案到 target tier |
| `POST` | `/api/v2/files/archive` | 歸檔到 Cold tier |
| `POST` | `/api/v2/files/restore` | 從 Cold tier 還原 |
| `POST` | `/api/v2/files/exit` | 移出管理 |
| `GET` | `/api/v2/files/:file_uuid/locations` | 查詢位置歷史 |
| `POST` | `/api/v2/files/validate` | 驗證檔案完整性SHA256 |
### 8.3 Mount API
| 方法 | 路徑 | 說明 |
|------|------|------|
| `GET` | `/api/v2/mounts` | 列出所有掛載點 |
| `POST` | `/api/v2/mounts` | 註冊新的掛載點 |
| `PUT` | `/api/v2/mounts/:mount_id` | 更新掛載點 |
| `DELETE` | `/api/v2/mounts/:mount_id` | 移除掛載點 |
| `GET` | `/api/v2/mounts/:mount_id/status` | 查詢掛載點狀態(是否在線、容量) |
### 8.4 Group API
| 方法 | 路徑 | 說明 |
|------|------|------|
| `GET` | `/api/v2/groups` | 列出所有群組 |
| `POST` | `/api/v2/groups` | 建立新群組 |
| `DELETE` | `/api/v2/groups/:group_id` | 刪除群組 |
| `POST` | `/api/v2/groups/:group_id/members` | 邀請成員 |
| `DELETE` | `/api/v2/groups/:group_id/members/:user_id` | 移除成員 |
| `PUT` | `/api/v2/groups/:group_id/members/:user_id/role` | 變更角色 |
| `POST` | `/api/v2/groups/:group_id/files` | 分享檔案到群組 |
| `DELETE` | `/api/v2/groups/:group_id/files/:file_uuid` | 從群組移除檔案 |
| `GET` | `/api/v2/groups/:group_id/files` | 列出群組檔案 |
---
## 9. 決策記錄
| # | 日期 | 決策 | 理由 |
|---|------|------|------|
| 1 | 2026-05-13 | Rust modular architecture (DisplayMode trait) | 與 Momentry Core 相同生態,模組化利於擴展 |
| 2 | 2026-05-13 | One user = one SQLite | 用戶隔離、簡單部署、檔案可攜 |
| 3 | 2026-05-13 | Group Share → Option A (Group SQLite) | 獨立可攜、不需專屬 server、備份簡單 |
| 4 | 2026-05-13 | Hot/Warm/Cold 三級儲存 | 真實世界檔案管理需求,結合 LTO/NAS/SSD |
| 5 | 2026-05-13 | Auto-archive rules (admin-configurable) | 減少手動管理idle days + tier 容量觸發 |
| 6 | 2026-05-14 | file_uuid 從 Momentry Core 繼承,不重新計算 | 唯一來源,避免不一致 |
| 7 | 2026-05-14 | file_uuid 不因層級遷移而改變 | 凍結在 birth 時刻,確保身份穩定 |
| 8 | 2026-05-14 | Display mode 儲存在 localStorage | 純 UI 偏好,不需後端儲存 |
| 9 | 2026-05-14 | 檔案操作 API-first | 後端邏輯完成後再加 UI壓縮、傳輸、歸檔 |
| 10 | 2026-05-14 | Exit records保留記錄 | 審計需求,不直接刪除記錄 |
| 11 | 2026-05-14 | rusqlite (同步) + spawn_blocking (異步包裝) | 避免整個堆疊都必須 async保持簡單 |
| 12 | 2026-05-14 | ATTACH DATABASE for Group Share 跨 DB 查詢 | 一行 SQL不需 Rust 端合併 |
| 13 | 2026-05-14 | notify crate (僅 Hot tier) | 減少資源消耗Warm/Cold 變更頻率低 |
| 14 | 2026-05-14 | zip + tar crate (不用外部 CLI) | 跨平台,不需 ditto/hdiutil |
| 15 | 2026-05-14 | Momentry Core 整合 A+B 混合模式 | 輕量運算用 crate重查詢用 HTTP API |
---
## 10. 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-05-12 | 初版設計Demo Display + Knowledge Graph | M4 / OpenCode | DeepSeek V4 Pro |
| V2.0 | 2026-05-14 | 虛擬檔案樹、Group Share、儲存層級、技術棧、file_uuid、檔案操作 API | M4 / OpenCode | DeepSeek V4 Pro |

View File

@@ -0,0 +1,730 @@
# MarkBase — Momentry 專屬 Display Engine 設計方案 v1.0
## 產品定位
**MarkBase** 是 Momentry 專屬的 Display Engine擔任 **demo runner 的固定顯示器**
不只是 Markdown 閱讀器,而是一個可控的內容呈現視窗,能夠動態展示:
| 內容類型 | 展示方式 |
|----------|----------|
| .md 文件 | 渲染為排版清晰的 HTML |
| Mermaid 圖表 | 流程圖、時序圖、ER 圖等 |
| API 回應 JSON | 語法高亮的格式化 JSON |
| 影片 | 嵌入 video player支援 HLS / MP4|
| 圖片 | 支援單張或輪播 |
| HTML | 直接內嵌 |
| 文字/程式碼 | syntax highlight |
**定位一句話:** *Demo runner 的 presentation layer一個專注、乾淨、可控的內容顯示器。*
| 面向 | 說明 |
|------|------|
| 願景 | Momentry 生態系的 UI 輸出終端 |
| 核心場景 | demo runner 的固定 display 視窗 |
| 平台 | macOS nativeRust + axum + Tauri WebView|
| 授權 | Momentry 專屬工具,隨 momentry_core 發布 |
---
## 命名
**MarkBase** — Markdown + Display Base
> 承載所有內容類型的顯示基底。
> 簡短、好記、產品感。
---
## 階段規劃
### Phase 0Demo DisplayMVP — 立即價值)
**目標**:取代 md_reader + 影片播放,成為 demo runner 的固定顯示視窗
| 功能 | 說明 |
|------|------|
| 文件渲染 | CommonMark + GFM表格、task list、strikethrough、footnotes|
| Mermaid 圖表 | 內建渲染(無需 CDN支援 flowchart / sequence / class / ER / mindmap |
| 程式碼高亮 | syntax highlighting支援 50+ 語言)|
| JSON 格式化 | API response 自動格式化 + 語法高亮 |
| 影片播放 | MP4 / HLS 嵌入播放(取代 browser 開啟 trace video|
| 全螢幕 mode | 乾淨無干擾的展示模式,適合 presentation |
| CLI 控制 | 透過 stdin / HTTP 動態載入內容,無需重新啟動 |
| 與 demo runner 整合 | `--display` flag 啟動作為固定顯示視窗 |
#### Demo Runner 整合流程
```
demo_runner.py --display MarkBase.app (固定顯示視窗)
┌────────────────────┐ ┌────────────────────┐
│ Step 3: Markdown │ ──HTTP──▶│ 渲染 GUIDE.md │
│ Step 11: Trace 5 │ ──HTTP──▶│ 播放 trace_5.mp4 │
│ Step 13: 3D Cube │ ──HTTP──▶│ 顯示 iframe: portal │
│ Step 22: API resp │ ──HTTP──▶│ 顯示格式化 JSON │
└────────────────────┘ └────────────────────┘
(控制端) (顯示端)
```
demo runner 透過 `--display` 啟動 MarkBase 作為顯示視窗,然後每步透過 HTTP 推送內容:
```python
# demo_runner.py 範例
step_type = "markdown" POST /display {"type":"md","file":"GUIDE.md"}
step_type = "video" POST /display {"type":"video","url":"trace_5.mp4"}
step_type = "curl" POST /display {"type":"json","data":response}
step_type = "browser" POST /display {"type":"url","url":"..."}
```
### Phase 2Knowledge Base
**目標**:從閱讀器升級為個人知識庫管理器
| 功能 | 說明 |
|------|------|
| 多文件索引 | 監控目錄,自動索引所有 .md |
| 全文檢索 | 跨文件模糊搜尋 + 標題索引 |
| 標籤管理 | YAML frontmatter tags → 標籤雲 |
| Backlinks | 文件間的雙向連結([[wiki-link]]|
| 收藏/書籤 | 標記常用文件 |
| 閱讀歷史 | 最近開啟 / 最近搜尋 |
### Phase 3Collaboration
**目標**:多人協作與發布
| 功能 | 說明 |
|------|------|
| 評論/註釋 | 段落層級註解 |
| 版本歷史 | git-based diff 檢視 |
| 靜態站點生成 | .md → 整站 HTML用於發布|
| Web 版本 | 瀏覽器可讀(可選自托管)|
---
## CLI 設計Portal / Demo 使用)
### 主要命令
```
markbase display ← 啟動顯示視窗blocking等待 HTTP 控制)
markbase display "GUIDE.md" ← 啟動並立刻顯示文件
markbase preview "GUIDE.md" ← (保留) 單次預覽,不回傳控制權
markbase render "GUIDE.md" ← (保留) 輸出 HTML 到 stdout
```
### display — 核心命令(給 demo runner 使用)
```bash
# 啟動顯示視窗demo runner 透過 HTTP 控制
markbase display
# 指定控制埠(預設 11438
markbase display --port 11438
# 全螢幕模式
markbase display --fullscreen
# 啟動時先顯示文件
markbase display GUIDE.md
```
### HTTP 控制 APIdisplay 模式下啟用)
`markbase display` 啟動後在 `localhost:11438` 監聽控制請求:
```bash
# 顯示 .md 文件
curl -X POST http://localhost:11438/display \
-H "Content-Type: application/json" \
-d '{"type":"md","file":"/path/to/doc.md","focus":"API 搜尋"}'
# 播放影片
curl -X POST http://localhost:11438/display \
-d '{"type":"video","url":"/path/to/trace.mp4","start":10,"end":30}'
# 顯示格式化 JSON
curl -X POST http://localhost:11438/display \
-d '{"type":"json","data":"{\"status\":\"ok\"}"}'
# 內嵌網頁
curl -X POST http://localhost:11438/display \
-d '{"type":"url","url":"http://localhost:1420/trace-viz/..."}'
# 顯示圖片
curl -X POST http://localhost:11438/display \
-d '{"type":"image","url":"/path/to/thumbnail.jpg"}'
# 控制命令
curl -X POST http://localhost:11438/control \
-d '{"cmd":"fullscreen"}'
curl -X POST http://localhost:11438/control \
-d '{"cmd":"zoom","level":1.5}'
curl -X POST http://localhost:11438/control \
-d '{"cmd":"close"}'
```
### demo_runner.py 整合
```python
class MarkBaseDisplay:
"""控制 MarkBase 顯示視窗。"""
def __init__(self, port=11438):
self.port = port
self.process = None
def start(self):
self.process = subprocess.Popen(["markbase", "display",
"--port", str(self.port)], ...)
time.sleep(1) # wait for server
def show(self, type, **kwargs):
"""顯示內容。type: md/video/json/url/image"""
body = {"type": type, **kwargs}
requests.post(f"http://localhost:{self.port}/display", json=body)
def show_step(self, step):
"""根據 demo step 類型自動選擇顯示方式。"""
t = step["type"]
if t == "curl":
self.show("json", data=run_curl(step["cmd"]))
elif t == "browser":
self.show("url", url=step["url"])
elif t == "markdown":
self.show("md", file=step["cmd"], focus=step.get("focus"))
elif t == "video":
self.show("video", url=step.get("url"))
---
## 技術架構
```
┌─────────────────────────────────────────┐
│ MarkBase App │
├─────────────────┬───────────────────────┤
│ Frontend │ Engine │
│ (SwiftUI) │ (Rust core) │
│ │ │
│ • 視窗管理 │ • 解析 .md → AST │
│ • 選單、快捷鍵 │ • Mermaid 渲染 │
│ • 設定介面 │ • Code highlight │
│ • 搜尋 UI │ • 全文索引 │
│ • 目錄樹 │ • 文件監控 │
└─────────────────┴───────────────────────┘
│ │
▼ ▼
macOS Native API Rust 二進制
(WebKit + Swift) (pulldown-cmark + syntect + mermaid-rs)
```
### 為什麼 Engine 用 Rust
| 原因 | 說明 |
|------|------|
| 效能 | 大型 .md 文件1000+ 行)瞬間渲染 |
| 無 runtime | 單一二進制,無 Node.js/Python 依賴 |
| 現有基礎 | 可直接重用 md_reader 的 rendering 邏輯 |
| Mermaid 內嵌 | 可用 mermaid-rs crate 替代 CDN |
### 為什麼 Frontend 用 SwiftUI
| 原因 | 說明 |
|------|------|
| Native 體驗 | macOS native 視窗、menu bar、快捷鍵 |
| WebKit 整合 | 直接嵌入 WKWebView 渲染 HTML |
| 系統整合 | Spotlight、QuickLook、分享功能 |
| 效能 | 比 Electron 省 200MB+ 記憶體 |
---
## UI 設計
### 主視窗佈局
```
┌────────────────────────────────────────────────┐
│ Menu Bar: File Edit View Window Help │
├──────────┬─────────────────────────────────────┤
│ │ │
│ 左側欄 │ 主內容區 │
│ ────── │ ───────────────── │
│ 📁 文件 │ # 標題 │
│ ├ README│ 正文... │
│ ├ Guide│ ```code block``` │
│ └ API │ 表格 │
│ │ [Mermaid diagram] │
│ 目錄 │ │
│ ────── │ │
│ • Introduction│ │
│ • Getting...│ │
│ • API Ref │ │
│ │ │
├──────────┴─────────────────────────────────────┤
│ Status Bar: 字數 | 段落 | UTF-8 | dark mode toggle│
└────────────────────────────────────────────────┘
```
### 快捷鍵
| 按鍵 | 功能 |
|------|------|
| `Cmd+O` | 開啟 .md 文件 |
| `Cmd+F` | 全文搜尋 |
| `Cmd+Shift+F` | 跨文件搜尋 |
| `Cmd++` / `Cmd+-` | 調整字級 |
| `Cmd+D` | Toggle dark mode |
| `Cmd+B` | 左側目錄 toggle |
| `Cmd+P` | 列印 / PDF 匯出 |
| `Esc` | 關閉搜尋 / 回到瀏覽 |
---
## 目錄結構
```
markbase/
├── Cargo.toml # Rust core
├── src/
│ ├── main.rs # CLI entry point
│ ├── render.rs # .md → HTML
│ ├── highlight.rs # Code syntax highlighting
│ ├── mermaid.rs # Mermaid rendering
│ ├── search.rs # Full-text search
│ └── watch.rs # File watcher
├── app/ # SwiftUI app
│ ├── MarkBase.xcodeproj
│ ├── MarkBase/
│ │ ├── ContentView.swift
│ │ ├── SidebarView.swift
│ │ ├── SearchView.swift
│ │ └── SettingsView.swift
│ └── markbase-cli # Embedded Rust binary
└── docs/
└── ARCHITECTURE.md
```
---
## 與現有 md_reader 的差異
| 面向 | md_reader | MarkBase |
|------|-----------|----------|
| 語言 | 純 Rust CLI | Rust engine + SwiftUI app |
| 架構 | 單一 main.rs 1134 行 | 模組化 6+ 檔案 |
| 視窗 | 簡陋的 WebKit 視窗 | 完整 SwiftUI + WKWebView |
| 搜尋 | ❌ 無 | ✅ Cmd+F + 跨文件搜尋 |
| 目錄 | ❌ 無 | ✅ 左側 heading tree |
| File watcher | ❌ 無 | ✅ 自動索引目錄 |
| dark mode | ❌ 無 | ✅ 系統跟隨 + 手動 |
| Mermaid | CDN-based | 內建引擎 |
| Code highlight | ❌ 無 | ✅ syntect 50+ 語言 |
| 命名 | 功能描述 | 產品品牌 |
---
## 技術選型記錄
> 2026-05-12 新增
### 1. 轉檔引擎
| 工具 | License | 用途 |
|------|---------|------|
| pandoc 3.9 | GPL 2.0 | MD ↔ DOCX/PPTX/PDF |
| LibreOffice 26.2 | Apache 2.0 | 任何格式 ↔ 任何格式 (headless CLI) |
| mmdc | MIT | Mermaid → SVG/PNG |
| rsvg-convert | LGPL | SVG → PNG |
### 2. 編輯器選型
| 方案 | 決策 | 理由 |
|------|:--:|------|
| CodeMirror 6 | ✅ 選用 | MIT, 190KB gzip, CDN 免 npm, 模組化 |
| Monaco (VS Code) | ❌ | 5MB 太大,需 webpack |
| Ace | ❌ | 維護停滯 |
### 3. Markdown 生態分析
| 工具 | License | 類型 | MarkBase 啟發 |
|------|---------|------|--------------|
| glow | MIT | CLI 渲染 | 保留為獨立 CLI viewer |
| MarkText | MIT | WYSIWYG GUI | 參考 split-pane 編輯/預覽設計 |
| mdcat | MPL 2.0 | CLI | 參考 terminal 圖片渲染 |
| bat | MIT/Apache | CLI | 參考語法高亮策略 |
| mdBook | MPL 2.0 | CLI | 作為靜態文件站匯出格式 |
| MkDocs | BSD | CLI | 備選文件站方案 |
| Obsidian | Proprietary | Desktop PKM | 參考 `[[wiki links]]`、graph view、backlinks |
### 4. 桌面 vs Web
| 決策 | 選擇 | 理由 |
|------|:--:|------|
| Web first | ✅ | 任何裝置可用,同一份 HTML/JS/CSS |
| Tauri shell | ✅ 可選 | <10MB, 跨平台 macOS/Win/Linux |
| Electron | ❌ | 300MB 過於肥大 |
### 5. MarkBase vs Obsidian 定位
| | Obsidian | MarkBase |
|------|:--:|:--:|
| 定位 | 個人知識管理 (PKM) | **文件處理引擎 + 編輯器** |
| 資料格式 | .md only | 全格式 (via soffice) |
| 搜尋 | 全文 | RAG + embedding (Qdrant) |
| 後端 | 無 | axum HTTP + PSQL + Qdrant |
| CLI | 無 | ✅ CLI first |
| Pipeline | 無 | ✅ Chunking + LLM pipeline |
| 跨裝置 | 付費 sync | 自建 server 即可 |
| 大小 | ~300MB (Electron) | <10MB (Tauri) |
| 授權 | Proprietary (個人免費) | Momentry 專屬 |
### 6. CLI 設計
```
markbase display [--port 11438] [FILE] 啟動顯示伺服器
markbase render <FILE> [-o output.html] Markdown → HTML
markbase serve <DIR> 檔案瀏覽 + 編輯器 (計畫中)
```
### 7. 架構對比
```
Obsidian: MarkBase:
┌──────────────────────┐ ┌──────────────────────┐
│ Electron Shell │ │ Tauri / Browser │
│ ┌────────────────┐ │ │ ┌────────────────┐ │
│ │ Renderer │ │ │ │ Renderer │ │
│ │ ├─ CodeMirror │ │ │ │ ├─ CodeMirror │ │ ← 相同
│ │ ├─ Graph/D3 │ │ │ │ ├─ Mermaid.js │ │ ← 相同
│ │ ├─ Mermaid.js │ │ 相同 │ │ └─ pulldown │ │
│ │ └─ MathJax │ │ │ └────────────────┘ │
│ └────────────────┘ │ │ ┌────────────────┐ │
│ ┌────────────────┐ │ │ │ Rust Backend │ │ ← MarkBase 獨有
│ │ Plugin API │ │ │ │ ├─ axum HTTP │ │
│ │ 1,800+ plugins │ │ │ │ ├─ Embedding │ │
│ └────────────────┘ │ │ │ ├─ Qdrant ANN │ │
│ ┌────────────────┐ │ │ │ ├─ pgvector │ │
│ │ FS Access │ │ │ │ ├─ PG TKG │ │
│ │ .md files only │ │ │ │ ├─ SQLite TKG │ │
│ │ └────────────────┘ │ │ │ ├─ sqlite-vec │ │
│ └──────────────────────┘ │ │ └─ Pipeline │ │
```
### 8. 向量儲存sqlite-vec + Datasette
> 2026-05-12 採用
#### 選型
| 需求 | pgvector (PG) | Qdrant | sqlite-vec | 決策 |
|------|:--:|:--:|:--:|:--:|
| Production API (3003) | ✅ | — | — | pgvector (已有) |
| HNSW ANN 搜尋 | ⚠️ | ✅ | — | Qdrant (已有) |
| Desktop 本機 RAG | ❌ 需裝 PG | ❌ 需 server | ✅ 單檔 | sqlite-vec |
| 檔案包內嵌向量 | ❌ | ❌ | ✅ 隨包分發 | sqlite-vec |
| 離線可用 | ❌ | ❌ | ✅ | sqlite-vec |
| Web UI 查詢 | — | — | via Datasette | Datasette |
#### sqlite-vec 規格
| 屬性 | 值 |
|------|-----|
| License | MIT + Apache 2.0(雙授權) |
| 作者 | Alex Garcia |
| 贊助 | Mozilla Builders + Fly.io + Turso + SQLite Cloud |
| Stars | 7,600+ |
| 語言 | Pure C零依賴 |
| 大小 | ~200KB `.dylib` |
| ANN 引擎 | exhaustive, IVF, DiskANN |
| Rust binding | `cargo add sqlite-vec` |
#### Datasette選配 Web UI
| 屬性 | 值 |
|------|-----|
| License | Apache 2.0 |
| 作者 | Simon Willison |
| 定位 | SQLite → Web UI + JSON API |
| Plugins | 154 個 |
| sqlite-vec 插件 | `datasette-sqlite-vec`(同一作者) |
#### 使用範例
```sql
.load ./vec0
CREATE VIRTUAL TABLE chunks USING vec0(
embedding float[768],
file_uuid text,
chunk_type text,
text_content text
);
INSERT INTO chunks VALUES (?, 'uuid-123', 'sentence', 'hello world');
SELECT rowid, text_content, distance
FROM chunks WHERE embedding MATCH ?
ORDER BY distance LIMIT 10;
```
#### 四層向量架構
```
Production ← Qdrant (HNSW ANN, fast at scale)
← pgvector (transactional, alongside chunk data)
↓ backup / export
Portable ← sqlite-vec (.sqlite single file, package distributable)
← Datasette (optional Web UI)
```
### 9. Qdrant Graph 分析
> 2026-05-12 結論Qdrant **沒有**原生 Graph 功能,是純向量資料庫
#### Qdrant 現有功能
| 功能 | 說明 | 圖論等級 |
|------|------|:--:|
| **Payload filtering** | 向量搜尋 + JSON 條件過濾 | ⚠️ 偽關聯查詢 |
| **Collection aliases** | 多 collection 聯合查詢 | ⚠️ 基礎 |
| **Hybrid Queries** | 向量 + 關鍵字混合 | ❌ |
| **Qdrant Edge** | 嵌入式向量搜尋 | ❌ 非 Graph |
| **Data Graphs (第三方)** | Neo4j + Qdrant hybrid RAG | ✅ 非原生 |
#### Payload filtering 的極限
可以模擬 1-hop 關係(例如「找 Cary Grant 說話的 chunk」但不能做真正的 graph traversal
```json
// ✅ 1-hopfilter speaker = "Cary Grant"
{"filter": {"must": [{"key": "speaker", "match": {"value": "Cary Grant"}}]}}
// ❌ 2-hopgraph traversal Qdrant 無法做到
// "誰跟 Cary Grant 在同一個場景出現?"
// "這些人中誰又跟 Audrey Hepburn 對話?"
```
| 限制 | 說明 |
|------|------|
| ❌ 2-hop+ traversal | 無法跨節點關聯查詢 |
| ❌ 邊緣權重/時間 | 無 edge property 概念 |
| ❌ Graph algebra | 無 `shortest_path`, `PageRank` 等演算法 |
| ❌ Cypher/GQL | 無圖查詢語言 |
#### Momentry TKG 決策
| | Qdrant-only | PG TKG | SQLite TKG | Neo4j |
|---|:--:|:--:|:--:|:--:|
| 向量搜尋 | ✅ 原生 | via pgvector | via sqlite-vec | via plugin |
| Graph traversal | ❌ | ✅ CTE | ✅ CTE | ✅ 原生 |
| 2-hop+ 查詢 | ❌ | ✅ | ✅ | ✅ |
| 時間範圍邊緣 | ❌ | ✅ | ✅ | ✅ |
| 部署 | 需 server | 需 PG | **單檔** | 需 Java |
| 檔案包分發 | ❌ | ❌ | ✅ | ❌ |
| 適合規模 | 大 | 中 | 小-中 | 大 |
#### 架構分工
```
Qdrant → 向量搜尋ANN- 核心效能
PG → TKG 圖查詢Recursive CTE- API server
SQLite → TKG 圖查詢Recursive CTE- 檔案包/離線
```
---
## 亮點:知識圖譜 (Knowledge Graph)
> 2026-05-12 新增
### Obsidian vs MarkBase 圖譜對比
| | Obsidian Graph | MarkBase Knowledge Graph |
|------|:--:|:--:|
| 節點來源 | 手動建立的 `.md` 筆記 | AI pipeline 自動產生的 chunks |
| 邊緣來源 | 手寫 `[[wikilinks]]` | **語意相似度**、結構層級、共現關係 |
| 生成方式 | 人工 | **自動**embedding + clustering |
| 影片支援 | ❌ | ✅ face traces, speaker graph, scene transitions |
| 實體辨識 | ❌ | ✅ 人臉/說話者/物件/場景 |
| 規模 | 數百節點 | **數萬節點**chunk 級) |
| 過濾 | 無 | 時間範圍、置信度、chunk type |
### 圖譜類型
#### A. 語意關係圖Semantic Graph
以 embedding 餘弦相似度建立邊緣,相近 chunk 靠近。
```
[Audrey Hepburn 說話] ──0.82── [Cary Grant 回應]
│ │
│ 0.75 │ 0.78
▼ ▼
[討論離婚原因] ──0.91── [緊張對話場景]
```
**演算法**
1. 取所有 chunk embedding
2. 計算 pairwise cosine similarity
3. 保留 top-K 相似邊K=5 預設)
4. 用 UMAP/t-SNE → 2D 座標
5. D3.js force layout 渲染
#### B. 結構層級圖Hierarchy Graph
文件 → 章節 → 段落 的三層樹狀結構。
#### C. 人物關係圖Identity Graph
基於 face_detections + speaker_assign。
```
Cary Grant ──[對手戲]── Audrey Hepburn
│ │
│[對話] │[場景共現]
▼ ▼
Walter Matthau ────── Ned Glass
```
#### D. 時序演進圖Timeline Graph
Chunks 按時間軸排列場景切換點標記。X 軸 = 時間Y 軸 = 說話者。
### 渲染技術
| 層 | 工具 | License |
|----|------|---------|
| 力導向佈局 | D3-force (d3.js v7) | ISC |
| 降維 (UMAP) | umap-js | MIT |
| 2D 繪圖 | Canvas / SVG via D3 | ISC |
| 3D 繪圖 | Three.js | MIT |
| 節點過濾 | Crossfilter / vanilla JS | — |
### API 設計
```
GET /api/v1/graph/:file_uuid/identity → 人物關係圖資料
GET /api/v1/graph/:file_uuid/semantic?depth=3 → 語意圖資料
GET /api/v1/graph/:file_uuid/hierarchy → 結構層級圖
GET /api/v1/graph/:file_uuid/timeline → 時序圖資料
```
回傳格式:
```json
{
"nodes": [
{"id": "chunk_100", "label": "Cary Grant: What's your name?", "group": 3, "x": 0.1, "y": 0.5}
],
"edges": [
{"source": "chunk_100", "target": "chunk_104", "weight": 0.82, "type": "semantic"}
]
}
```
### 互動設計
| 操作 | 行為 |
|------|------|
| Drag node | 拖曳節點 |
| Click node | 展開 chunk 內容預覽 |
| Scroll | 縮放圖譜 |
| Filter bar | 依 chunk_type / speaker / confidence 過濾 |
| Double-click | 聚焦該節點,展開子圖 |
| Hover edge | 顯示相似度分數 |
### 圖譜渲染工具選型
> 2026-05-12 新增
#### 候選工具對比
| 工具 | License | 大小 | CDN | 圖論演算法 | 中國社群 | 最佳場景 |
|------|---------|:--:|:--:|:--:|:--:|------|
| **Cytoscape.js** | MIT | ~120KB | ✅ | ✅ BFS/DFS/PageRank | ⚠️ | 複雜網絡圖 |
| D3.js v7 | ISC | ~80KB | ✅ | ❌ 需自寫 | ⚠️ | 任何自訂圖表 |
| ECharts | Apache 2.0 | ~1MB | ✅ | ❌ | ✅ 非常大 | 通用圖表 + 地圖 |
| G6 (AntV) | MIT | ~500KB | ✅ | ✅ 多種佈局 | ✅ 非常大 | 關係圖專用 |
| vis-network | MIT/Apache | ~300KB | ✅ | ❌ | ❌ | 網絡圖 |
| Sigma.js | MIT | ~80KB | ✅ | ❌ | ❌ | WebGL 大圖 (>5000節點) |
| Graphviz | EPL 1.0 | ~3MB | ❌ CLI only | ✅ | ⚠️ | 靜態匯出 SVG/PNG |
#### 選型過程
**第一輪篩選**:排除 CLI-only (Graphviz)、無 CDN、中文社群弱且圖論支援差的 (vis-network, Sigma.js)。
剩餘Cytoscape.js, D3.js, ECharts, G6。
**第二輪深度評估**
| | Cytoscape.js | D3.js | ECharts | G6 |
|---|:--:|:--:|:--:|:--:|
| 力導向佈局 | ✅ 9 種 | ✅ 自寫 | ✅ 1 種內建 | ✅ 9 種 |
| 複合節點 (compound) | ✅ | ❌ | ❌ | ✅ |
| 圖論演算法 | ✅ 內建 | ❌ | ❌ | ✅ |
| JSON → Graph | ✅ 原生 | ⚠️ 手動 | ⚠️ 手動 | ✅ 原生 |
| TreeGraph | ⚠️ 需擴展 | ✅ | ❌ | ✅ 專用 |
| 大型圖效能 | ⚠️ (>5000會慢) | ✅ | ✅ Canvas | ✅ |
| 互動 API | ✅ 豐富 | ✅ 最靈活 | ✅ | ✅ |
| 零外部依賴 | ✅ | ✅ | ❌ (zrender) | ❌ |
**最終決策**
| 場景 | 選用 | 理由 |
|------|:--:|------|
| 知識圖譜核心 | **Cytoscape.js** | 圖論演算法、fCoSE 佈局、JSON 原生對接、Obsidian/Mermaid 都用 |
| 統計輔助圖表 | **ECharts** | 中文社群大、Apache 背書、長條/圓餅/分佈圖開箱即用 |
| 樹狀層級圖 | **G6 TreeGraph** | 專用 API文件結構圖最簡潔 |
| 自訂特殊需求 | **D3.js** | 保底方案,任何無法滿足的圖表 |
#### Cytoscape.js 使用者背書
| 組織 | 用途 |
|------|------|
| **Mermaid** | 流程圖/時序圖渲染引擎 |
| **Obsidian** | 知識圖譜 (Graph View) |
| Amazon, Google, Meta, Microsoft | 內部網絡圖視覺化 |
| IBM, Cisco, Tencent, Uber | 網路拓樸視覺化 |
| GitHub | 相依性圖 |
#### 整合架構
```
MarkBase Knowledge Graph:
┌──────────────────────────────────────┐
│ 圖譜類型 渲染引擎 │
│ ───────── ──────── │
│ 語意關係圖 → Cytoscape.js │
│ 結構層級圖 → G6 TreeGraph │
│ 人物關係圖 → Cytoscape.js │
│ 時序演進圖 → ECharts timeline │
│ 降維散點圖 → D3.js │
│ 統計分佈圖 → ECharts │
│ │
│ 全部 CDN 載入,無需 npm │
└──────────────────────────────────────┘
```
### 在 MarkBase 中的整合
```
MarkBase Control Bar:
⏮ ◀ ▶ ⏭ | Graph | Tree | Edit | 🔍
Knowledge Graph View
```
---
## 開發路線圖
| 階段 | 時程 | 交付 |
|------|:----:|------|
| P0 Core rendering | ✅ Done | Rust engine: .md→HTML with Mermaid + AJAX refresh |
| P1 macOS app | ✅ Done | Tauri shell (可選) |
| P2 File tree + Editor | 2-3d | CodeMirror 6 + lazy-load 樹狀瀏覽 + 存檔 |
| P3 Knowledge Graph | 3-5d | Cytoscape.js + G6 + ECharts: 語意/結構/人物關係圖譜 |
| P4 Knowledge base | 3-5d | 多文件索引、全文檢索、backlinks |
| P5 Export | 2d | 轉檔 CLI (md→pdf/docx/pptx) |
| P6 Collaboration | 5-10d | 評論、版本、靜態站點 |

View File

@@ -0,0 +1,111 @@
# Release Notes — v1.0.0 (Production 3002)
**Date**: 2026-05-13
**Build**: `301da08`
**Deployed by**: M4
---
## Release Scope
### Binaries
| Binary | Size | Source |
|--------|:----:|--------|
| `momentry` (production) | 21 MB | M5 build |
| `release` CLI | 3.5 MB | M5 build |
### File Packages
| Package | UUID | Content |
|---------|------|---------|
| Charade (HD) | `aeed71342a899fe4b4c57b7d41bcb692` | 1920×1080, 25fps |
| Charade (YouTube) | `23b1c872379d4ec06479e5ed39eef4c5` | 640×360, 23.98fps |
### Data Per Package
| Table | HD | YouTube |
|-------|:--:|:--:|
| chunk | 2,407 | 2,340 |
| chunk_vectors (768D) | 2,407 | 2,340 |
| face_detections | 70,691 | 70,729 |
| identities (TMDB) | 15 actors | 280 clusters |
| identity_bindings | 18,635 | 18,635 |
| tkg_nodes | 6,457 | 5,776 |
| tkg_edges | 21,028 | 18,847 |
### TMDB Matched Actors
| Actor | TMDB ID | Package |
|-------|:------:|:--:|
| Cary Grant | 2638 | HD |
| Audrey Hepburn | 1932 | HD |
| James Coburn | 5563 | HD |
| George Kennedy | 12950 | HD |
| Dominique Minot | 41714 | HD |
| Ned Glass | 18870 | HD |
| Jacques Marin | 26890 | HD |
| Paul Bonifas | 41716 | HD |
---
## Schema Changes
| Change | Schema |
|--------|--------|
| `chunks``chunk` (rename) | public |
| Drop `old_chunk_id`, `chunk_index` | public |
| Add `timestamp_secs` to `face_detections` | public |
| Add `file_uuid` to `identities` | public |
| Drop `chunk_vectors_chunk_id_key` (duplicate unique) | public |
---
## Verification
### API (all 200)
| Category | Endpoints | Result |
|----------|-----------|:--:|
| Health | `/health`, `/health/detailed` | ✅ |
| Auth | login, logout | ✅ |
| Files | list, scan, detail, probe | ✅ |
| Chunk | `/file/{uuid}/chunk/{id}` | ✅ |
| Search | universal, frames, visual | ✅ |
| Identities | list, detail, files, chunks | ✅ |
| Face Trace | sortby, faces, 3D | ✅ |
| Media | video, thumbnail, trace video | ✅ |
| Resources | list | ✅ |
### Database
| Table | Count |
|-------|------:|
| Files registered | 38 |
| TMDB identities | 15 |
| Total chunks (both files) | 4,747 |
| Total faces (both files) | 141,420 |
| Total TKG nodes | 12,233 |
| Total TKG edges | 39,875 |
---
## Deployment Process
1. **Backup**: Full `public` schema dump (1.3 GB)
2. **Schema Migration**: `chunks→chunk`, drop deprecated columns, add new columns
3. **Deploy aeed7134**: All 8 tables imported via `sed dev.→public.` per-table SQL files
4. **Deploy 23b1c87**: Fixed `chunk_vectors_chunk_id_key` constraint conflict, all 8 tables imported
5. **Binary Swap**: Old binary stopped, M5 `momentry_v1.0.0` deployed, restarted on 3002
6. **Verification**: All API endpoints 200, both files queryable
---
## Known Notes
| Item | Note |
|------|------|
| `/files` status | API hardcodes `"ready"`, does not reflect actual DB status (reported to M5) |
| `/files/scan` | Only scans video extensions (mp4/mov/mkv/avi/webm), misses jpg/png |
| deploy.sh schema | Uses `sed dev.→public.` for public deployment (pending M5 native SCHEMA support) |
| chunk_vectors constraint | `chunk_vectors_chunk_id_key` dropped from public (was preventing multi-file import) |

View File

@@ -0,0 +1,280 @@
# Release SOP — Dev → Production (3002)
**Date**: 2026-05-13
**Version**: 2.0
**Status**: Draft — M5 Review Required
---
## 1. Prerequisites
### 必須確認
| # | 檢查項 | 方法 | 門檻 |
|:--:|------|------|:--:|
| 1 | Dev (3003) 39/39 API test | `bash api_test.sh` | ✅ 39/39 |
| 2 | 檔案內容包匯入成功 | 9 tables all > 0 | ✅ 通過 |
| 3 | TMDB identities 匹配 | `SELECT * FROM dev.identities WHERE source='tmdb'` | ✅ 7 actors |
| 4 | TKG 到位 | tkg_nodes + tkg_edges > 0 | ✅ 通過 |
| 5 | Release binary 已編譯 | `cargo build --release --bin momentry` | ✅ 成功 |
| 6 | `.env.development` 切換確認 | `DATABASE_SCHEMA=public` | ✅ |
## 2. Backup
### 2.1 Database
```bash
BACKUP_DIR="/Users/accusys/momentry_core_releases/backup_$(date +%Y%m%d_%H%M)"
mkdir -p "$BACKUP_DIR"
# Full public schema dump
pg_dump -U accusys -d momentry --schema=public \
--no-owner --no-acl \
> "$BACKUP_DIR/public_schema_full.sql"
# Data-only dump (smaller, faster restore)
pg_dump -U accusys -d momentry --schema=public \
--data-only --no-owner --no-acl \
> "$BACKUP_DIR/public_schema_data.sql"
echo "Backup: $BACKUP_DIR"
```
### 2.2 Binary
```bash
cp /path/to/release/momentry "$BACKUP_DIR/momentry_$(date +%Y%m%d)"
```
### 2.3 Release Info
```bash
echo "Release: v1.0.0" > "$BACKUP_DIR/RELEASE_INFO.txt"
echo "Date: $(date)" >> "$BACKUP_DIR/RELEASE_INFO.txt"
echo "Binary: $(ls -la target/release/momentry)" >> "$BACKUP_DIR/RELEASE_INFO.txt"
echo "Schema: public" >> "$BACKUP_DIR/RELEASE_INFO.txt"
```
## 3. Schema Migration
### 3.1 Apply Migration
```sql
-- Rename chunks → chunk (if not already done)
ALTER TABLE IF EXISTS public.chunks RENAME TO chunk;
-- Drop deprecated columns
ALTER TABLE public.chunk DROP COLUMN IF EXISTS old_chunk_id;
ALTER TABLE public.chunk DROP COLUMN IF EXISTS chunk_index;
-- Add new columns (v1.0.3+)
ALTER TABLE public.face_detections ADD COLUMN IF NOT EXISTS timestamp_secs float8;
```
### 3.2 Verify Schema
```sql
SELECT column_name, data_type
FROM information_schema.columns
WHERE table_schema = 'public' AND table_name = 'chunk'
ORDER BY ordinal_position;
```
Expected: 24 columns, no `old_chunk_id`, no `chunk_index`.
## 4. Deploy Package
### 4.1 Deploy via deploy.sh
```bash
cd path/to/package/
PG_BIN="/opt/homebrew/opt/postgresql@18/bin" \
DB_NAME="momentry" DB_USER="accusys" \
DATABASE_SCHEMA=public \
bash deploy.sh
```
### 4.2 Manual Verification (after deploy)
```sql
SELECT 'chunk' as tbl, count(*) FROM public.chunk WHERE file_uuid = '{uuid}';
SELECT 'identities (tmdb)', count(*) FROM public.identities WHERE source = 'tmdb';
SELECT 'tkg_nodes', count(*) FROM public.tkg_nodes WHERE file_uuid = '{uuid}';
SELECT 'tkg_edges', count(*) FROM public.tkg_edges WHERE file_uuid = '{uuid}';
```
Expected: chunk > 0, identities > 0, tkg_nodes > 0, tkg_edges > 0.
### 4.3 Set Status
```sql
UPDATE public.videos SET status = 'completed' WHERE file_uuid IN (
'aeed71342a899fe4b4c57b7d41bcb692',
'23b1c872379d4ec06479e5ed39eef4c5'
);
```
## 5. Binary Swap & Restart
### 5.1 Stop Old Server
```bash
# Graceful stop
pkill -TERM momentry
sleep 5
# Force if needed
pkill -9 momentry 2>/dev/null
```
### 5.2 Deploy New Binary
```bash
cp target/release/momentry /path/to/production/binary
chmod +x /path/to/production/binary
```
### 5.3 Start New Server
```bash
DATABASE_SCHEMA=public /path/to/production/binary server --port 3002
# or with .env
MOMENTRY_SERVER_PORT=3002 DATABASE_SCHEMA=public cargo run --release -- server
```
### 5.4 Health Check
```bash
curl http://localhost:3002/health
# Expected: {"status":"ok","version":"1.0.0","build_git_hash":"..."}
```
## 6. Verification
### 6.1 API Tests
```bash
API_KEY="muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69"
HOST="http://localhost:3002"
# Health
curl "$HOST/health"
curl "$HOST/health/detailed"
# Files
curl -H "X-API-Key: $API_KEY" "$HOST/api/v1/files?page=1&page_size=5"
curl -H "X-API-Key: $API_KEY" "$HOST/api/v1/files/scan"
# Search
curl -X POST -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"query":"Audrey Hepburn","uuid":"aeed71342a899fe4b4c57b7d41bcb692","limit":3}' \
"$HOST/api/v1/search/universal"
# Identities
curl -H "X-API-Key: $API_KEY" "$HOST/api/v1/identities?page=1&page_size=5"
# Chunk detail (v1.0.3+)
curl -H "X-API-Key: $API_KEY" "$HOST/api/v1/file/aeed71342a899fe4b4c57b7d41bcb692/chunk/0"
# Face trace
curl -X POST -H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-d '{"sort_by":"face_count","limit":3}' \
"$HOST/api/v1/file/aeed71342a899fe4b4c57b7d41bcb692/face_trace/sortby"
# Trace video
curl -o /dev/null -w "%{http_code}" -H "X-API-Key: $API_KEY" \
"$HOST/api/v1/file/aeed71342a899fe4b4c57b7d41bcb692/trace/1934/video?padding=1"
# Expected: 200
# Full test suite
bash api_test.sh
# Expected: 39/39
```
### 6.2 Portal Check
| Page | URL | Expect |
|------|-----|--------|
| FilesView | `http://localhost:1420/files` | Charade = ✅ 已就绪 |
| SearchView | `http://localhost:1420/search` | File dropdown has Charade |
| Trace View | File → Traces | Face list visible |
| Video Playback | Search → Play | Video plays |
### 6.3 Demo
```bash
DEMO_BASE="http://localhost:3002" \
DEMO_FILE="aeed71342a899fe4b4c57b7d41bcb692" \
python3 scripts/demo_runner.py API_V1.0.0/DEMO_SCRIPT_v1.0.0.json --auto --speed 0.03
# Expected: 21/21 steps passed
```
## 7. Rollback
### 7.1 Binary Rollback
```bash
pkill momentry
cp "$BACKUP_DIR/momentry_$(date +%Y%m%d)" /path/to/production/binary
DATABASE_SCHEMA=public /path/to/production/binary server --port 3002
```
### 7.2 Schema Rollback
```sql
-- Rename back if needed
ALTER TABLE public.chunk RENAME TO chunks;
```
### 7.3 Data Rollback
```bash
psql -U accusys -d momentry < "$BACKUP_DIR/public_schema_full.sql"
```
### 7.4 Downtime Estimation
| Step | Est. Time |
|------|:--------:|
| Backup | 2 min |
| Schema migration | 1 min |
| Package deploy | 5-10 min |
| Binary swap | 1 min |
| Verification | 5 min |
| **Total** | **~15-20 min** |
## 8. Decision Log
| # | Decision | Owner | Decision |
|---|----------|:--:|------|
| 1 | Production binary source | M5 | M5 提供 release binary or M4 自編譯 |
| 2 | Schema: rename chunks→chunk? | M5 | M5 決定 public schema 結構 |
| 3 | Identity merge strategy | M5 | Keep prod 15 TMDB + merge with dev data? |
| 4 | Downtime window | M5 | 維護模式 (403 all) or hard stop? |
| 5 | Release scope | M5 | `aeed7134` only or both HD + YouTube? |
| 6 | .env / config for public schema | M5 | Production binary reads `DATABASE_SCHEMA=public` |
---
## Appendix: Release Checklist
- [ ] Dev 3003 passed all tests (39/39 + demo 21/21)
- [ ] Production old binary backed up
- [ ] Production DB full backup
- [ ] Schema migration SQL ready
- [ ] Package deployed to public schema
- [ ] Status set to completed
- [ ] New binary deployed
- [ ] Server restarted on 3002
- [ ] Health check returns ok
- [ ] 39/39 API tests pass on 3002
- [ ] Portal shows files correctly
- [ ] Demo runs 21/21 on 3002

View File

@@ -0,0 +1,418 @@
---
document_type: "spec"
service: "MOMENTRY_CORE"
title: "file_uuid 規格文件"
date: "2026-05-14"
version: "V1.0"
status: "active"
owner: "M4"
created_by: "OpenCode"
tags:
- "file-uuid"
- "birth-uuid"
- "sha256"
- "storage"
- "identity"
- "naming-convention"
- "markbase"
ai_query_hints:
- "查詢 file_uuid 規格文件的內容"
- "file_uuid 計算公式是什麼"
- "file_uuid 與 birth_uuid 的關係"
- "如何產生 file_uuid"
- "file_uuid 路徑凍結規則"
- "MAC 位址優先級定義"
- "file_uuid 跨層級遷移如何處理"
- "file_uuid vs uuid 命名規範"
related_documents:
- "REFERENCE/MARKBASE_DESIGN_V2.0.md"
- "REFERENCE/SPATIAL_COORDINATE_REGISTRY.md"
---
# file_uuid 規格文件
| 項目 | 內容 |
|------|------|
| 建立者 | M4 / OpenCode |
| 建立時間 | 2026-05-14 |
| 文件版本 | V1.0 |
---
## 版本歷史
| 版本 | 日期 | 目的 | 操作人 | 工具/模型 |
|------|------|------|--------|-----------|
| V1.0 | 2026-05-14 | 建立 file_uuid 規範文件 | M4 / OpenCode | DeepSeek V4 Pro |
---
## 概述
本文件定義 Momentry Core 生態系中 **file_uuid** 的統一規格。file_uuid 是一個檔案出生時產生的不變標識符用於跨系統Momentry Core、MarkBase的唯一檔案識別。
**核心原則:**
- file_uuid 在檔案首次註冊birth時計算一次之後永不改變。
- file_uuid 包含位置訊息(路徑),作為身份的一部分。
- 同一檔案移動到不同路徑會產生不同的 file_uuid新位置 = 新身份)。
- file_uuid 從 Momentry Core 繼承MarkBase 直接復用。
---
## 關鍵術語定義
| 術語 | 定義 |
|------|------|
| file_uuid | 檔案出生標識符32 字元十六進制字串 |
| birth_uuid | 與 file_uuid 同義,強調「出生時計算」的語意 |
| birthday | 檔案首次註冊時的 ISO 8601 時間戳,固定不變 |
| physical_path_at_birth | 檔案出生時的規範化絕對路徑 |
| MAC address | 本機內建網路介面的 MAC 位址en0 > en1 > en2 |
| SHA256 | SHA-256 雜湊函數 |
---
## 1. 計算公式
### 1.1 公式
```
file_uuid = SHA256(mac_address | birthday | physical_path_at_birth | filename)[0:32]
```
分隔符為 `|`pipe 字元)。
### 1.2 成分說明
| 成分 | 格式 | 範例 | 說明 |
|------|------|------|------|
| mac_address | `xx:xx:xx:xx:xx:xx` | `a1:b2:c3:d4:e5:f6` | 本機內建網路介面(非外接) |
| birthday | ISO 8601 + 時區 | `2026-04-27T22:00:00+08:00` | 檔案首次註冊時間戳 |
| physical_path_at_birth | 絕對路徑 | `/Users/demo/raw/video.mp4` | 去除尾端 `/` |
| filename | 純檔名 | `video.mp4` | 包含副檔名 |
### 1.3 計算範例
輸入:
```text
mac: a1:b2:c3:d4:e5:f6
birthday: 2026-04-27T22:00:00+08:00
path: /Users/demo/raw
file: video.mp4
```
組合字串:
```text
a1:b2:c3:d4:e5:f6|2026-04-27T22:00:00+08:00|/Users/demo/raw|video.mp4
```
SHA256
```text
e5f7a1b2c3d4... (64 字元)
```
取前 32 字元:
```text
e5f7a1b2c3d4e5f6a7b8c9d0e1f2a3b4
```
### 1.4 Rust 實作momentry_core
```rust
use sha2::{Digest, Sha256};
pub fn compute_birth_uuid(
mac_address: &str,
birthday: &str,
path: &str,
filename: &str,
) -> String {
let key = format!(
"{}|{}|{}|{}",
mac_address,
birthday,
path.trim_end_matches('/'),
filename
);
let hash = Sha256::digest(key.as_bytes());
hex::encode(hash)[0..32].to_string()
}
```
參考路徑:`src/core/storage/uuid.rs:141`
---
## 2. 路徑規範
### 2.1 路徑來源
`physical_path_at_birth` 來自檔案首次被系統發現時的位置。在 Momentry Core 中,這通常是 SFTPGo 用戶的家目錄:
```
/Users/accusys/sftpgo/data/demo/raw/video.mp4
```
### 2.2 路徑正規化規則
| 規則 | 說明 |
|------|------|
| 絕對路徑 | 必須使用絕對路徑(`/` 開頭) |
| 去除尾端 `/` | `trim_end_matches('/')` |
| 不解引用符號連結 | 路徑是記錄值,不解析 symlink |
| 不處理 `..` | 路徑是記錄值,不正規化 `..` |
| 保留大小寫 | macOS 預設不區分大小寫,但路徑保留原始大小寫 |
### 2.3 路徑凍結原則
file_uuid 計算後,`physical_path_at_birth` 凍結不變。
| 情境 | file_uuid 行為 |
|------|---------------|
| 檔案移到同層級不同目錄 | **不變**file_uuid 已凍結) |
| 檔案複製到另一路徑 | **新生產**(新位置 = 新 file_uuid |
| 檔案從 Hot 移到 Cold tier | **不變**(路徑改變,但 file_uuid 仍指向原始 birth 路徑) |
| 檔案重新命名 | **不變**filename 已凍結在 birth 時) |
---
## 3. MAC 位址獲取規範
### 3.1 優先級
| 優先級 | 介面 | 說明 |
|:------:|------|------|
| 0 | en0 | Wi-Fi通常是內建最優先 |
| 1 | en1 | 第二內建網路介面 |
| 2 | en2 | 第三內建網路介面 |
| 3 | enN (N>=3) | 其他 en 開頭介面 |
| 100 | 其他 | 非 en 開頭介面(最後才用) |
### 3.2 排除規則
| 排除條件 | 理由 |
|----------|------|
| MAC = `00:00:00:00:00:00` | 無效 MAC |
| MAC = `ff:ff:ff:ff:ff:ff` | 廣播位址 |
| 外接 Thunderbolt/USB 網卡 | 非內建 |
| 可拆卸的硬體 | 換機器後會改變 |
### 3.3 Fallback
若所有介面都不符合,回傳 `00:00:00:00:00:00` 作為 fallback。
### 3.4 實作方式
使用 `ifconfig -a` 解析 `ether``lladdr`macOS
參考路徑:`src/core/storage/uuid.rs:61`
---
## 4. Birthday 格式規範
### 4.1 格式
```
YYYY-MM-DDTHH:MM:SS±HH:MM
```
例如:`2026-04-27T22:00:00+08:00`
### 4.2 時間來源
使用系統時鐘(`chrono::Local::now()`),在檔案首次註冊時記錄。
### 4.3 精確度
秒級(不含毫秒)。
### 4.4 穩定性要求
- birthday 一旦設定,永不改變。
- 即使是同一天重複註冊同一檔案birthday 也不變(已存在則跳過)。
- 若因故需要重新計算 file_uuid不建議必須使用原始 birthday而非當前時間。
---
## 5. 格式規範
### 5.1 長度
固定 32 字元16 進制0-9, a-f
### 5.2 範例
```
e5f7a1b2c3d4e5f6a7b8c9d0e1f2a3b4
384b0ff44aaaa1f1
aeed71342a899fe4b4c57b7d41bcb692
```
### 5.3 驗證函數
```rust
pub fn is_birth_uuid(uuid: &str) -> bool {
uuid.len() == 32 && !uuid.contains('_')
}
```
參考路徑:`src/core/storage/uuid.rs:159`
---
## 6. 命名規範
### 6.1 統一使用 `file_uuid`
| 命名 | 狀態 | 說明 |
|------|:----:|------|
| `file_uuid` | ✅ **統一使用** | 所有新程式碼、API、資料庫欄位 |
| `uuid` | ❌ 廢棄 | V3.x 命名,不再使用 |
| `UUID` | ❌ 廢棄 | 不使用大寫變體 |
| `video_uuid` | ❌ 廢棄 | V3.x 命名,改為 `file_uuid` |
| `birth_uuid` | ⚠️ 同義詞 | 內部函數命名可保留,對外一律 `file_uuid` |
### 6.2 資料庫欄位
```sql
-- 所有包含 file_uuid 的表格
file_uuid VARCHAR(32) NOT NULL
```
### 6.3 API 欄位
```json
{
"file_uuid": "aeed71342a899fe4b4c57b7d41bcb692"
}
```
---
## 7. 跨系統整合
### 7.1 Momentry Core
- 計算:`compute_birth_uuid()` @ `src/core/storage/uuid.rs:141`
- 使用位置:`src/core/ingestion.rs`(檔案註冊時)
- 資料庫:`videos.file_uuid`, `files.file_uuid`, `chunk.file_uuid`, `file_identities.file_uuid`
### 7.2 MarkBase
- **復用 Momentry Core 的 file_uuid**,不重新計算。
- MarkBase 的 `file_registry` 表中,`file_uuid` 來自 Momentry Core。
- MarkBase 的 SQLite 資料庫使用 `file_uuid` 作為檔案的主鍵關聯。
### 7.3 整合模式
```
Momentry Core (PostgreSQL) MarkBase (SQLite)
┌──────────────────────┐ ┌──────────────────────┐
│ videos.file_uuid │───copy───▶│ file_registry.file_uuid │
│ files.file_uuid │ │ file_nodes.file_uuid │
│ chunk.file_uuid │ │ │
└──────────────────────┘ └──────────────────────┘
```
MarkBase 不重新計算 file_uuid而是從 Momentry Core 匯入。
---
## 8. 唯一性分析
### 8.1 碰撞機率
32 字元 hex = 128 bits。
- 單一機器上的檔案數:< 10^6
- 生日悖論n=10^6 在 2^128 空間中的碰撞機率 ≈ 1.5 × 10^-27
- **結論:實際上不可能碰撞。**
### 8.2 跨機器唯一性
不同機器的 MAC 不同 → file_uuid 不同(即使路徑和生日相同)。
### 8.3 同檔案不同路徑
同一檔案從 `/demo/video.mp4` 複製到 `/demo/archive/video.mp4`
- MAC 相同、生日不同(或相同)、路徑不同 → file_uuid 不同
- **結論:不同路徑被視為不同檔案。**
---
## 9. 儲存層級遷移
### 9.1 遷移情境
檔案從 Hot tier 遷移到 Cold tier
```
Hot: /Volumes/RAID5/projects/video.mp4
Cold: /Volumes/LTO_Archive/2026/video.mp4
```
### 9.2 file_uuid 行為
file_uuid **不變**。它永遠指向 birth 時的 `physical_path_at_birth`Hot 路徑)。
### 9.3 位置追蹤
遷移後,實際位置記錄在 `location_history` 表中(由 MarkBase 管理),與 file_uuid 分離:
```sql
CREATE TABLE location_history (
file_uuid TEXT NOT NULL,
location TEXT NOT NULL, -- 目前實際路徑
tier TEXT NOT NULL, -- hot/warm/cold
moved_at TEXT NOT NULL, -- 遷移時間
reason TEXT
);
```
### 9.4 檔案不在原位時的處理
查詢 `location_history` 取最新記錄,取得 `location` 欄位的目前路徑。若無記錄,則使用 birth 路徑。
---
## 10. 相容性
### 10.1 向前相容V3.x → V4.0
| V3.x 欄位 | V4.0 對應 |
|-----------|-----------|
| `video_uuid` | `file_uuid`(概念對齊,公式不同) |
### 10.2 資料遷移
V3.x 使用 `compute_uuid()`(僅路徑+檔名V4.0 使用 `compute_birth_uuid()`(路徑+檔名+MAC+birthday
- 已是相同的 32 字元格式
- 不變更已存在的 file_uuid資料庫不回溯更新
- 新註冊的檔案使用新公式
### 10.3 舊 UUID 格式
`compute_uuid()` 產生的 32 字元 hex 字串與 `compute_birth_uuid()` 在格式上完全相容(都是 32 hex驗證函數 `is_birth_uuid()` 對兩者都會返回 `true`
---
## 11. 反模式與禁止事項
| 禁止 | 理由 |
|------|------|
| ❌ 使用 `uuid` 命名欄位 | 模糊不清,無法區分是 file_uuid 還是 identity_uuid |
| ❌ 基於當前路徑重新計算 file_uuid | 破壞身份穩定性 |
| ❌ 使用相對路徑 | 不同工作目錄產生不同結果 |
| ❌ 排除 MAC 位址 | 跨機器唯一性無法保證 |
| ❌ 依賴檔案內容雜湊 | 內容可能改變,且計算成本高 |
| ❌ 使用人類可讀的 UUID (如 UUIDv4) | 缺乏位置編碼語意 |
---
## 版本資訊
- 版本: V1.0
- 建立日期: 2026-05-14