docs: fix start/end → start_frame/end_frame in API docs
This commit is contained in:
@@ -338,7 +338,7 @@ Returns video with face bounding boxes overlaid.
|
|||||||
|
|
||||||
```
|
```
|
||||||
GET /api/v1/file/{file_uuid}/video/bbox
|
GET /api/v1/file/{file_uuid}/video/bbox
|
||||||
Query: ?start=0&end=300&face_uuid=xxx
|
Query: ?start_frame=0&end_frame=300&face_uuid=xxx
|
||||||
```
|
```
|
||||||
|
|
||||||
Returns `video/mp4` binary with red bboxes drawn at frame intervals.
|
Returns `video/mp4` binary with red bboxes drawn at frame intervals.
|
||||||
|
|||||||
@@ -338,7 +338,7 @@ Returns video with face bounding boxes overlaid.
|
|||||||
|
|
||||||
```
|
```
|
||||||
GET /api/v1/file/{file_uuid}/video/bbox
|
GET /api/v1/file/{file_uuid}/video/bbox
|
||||||
Query: ?start=0&end=300&face_uuid=xxx
|
Query: ?start_frame=0&end_frame=300&face_uuid=xxx
|
||||||
```
|
```
|
||||||
|
|
||||||
Returns `video/mp4` binary with red bboxes drawn at frame intervals.
|
Returns `video/mp4` binary with red bboxes drawn at frame intervals.
|
||||||
|
|||||||
@@ -338,7 +338,7 @@ Returns video with face bounding boxes overlaid.
|
|||||||
|
|
||||||
```
|
```
|
||||||
GET /api/v1/file/{file_uuid}/video/bbox
|
GET /api/v1/file/{file_uuid}/video/bbox
|
||||||
Query: ?start=0&end=300&face_uuid=xxx
|
Query: ?start_frame=0&end_frame=300&face_uuid=xxx
|
||||||
```
|
```
|
||||||
|
|
||||||
Returns `video/mp4` binary with red bboxes drawn at frame intervals.
|
Returns `video/mp4` binary with red bboxes drawn at frame intervals.
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ https://api.momentry.ddns.net/api/v1/file/3abeee81d94597629ed8cb943f182e94/trace
|
|||||||
|
|
||||||
Browser 開:
|
Browser 開:
|
||||||
```
|
```
|
||||||
https://api.momentry.ddns.net/api/v1/file/3abeee81d94597629ed8cb943f182e94/video/bbox?start=68000&end=69000
|
https://api.momentry.ddns.net/api/v1/file/3abeee81d94597629ed8cb943f182e94/video/bbox?start_frame=68000&end_frame=69000
|
||||||
```
|
```
|
||||||
|
|
||||||
**預期**: 該區間內所有臉部偵測的 bbox overlay 影片
|
**預期**: 該區間內所有臉部偵測的 bbox overlay 影片
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ https://api.momentry.ddns.net/api/v1/file/3abeee81d94597629ed8cb943f182e94/trace
|
|||||||
|
|
||||||
Browser 開:
|
Browser 開:
|
||||||
```
|
```
|
||||||
https://api.momentry.ddns.net/api/v1/file/3abeee81d94597629ed8cb943f182e94/video/bbox?start=68000&end=69000
|
https://api.momentry.ddns.net/api/v1/file/3abeee81d94597629ed8cb943f182e94/video/bbox?start_frame=68000&end_frame=69000
|
||||||
```
|
```
|
||||||
|
|
||||||
**預期**: 該區間內所有臉部偵測的 bbox overlay 影片
|
**預期**: 該區間內所有臉部偵測的 bbox overlay 影片
|
||||||
|
|||||||
@@ -133,8 +133,8 @@ curl "$BASE/api/v1/file/$FILE/trace/2/faces?limit=2&interpolate=true" -H "$KEY"
|
|||||||
| # | Method | Path | Description |
|
| # | Method | Path | Description |
|
||||||
|---|--------|------|-------------|
|
|---|--------|------|-------------|
|
||||||
| 29 | GET | `/api/v1/file/:file_uuid/thumbnail` | Frame JPEG (?frame=&x=&y=&w=&h=) |
|
| 29 | GET | `/api/v1/file/:file_uuid/thumbnail` | Frame JPEG (?frame=&x=&y=&w=&h=) |
|
||||||
| 30 | GET | `/api/v1/file/:file_uuid/video` | Raw video (?start=&end=) |
|
| 30 | GET | `/api/v1/file/:file_uuid/video` | Raw video (start_frame=&end_frame=) |
|
||||||
| 31 | GET | `/api/v1/file/:file_uuid/video/bbox` | Bbox overlay (?start=&end=&duration=) |
|
| 31 | GET | `/api/v1/file/:file_uuid/video/bbox` | Bbox overlay (start_frame=&end_frame=&duration=) |
|
||||||
| 32 | GET | `/api/v1/file/:file_uuid/trace/:trace_id/video` | Trace clip (?padding=) |
|
| 32 | GET | `/api/v1/file/:file_uuid/trace/:trace_id/video` | Trace clip (?padding=) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -133,8 +133,8 @@ curl "$BASE/api/v1/file/$FILE/trace/2/faces?limit=2&interpolate=true" -H "$KEY"
|
|||||||
| # | Method | Path | Description |
|
| # | Method | Path | Description |
|
||||||
|---|--------|------|-------------|
|
|---|--------|------|-------------|
|
||||||
| 29 | GET | `/api/v1/file/:file_uuid/thumbnail` | Frame JPEG (?frame=&x=&y=&w=&h=) |
|
| 29 | GET | `/api/v1/file/:file_uuid/thumbnail` | Frame JPEG (?frame=&x=&y=&w=&h=) |
|
||||||
| 30 | GET | `/api/v1/file/:file_uuid/video` | Raw video (?start=&end=) |
|
| 30 | GET | `/api/v1/file/:file_uuid/video` | Raw video (start_frame=&end_frame=) |
|
||||||
| 31 | GET | `/api/v1/file/:file_uuid/video/bbox` | Bbox overlay (?start=&end=&duration=) |
|
| 31 | GET | `/api/v1/file/:file_uuid/video/bbox` | Bbox overlay (start_frame=&end_frame=&duration=) |
|
||||||
| 32 | GET | `/api/v1/file/:file_uuid/trace/:trace_id/video` | Trace clip (?padding=) |
|
| 32 | GET | `/api/v1/file/:file_uuid/trace/:trace_id/video` | Trace clip (?padding=) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
88
docs_v1.0/M4_workspace/2026-05-11_copy_import_issue.md
Normal file
88
docs_v1.0/M4_workspace/2026-05-11_copy_import_issue.md
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
# File Content Import Failure: COPY column mismatch
|
||||||
|
|
||||||
|
**Date**: 2026-05-11
|
||||||
|
**From**: M4
|
||||||
|
**To**: M5
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Failure
|
||||||
|
|
||||||
|
```
|
||||||
|
psql:/tmp/.../data.sql:22054: ERROR: syntax error at or near "id"
|
||||||
|
LINE 1: id,file_uuid,face_id,frame_number,x,y,width,height,confidenc...
|
||||||
|
^
|
||||||
|
```
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
|
||||||
|
### Step 1: First error — COPY dev.chunk_vectors fails silently
|
||||||
|
|
||||||
|
The real error occurs earlier in the file. psql suppresses it and continues, but then treats all subsequent lines as SQL commands. The face_detections CSV header `id,file_uuid,face_id,...` is the first line that triggers a visible syntax error.
|
||||||
|
|
||||||
|
### Step 2: Why COPY fails — extra column
|
||||||
|
|
||||||
|
M4 and M5 table schemas differ:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- M4 dev.face_detections (13 columns)
|
||||||
|
id, file_uuid, face_id, frame_number,
|
||||||
|
x, y, width, height,
|
||||||
|
confidence, embedding, identity_id,
|
||||||
|
created_at, trace_id
|
||||||
|
|
||||||
|
-- M5 exported CSV (14 columns)
|
||||||
|
id, file_uuid, face_id, frame_number,
|
||||||
|
x, y, width, height,
|
||||||
|
confidence, embedding, identity_id,
|
||||||
|
created_at, trace_id, timestamp_secs ← extra
|
||||||
|
```
|
||||||
|
|
||||||
|
The `COPY` command uses an empty column list:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
COPY dev.face_detections () FROM STDIN WITH CSV HEADER;
|
||||||
|
^^ empty = expects ALL columns
|
||||||
|
```
|
||||||
|
|
||||||
|
PostgreSQL requires the CSV column count to match the table column count. With 14 CSV columns vs 13 table columns, COPY rejects the entire data block.
|
||||||
|
|
||||||
|
### Step 3: Additionally, first chunk_vectors COPY may also fail
|
||||||
|
|
||||||
|
Same issue applies to `dev.chunk_vectors`:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
COPY dev.chunk_vectors () FROM STDIN WITH CSV HEADER;
|
||||||
|
```
|
||||||
|
|
||||||
|
If M5 added any columns to `chunk_vectors` that don't exist on M4, this COPY would also fail — which would explain why ALL three data tables (chunk, chunk_vectors, face_detections) show 0 rows after import.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
Check if M5's chunk_vectors has extra columns beyond:
|
||||||
|
```
|
||||||
|
id, chunk_id, uuid, chunk_type, embedding, created_at
|
||||||
|
```
|
||||||
|
|
||||||
|
## Recommended Fix
|
||||||
|
|
||||||
|
### Option A: Use column-explicit COPY (recommended)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
COPY dev.face_detections (id, file_uuid, face_id, frame_number, x, y, width, height, confidence, embedding, identity_id, created_at, trace_id)
|
||||||
|
FROM STDIN WITH CSV HEADER;
|
||||||
|
```
|
||||||
|
|
||||||
|
This explicitly names the columns, ignoring `timestamp_secs` if present in CSV but missing in M4 schema.
|
||||||
|
|
||||||
|
### Option B: Deliver INSERT-format dump
|
||||||
|
|
||||||
|
```sql
|
||||||
|
pg_dump --column-inserts --table=...
|
||||||
|
```
|
||||||
|
|
||||||
|
Same format as the working `dev_backup_post_correction.sql`. Larger file but portable across schema versions.
|
||||||
|
|
||||||
|
### Option C: Deliver schema migration
|
||||||
|
|
||||||
|
Provide `ALTER TABLE dev.face_detections ADD COLUMN timestamp_secs double precision;` as part of the dev upgrade package.
|
||||||
31
docs_v1.0/M4_workspace/2026-05-11_delivery_checklist.md
Normal file
31
docs_v1.0/M4_workspace/2026-05-11_delivery_checklist.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# 交付確認 — v1.0.3 打包
|
||||||
|
|
||||||
|
**Date**: 2026-05-11
|
||||||
|
**From**: M4
|
||||||
|
**To**: M5
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 已收到
|
||||||
|
|
||||||
|
| 文件 | 大小 | 狀態 |
|
||||||
|
|------|------|:--:|
|
||||||
|
| `momentry_core_v1.0.3_source.tar.gz` | 378MB | ✅ |
|
||||||
|
| `dev_backup_post_correction.sql` | 86MB | ✅ |
|
||||||
|
| `aeed71342a899fe4b4c57b7d41bcb692.asr-1.json` | 1.3MB | ✅ |
|
||||||
|
| `api_test.sh` | 9KB | ✅ |
|
||||||
|
| `RELEASE_PACKAGING_README.md` | 2.6KB | ✅ |
|
||||||
|
| `HANDOVER_v1.0.3.md` | 9.8KB | ✅ |
|
||||||
|
|
||||||
|
## 未收到
|
||||||
|
|
||||||
|
| 文件 | 預計大小 | 狀態 |
|
||||||
|
|------|---------|:--:|
|
||||||
|
| `release/dev_upgrade_v1.0.3.tar.gz` | ~400MB | ❌ |
|
||||||
|
| `release/file_aeed71342a899fe4b4c57b7d41bcb692_v1.0.tar.gz` | ? | ❌ |
|
||||||
|
|
||||||
|
## 問題
|
||||||
|
|
||||||
|
1. 兩個包未出現在 `/Volumes/docs_v1.0/M4_HANDOVER/`。是否還在打包?
|
||||||
|
2. `momentry_core_v1.0.3_source.tar.gz` 裡面的 `release/` 仍是 v1.0.0 的舊檔。v1.0.3 的 `release/system/dev/` 和 `release/files/` 目錄結構是否應該包在 source tarball 內?
|
||||||
|
3. 檔案內容包的命名格式是 `file_{uuid}_v{major}.{minor}.tar.gz` 嗎?
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# M5 回覆 — 交付確認
|
||||||
|
|
||||||
|
## Q1: 兩包已放共享目錄
|
||||||
|
|
||||||
|
`/Volumes/docs_v1.0/M4_HANDOVER/` 現在有:
|
||||||
|
|
||||||
|
| 檔案 | 大小 |
|
||||||
|
|------|:----:|
|
||||||
|
| `dev_upgrade_v1.0.3.tar.gz` | 400MB |
|
||||||
|
| `file_aeed71342a899fe4b4c57b7d41bcb692_v1.0.tar.gz` | 53MB |
|
||||||
|
| `momentry_core_v1.0.3_source.tar.gz` | 205MB(已清理 release/) |
|
||||||
|
|
||||||
|
## Q2: source tarball 已清理
|
||||||
|
|
||||||
|
重新打包,排除 `release/`、`storage/`、`model_checkpoints/`、`output/`。從 378MB → **205MB**。
|
||||||
|
|
||||||
|
`release/system/dev/` 和 `release/files/` 是**交付時產生的目錄**,不是 source code 的一部分,不會包進 source tarball。M4 收到後解壓 `dev_upgrade_*.tar.gz` 執行升級即可。
|
||||||
|
|
||||||
|
## Q3: 命名規範確認
|
||||||
|
|
||||||
|
| 包類型 | 格式 | 範例 |
|
||||||
|
|--------|------|------|
|
||||||
|
| 開發系統升級包 | `dev_upgrade_{version}.tar.gz` | `dev_upgrade_v1.0.3.tar.gz` |
|
||||||
|
| 生產系統升級包 | `prod_upgrade_{version}.tar.gz` | `prod_upgrade_v1.0.3.tar.gz` |
|
||||||
|
| 檔案內容包 | `file_{file_uuid}_{version}.tar.gz` | `file_aeed71342a...b692_v1.0.tar.gz` |
|
||||||
|
|
||||||
|
目前只有一個 Charade 檔案內容包。後續其他影片會用同一格式。
|
||||||
195
docs_v1.0/M4_workspace/2026-05-11_package_delivery_spec.md
Normal file
195
docs_v1.0/M4_workspace/2026-05-11_package_delivery_spec.md
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
# M5 Package Delivery Spec — Three-Tier Packaging System
|
||||||
|
|
||||||
|
**Date**: 2026-05-11
|
||||||
|
**From**: M4
|
||||||
|
**To**: M5
|
||||||
|
**Priority**: High
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 三層打包架構
|
||||||
|
|
||||||
|
```
|
||||||
|
├── 開發系統升級包 (Dev System Upgrade)
|
||||||
|
│ └── Schema migration + binary upgrade for dev (port 3003)
|
||||||
|
│
|
||||||
|
├── 生產系統升級包 (Production System Upgrade)
|
||||||
|
│ └── Schema migration + binary upgrade for prod (port 3002)
|
||||||
|
│
|
||||||
|
└── 檔案內容包 (File Content Package)
|
||||||
|
└── One file_uuid per package, complete post-correction data
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 開發系統升級包
|
||||||
|
|
||||||
|
**目的**:升級開發環境(schema=dev, port 3003)。
|
||||||
|
|
||||||
|
| 屬性 | 說明 |
|
||||||
|
|------|------|
|
||||||
|
| Schema | `dev` |
|
||||||
|
| 內容 | Schema migration SQL + 新版 binary + source code |
|
||||||
|
| 不含 | 任何檔案資料(無 file content data) |
|
||||||
|
| 頻率 | 每次系統升級 |
|
||||||
|
|
||||||
|
### 內容物
|
||||||
|
|
||||||
|
```
|
||||||
|
dev_upgrade_v1.0.2.tar.gz
|
||||||
|
├── schema/
|
||||||
|
│ └── migration_v1.0.2.sql # ALTER TABLE, CREATE INDEX, etc.
|
||||||
|
├── bin/
|
||||||
|
│ └── momentry_playground # Dev binary
|
||||||
|
├── src/
|
||||||
|
│ └── momentry_core_v1.0.2.tar.gz # Full source (optional)
|
||||||
|
└── UPGRADE.md # 升級步驟
|
||||||
|
```
|
||||||
|
|
||||||
|
### 升級步驟
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Apply schema migration
|
||||||
|
psql -U accusys -d momentry -c "SET search_path TO dev;"
|
||||||
|
psql -U accusys -d momentry < schema/migration_v1.0.2.sql
|
||||||
|
|
||||||
|
# 2. Replace binary
|
||||||
|
cp bin/momentry_playground /path/to/target/debug/
|
||||||
|
|
||||||
|
# 3. Restart server (port 3003)
|
||||||
|
DATABASE_SCHEMA=dev ./momentry_playground server --port 3003
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 生產系統升級包
|
||||||
|
|
||||||
|
**目的**:升級生產環境(schema=public, port 3002)。
|
||||||
|
|
||||||
|
| 屬性 | 說明 |
|
||||||
|
|------|------|
|
||||||
|
| Schema | `public` |
|
||||||
|
| 內容 | Schema migration SQL + release binary |
|
||||||
|
| 不含 | 任何檔案資料 |
|
||||||
|
| ⚠️ 注意 | 生產環境變更需要謹慎,必須先備份 |
|
||||||
|
|
||||||
|
### 內容物
|
||||||
|
|
||||||
|
```
|
||||||
|
prod_upgrade_v1.0.2.tar.gz
|
||||||
|
├── schema/
|
||||||
|
│ └── migration_v1.0.2.sql # ALTER TABLE, CREATE INDEX (public schema)
|
||||||
|
├── bin/
|
||||||
|
│ └── momentry # Release binary
|
||||||
|
└── UPGRADE.md # 升級步驟 + rollback 指令
|
||||||
|
```
|
||||||
|
|
||||||
|
### 升級步驟
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 0. Backup current DB (mandatory)
|
||||||
|
pg_dump -U accusys -d momentry --schema=public > backup_v1.0.1.sql
|
||||||
|
|
||||||
|
# 1. Apply schema migration
|
||||||
|
psql -U accusys -d momentry < schema/migration_v1.0.2.sql
|
||||||
|
|
||||||
|
# 2. Replace binary
|
||||||
|
cp bin/momentry /path/to/target/release/
|
||||||
|
|
||||||
|
# 3. Restart server (port 3002)
|
||||||
|
./momentry server --port 3002
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 檔案內容包
|
||||||
|
|
||||||
|
**目的**:交付單一影片的完整已處理資料。一包一檔。
|
||||||
|
|
||||||
|
| 屬性 | 說明 |
|
||||||
|
|------|------|
|
||||||
|
| 範圍 | **一個 file_uuid**(不跨檔案) |
|
||||||
|
| 內容 | 已註冊 + 已處理 + post-correction |
|
||||||
|
| Schema | `dev` 或 `public`(標註清楚) |
|
||||||
|
| 狀態 | 匯入後該檔案即為「已就緒(ready)」 |
|
||||||
|
|
||||||
|
### 內容物
|
||||||
|
|
||||||
|
```
|
||||||
|
charade_1963_{file_uuid}.tar.gz
|
||||||
|
├── data.sql # PG dump: videos + chunk + chunk_vectors + face_detections
|
||||||
|
├── file_info.json # { file_uuid, file_name, fps, duration, resolution, ... }
|
||||||
|
├── checksums.md5 # Integrity check
|
||||||
|
└── README.md # Import instructions
|
||||||
|
```
|
||||||
|
|
||||||
|
### Data Scope(data.sql 內容)
|
||||||
|
|
||||||
|
每個 table 只匯出該 file_uuid 的 rows:
|
||||||
|
|
||||||
|
| Table | WHERE Clause |
|
||||||
|
|-------|-------------|
|
||||||
|
| `videos` | `WHERE file_uuid = '{uuid}'` |
|
||||||
|
| `chunk` | `WHERE file_uuid = '{uuid}'` |
|
||||||
|
| `chunk_vectors` | `WHERE uuid = '{uuid}'` |
|
||||||
|
| `face_detections` | `WHERE file_uuid = '{uuid}'` |
|
||||||
|
| `identities` (if related) | 與此 file_uuid 關聯 |
|
||||||
|
|
||||||
|
### 匯出指令(M5 端)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
UUID="aeed71342a899fe4b4c57b7d41bcb692"
|
||||||
|
|
||||||
|
pg_dump -U accusys -d momentry --schema=dev \
|
||||||
|
--data-only --column-inserts \
|
||||||
|
--table=dev.videos \
|
||||||
|
--table=dev.chunk \
|
||||||
|
--table=dev.chunk_vectors \
|
||||||
|
--table=dev.face_detections \
|
||||||
|
--where="file_uuid='${UUID}'" \
|
||||||
|
> data.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
### 匯入指令(M4 端)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export UUID="aeed71342a899fe4b4c57b7d41bcb692"
|
||||||
|
|
||||||
|
# 1. Delete existing data for this file_uuid (if any)
|
||||||
|
psql -U accusys -d momentry <<SQL
|
||||||
|
DELETE FROM dev.chunk_vectors WHERE uuid = '${UUID}';
|
||||||
|
DELETE FROM dev.chunk WHERE file_uuid = '${UUID}';
|
||||||
|
DELETE FROM dev.face_detections WHERE file_uuid = '${UUID}';
|
||||||
|
DELETE FROM dev.videos WHERE file_uuid = '${UUID}';
|
||||||
|
SQL
|
||||||
|
|
||||||
|
# 2. Restore package
|
||||||
|
psql -U accusys -d momentry < data.sql
|
||||||
|
|
||||||
|
# 3. Done — file is ready, no migration needed
|
||||||
|
```
|
||||||
|
|
||||||
|
### 驗證標準
|
||||||
|
|
||||||
|
匯入後所有 API 正常:
|
||||||
|
|
||||||
|
- [ ] `GET /api/v1/file/{uuid}` → 200,status 為 `ready`
|
||||||
|
- [ ] `GET /api/v1/file/{uuid}/chunk/0-01` → 200(corrected chunk_id)
|
||||||
|
- [ ] `POST /api/v1/search/universal` → 有結果
|
||||||
|
- [ ] `POST /api/v1/file/{uuid}/face_trace/sortby` → 有 traces
|
||||||
|
- [ ] `GET /api/v1/file/{uuid}/trace/{id}/faces` → 有 faces
|
||||||
|
- [ ] Portal FilesView 顯示 `✅ 已就绪`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 三者對照
|
||||||
|
|
||||||
|
| | 開發升級包 | 生產升級包 | 檔案內容包 |
|
||||||
|
|------|:--:|:--:|:--:|
|
||||||
|
| Schema | dev | public | dev 或 public |
|
||||||
|
| Data | ❌ | ❌ | ✅ 一個 file_uuid |
|
||||||
|
| Binary | dev compile | release build | ❌ |
|
||||||
|
| Migration SQL | ✅ | ✅ | ❌ |
|
||||||
|
| Rollback SQL | 可選 | **必須** | ❌ |
|
||||||
|
| 頻率 | 每次版本 | 每次 release | 每個新影片 |
|
||||||
|
| Port | 3003 | 3002 | — |
|
||||||
52
docs_v1.0/M4_workspace/2026-05-11_portal_setup_fix.md
Normal file
52
docs_v1.0/M4_workspace/2026-05-11_portal_setup_fix.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Portal Setup Fix — Tauri CLI Missing
|
||||||
|
|
||||||
|
**Date**: 2026-05-11
|
||||||
|
**From**: M4
|
||||||
|
**To**: M5
|
||||||
|
**Priority**: Medium
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Issue
|
||||||
|
|
||||||
|
Running `npm run tauri dev` produces:
|
||||||
|
```
|
||||||
|
sh: tauri: command not found
|
||||||
|
```
|
||||||
|
|
||||||
|
## Root Cause
|
||||||
|
|
||||||
|
`@tauri-apps/cli` was missing from `package.json` devDependencies. The `npm run tauri` script relies on the `tauri` binary installed by this package.
|
||||||
|
|
||||||
|
## Fix
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd portal
|
||||||
|
npm install --save-dev @tauri-apps/cli
|
||||||
|
npm install
|
||||||
|
npm run tauri dev
|
||||||
|
```
|
||||||
|
|
||||||
|
This has already been applied to the updated source tarball at `/Volumes/docs_v1.0/M4_HANDOVER/momentry_portal_v0.1_source.tar.gz`.
|
||||||
|
|
||||||
|
## Additional Fixes in Updated Tarball
|
||||||
|
|
||||||
|
| Fix | File | Description |
|
||||||
|
|-----|------|-------------|
|
||||||
|
| Tauri CLI dep | `package.json` | Added `@tauri-apps/cli` to devDependencies |
|
||||||
|
| API key | `.env.development` | Corrected `VITE_API_KEY` (was `muser_test_001`) |
|
||||||
|
| Search play | `SearchView.vue` | Don't seek segment that was already extracted |
|
||||||
|
|
||||||
|
## After Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# API server must be running on port 3003
|
||||||
|
cd momentry_core_0.1
|
||||||
|
DATABASE_SCHEMA=dev cargo run --bin momentry_playground -- server --port 3003
|
||||||
|
|
||||||
|
# Then in another terminal
|
||||||
|
cd portal
|
||||||
|
npm run tauri dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Portal opens desktop app window connecting to `http://127.0.0.1:3003` with API key `muser_68600856036340bcafc01930eb4bd839_1774418104_97221b69`.
|
||||||
32
docs_v1.0/M4_workspace/2026-05-11_release_packaging_v2.md
Normal file
32
docs_v1.0/M4_workspace/2026-05-11_release_packaging_v2.md
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Release Packaging V2 — 三包制
|
||||||
|
|
||||||
|
**Date**: 2026-05-11
|
||||||
|
**From**: M5
|
||||||
|
**To**: M4
|
||||||
|
|
||||||
|
## 變更
|
||||||
|
|
||||||
|
統一改為三包制:
|
||||||
|
|
||||||
|
| 包類型 | 路徑 | 說明 |
|
||||||
|
|--------|------|------|
|
||||||
|
| 開發系統升級包 | `release/system/dev/latest/` | source + dev schema + scripts + portal |
|
||||||
|
| 生產系統升級包 | `release/system/prod/` | (待建立) |
|
||||||
|
| 檔案內容包 | `release/files/{uuid}/latest/` | 單一影片完整資料 |
|
||||||
|
|
||||||
|
## 開發系統升級包 v1.0.3 已就位
|
||||||
|
|
||||||
|
位置: `release/system/dev/v1.0.3/` (385MB)
|
||||||
|
|
||||||
|
| 內容 | 說明 |
|
||||||
|
|------|------|
|
||||||
|
| `source.tar.gz` | Rust + scripts + configs |
|
||||||
|
| `schema.sql` | dev schema DDL |
|
||||||
|
| `.env.development` | 開發環境設定 |
|
||||||
|
| `scripts/` | pipeline, correction, import tools |
|
||||||
|
| `test/api_test.sh` | 39 endpoint 測試 |
|
||||||
|
| `portal/dist/` | 前端 build |
|
||||||
|
|
||||||
|
## 轉移流程
|
||||||
|
|
||||||
|
見 `docs_v1.0/M4_HANDOVER/RELEASE_PACKAGING_README.md`
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# Request: Complete Post-Correction DB Backup
|
||||||
|
|
||||||
|
**Date**: 2026-05-11
|
||||||
|
**From**: M4
|
||||||
|
**To**: M5
|
||||||
|
**Priority**: High
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Issue
|
||||||
|
|
||||||
|
Current backup (`dev.chunks.sql` + `dev.chunk_vectors.sql`) contains **pre-correction** data. The correction scripts (`generate_asr1.py` → `apply_asr_corrections.py`) were never applied in the backup.
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- Chunk count differs: backup has 6,021 chunks for Charade `aeed71342a899fe4b4c57b7d41bcb692`, but post-correction should be **4,188** sentence chunks
|
||||||
|
- Corrected chunk_ids (`0-01`, `1446-01`, etc.) don't exist → chunk detail tests fail (404 instead of 200)
|
||||||
|
- `api_test.sh` "Chunk detail" section: `chunk/0-01` and `chunk/1446-01` fail on M4
|
||||||
|
|
||||||
|
## Request
|
||||||
|
|
||||||
|
Provide a **complete post-correction** DB dump of `dev.chunk` + `dev.chunk_vectors` for `aeed71342a899fe4b4c57b7d41bcb692`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pg_dump -U accusys -d momentry --schema=dev \
|
||||||
|
--table=dev.chunk \
|
||||||
|
--table=dev.chunk_vectors \
|
||||||
|
--data-only --column-inserts \
|
||||||
|
> release/phase1/backup_post_correction.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
Both tables should reflect the state AFTER running `apply_asr_corrections.py`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## M5 Response
|
||||||
|
|
||||||
|
**Status**: ✅ Backup ready
|
||||||
|
|
||||||
|
| Check | Value |
|
||||||
|
|------|-------|
|
||||||
|
| dev.chunk rows (Charade) | 6,021 total (4,188 sentence + 1,130 cut + 423 trace + 280 story) |
|
||||||
|
| dev.chunk_vectors | 4,188 |
|
||||||
|
| Matched (chunk ↔ vectors) | 4,188 ✅ |
|
||||||
|
| chunk_id format | Short (`0-01`, `1446-01`, `story_240`, etc.) |
|
||||||
|
|
||||||
|
**File**: `docs_v1.0/M4_HANDOVER/dev_backup_post_correction.sql` (86 MB)
|
||||||
|
|
||||||
|
This backup reflects the state AFTER applying corrections, with all schema changes (RENAME, column drops, short chunk_ids) already applied. No further migration steps needed — just `psql -U accusys -d momentry < dev_backup_post_correction.sql`.
|
||||||
45
docs_v1.0/M4_workspace/2026-05-11_v1.0.3_issues.md
Normal file
45
docs_v1.0/M4_workspace/2026-05-11_v1.0.3_issues.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# v1.0.3 文件內容包 — COPY 格式確認
|
||||||
|
|
||||||
|
**Date**: 2026-05-11
|
||||||
|
**From**: M4
|
||||||
|
**To**: M5
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 結論:COPY 格式無問題
|
||||||
|
|
||||||
|
M4 再次以 clean state 測試後,`data.sql` 匯入成功:
|
||||||
|
|
||||||
|
```
|
||||||
|
COPY 1 (videos)
|
||||||
|
COPY 6021 (chunk)
|
||||||
|
COPY 4188 (chunk_vectors)
|
||||||
|
COPY 11820 (face_detections)
|
||||||
|
COMMIT
|
||||||
|
```
|
||||||
|
|
||||||
|
39/39 API tests pass.
|
||||||
|
|
||||||
|
## 之前失敗的原因
|
||||||
|
|
||||||
|
M4 測試時先以 INSERT 格式的 `dev_backup_post_correction.sql` 匯入,再用 COPY 格式的 `data.sql` 匯入 → 兩套相同資料的 ID 衝突(duplicate key)。
|
||||||
|
|
||||||
|
## 正確的檔案內容包匯入流程
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Apply schema migration first (dev upgrade)
|
||||||
|
psql -U accusys -d momentry < schema/migration_v1.0.3.sql
|
||||||
|
|
||||||
|
# 2. Delete existing data for this UUID (if any)
|
||||||
|
psql -U accusys -d momentry <<SQL
|
||||||
|
DELETE FROM dev.chunk_vectors WHERE uuid = '{UUID}';
|
||||||
|
DELETE FROM dev.chunk WHERE file_uuid = '{UUID}';
|
||||||
|
DELETE FROM dev.face_detections WHERE file_uuid = '{UUID}';
|
||||||
|
DELETE FROM dev.videos WHERE file_uuid = '{UUID}';
|
||||||
|
SQL
|
||||||
|
|
||||||
|
# 3. Import file content
|
||||||
|
psql -U accusys -d momentry < data.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
Issue 1 (migration SQL empty) 和 Issue 2 (COPY format) 均確認無誤。
|
||||||
50
docs_v1.0/M4_workspace/2026-05-12_api_doc_fixes.md
Normal file
50
docs_v1.0/M4_workspace/2026-05-12_api_doc_fixes.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# API 文件修正請求 — v1.0.0 目錄
|
||||||
|
|
||||||
|
**Date**: 2026-05-12
|
||||||
|
**From**: M4
|
||||||
|
**To**: M5
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
與實際系統(3003 port)交叉驗證後,發現以下問題:
|
||||||
|
|
||||||
|
## 1. 路徑錯誤
|
||||||
|
|
||||||
|
| 檔案 | 行號 | 錯誤 | 正確 |
|
||||||
|
|------|:---:|------|------|
|
||||||
|
| `INTERNAL/API_DICTIONARY_V1.0.0.md` | 87 | `POST /api/v1/files/unregister` | `POST /api/v1/unregister` |
|
||||||
|
|
||||||
|
## 2. 回應格式過時
|
||||||
|
|
||||||
|
| 檔案 | 欄位 | DOC 說 | ACTUAL 是 |
|
||||||
|
|------|------|--------|-----------|
|
||||||
|
| `API_REFERENCE_V1.0.0.md:73` | `/files` 結果 key | `"files"` | `"data"` |
|
||||||
|
| `API_REFERENCE_V1.0.0.md:73` | `/files` 結果 | 缺 `success` | 有 `success:true` |
|
||||||
|
| `API_REFERENCE_V1.0.0.md` | `/search/universal` 結果數 key | `"count"` | `"total"` |
|
||||||
|
| `API_REFERENCE_V1.0.0.md` | `/search/universal` 回應 | 缺分頁欄位 | 有 `page`, `page_size`, `took_ms` |
|
||||||
|
| `API_REFERENCE_V1.0.0.md` | `/identities` 回應 | 缺分頁欄位 | 有 `page`, `page_size`, `count` |
|
||||||
|
| `API_REFERENCE_V1.0.0.md` | `/resources` 結果 key | `"resources"` | `"data"` |
|
||||||
|
| `API_REFERENCE_V1.0.0.md:63` | register body | `file_path` 為相對路徑 | 需絕對路徑 |
|
||||||
|
|
||||||
|
## 3. 已移除但文件仍列出的 endpoint
|
||||||
|
|
||||||
|
| 檔案 | 行號 | Endpoint | 現狀 |
|
||||||
|
|------|:---:|------|:--:|
|
||||||
|
| `API_REFERENCE_V1.0.0.md` | 58 | `GET /api/v1/file/:uuid/chunks` | 404(M5 Phase 1 已移除) |
|
||||||
|
| `RELEASE/RELEASE_API_REFERENCE_V1.0.0.md` | 66 | 同上 | 同上 |
|
||||||
|
|
||||||
|
應改為:`GET /api/v1/file/:uuid/chunk/:chunk_id` (V1.0.2+)
|
||||||
|
|
||||||
|
## 4. 缺少範例
|
||||||
|
|
||||||
|
| 檔案 | Endpoint | 缺少 |
|
||||||
|
|------|------|------|
|
||||||
|
| `API_REFERENCE_V1.0.0.md:52` | `POST /api/v1/unregister` | 無 curl 範例、無回應範例 |
|
||||||
|
| `API_DICTIONARY_V1.0.0.md:64` | 同上 | 同上 |
|
||||||
|
| `RELEASE/RELEASE_API_REFERENCE_V1.0.0.md:60` | 同上 | 同上 |
|
||||||
|
|
||||||
|
參考已有較完整說明的檔案:`API_DOCUMENTATION.md:80` 和 `INTERNAL/API_REFERENCE_V1.0.0_20260501.md:113`。
|
||||||
|
|
||||||
|
## 5. Port 不一致
|
||||||
|
|
||||||
|
所有 curl 範例都用 `localhost:3002`(production),建議加註 `# dev: localhost:3003`。
|
||||||
112
docs_v1.0/M4_workspace/2026-05-12_api_doc_verification.md
Normal file
112
docs_v1.0/M4_workspace/2026-05-12_api_doc_verification.md
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
# API 文件驗證報告 — API_REFERENCE_V1.0.0.md vs 實際系統 (3003)
|
||||||
|
|
||||||
|
**Date**: 2026-05-12
|
||||||
|
**From**: M4
|
||||||
|
**驗證對象**: `docs_v1.0/API_V1.0.0/API_REFERENCE_V1.0.0.md` (14 curl examples)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 摘要
|
||||||
|
|
||||||
|
| 狀態 | 數量 |
|
||||||
|
|:--:|:--:|
|
||||||
|
| ✅ 一致 | 3 |
|
||||||
|
| ⚠️ 欄位差異 | 6 |
|
||||||
|
| ❌ 錯誤/過時 | 2 |
|
||||||
|
| 🔴 功能未啟動 | 1 |
|
||||||
|
| — | — |
|
||||||
|
| **兩項以上問題** | **9/14 (64%)** |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 詳細差異
|
||||||
|
|
||||||
|
### 1. `/files` 回應格式 ⚠️
|
||||||
|
|
||||||
|
| | DOC | ACTUAL |
|
||||||
|
|------|------|--------|
|
||||||
|
| Key | `"files"` | `"data"` |
|
||||||
|
| `success` | 無 | `true` |
|
||||||
|
| `total` | `37` | `0` |
|
||||||
|
|
||||||
|
**建議**: doc 改為 `"data"`,`total` 修正(目前 hardcoded 0)。
|
||||||
|
|
||||||
|
### 2. `/search/universal` 回應格式 ⚠️
|
||||||
|
|
||||||
|
| | DOC | ACTUAL |
|
||||||
|
|------|------|--------|
|
||||||
|
| 結果數 key | `"count"` | `"total"` |
|
||||||
|
| 分頁 | 無 | `page`, `page_size` |
|
||||||
|
| 執行時間 | 無 | `took_ms` |
|
||||||
|
|
||||||
|
**建議**: doc 補上分頁欄位,`count` → `total`。
|
||||||
|
|
||||||
|
### 3. `/identities` 回應格式 ⚠️
|
||||||
|
|
||||||
|
| | DOC | ACTUAL |
|
||||||
|
|------|------|--------|
|
||||||
|
| 分頁 | 無 | `page`, `page_size` |
|
||||||
|
| 總數 key | 無 | `count` |
|
||||||
|
|
||||||
|
**建議**: doc 補上分頁欄位。
|
||||||
|
|
||||||
|
### 4. `/resources` 回應格式 ⚠️
|
||||||
|
|
||||||
|
| | DOC | ACTUAL |
|
||||||
|
|------|------|--------|
|
||||||
|
| 資料 key | `"resources"` | `"data"` |
|
||||||
|
| 額外欄位 | 無 | `success`, `message` |
|
||||||
|
|
||||||
|
**建議**: doc 改為 `"data"`,補 `success` 欄位。
|
||||||
|
|
||||||
|
### 5. `/faces/candidates` ✅
|
||||||
|
|
||||||
|
DOC 與 ACTUAL 一致。
|
||||||
|
|
||||||
|
### 6. `/health` ✅
|
||||||
|
|
||||||
|
DOC 與 ACTUAL 一致。
|
||||||
|
|
||||||
|
### 7. `/thumbnail` ✅
|
||||||
|
|
||||||
|
DOC: JPEG 82KB / ACTUAL: 200, image/jpeg, 82KB。
|
||||||
|
|
||||||
|
### 8. `/file/:uuid/chunks` ❌ 已移除
|
||||||
|
|
||||||
|
DOC 列出此 endpoint,ACTUAL 回 404(M5 Phase 1 移除)。
|
||||||
|
|
||||||
|
**建議**: 從 doc 移除,改為 `/file/:uuid/chunk/:chunk_id`。
|
||||||
|
|
||||||
|
### 9. 檔案註冊路徑 ❌ 格式不同
|
||||||
|
|
||||||
|
DOC: `/sftpgo/data/demo/video.mp4`(相對路徑)
|
||||||
|
ACTUAL: 需絕對路徑 `/Users/accusys/momentry/var/sftpgo/data/demo/...`
|
||||||
|
|
||||||
|
### 10. `/agents/translate` 🔴 未啟動
|
||||||
|
|
||||||
|
DOC: `{"success":true,"translated_text":"你好世界"}`
|
||||||
|
ACTUAL: 500(LLM 服務未啟動或無法連線)
|
||||||
|
|
||||||
|
### 11. Face trace 數字 ⚠️
|
||||||
|
|
||||||
|
DOC: `total_traces=6892, total_faces=108204`
|
||||||
|
這些數字來自 M4 的舊資料(`3abeee81`)。不同 file UUID 會有不同數字。
|
||||||
|
|
||||||
|
**建議**: doc 標註「數字隨檔案而異」。
|
||||||
|
|
||||||
|
### 12. Identity binding ⚠️
|
||||||
|
|
||||||
|
DOC: `POST /api/v1/identity/:uuid/bind`
|
||||||
|
ACTUAL: route 存在但未測試(需特定 identity UUID)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 修正建議
|
||||||
|
|
||||||
|
1. **Port**: doc 全用 3002,建議加註解 `# dev: port 3003`
|
||||||
|
2. **Response format**: `"files"` → `"data"`、`"count"` → `"total"`、補 `success`/`page`/`page_size`
|
||||||
|
3. **移除**: `/file/:uuid/chunks` 已過時
|
||||||
|
4. **新增**: `/file/:uuid/chunk/:chunk_id`(V1.0.2+)
|
||||||
|
5. **範例路徑**: 改為絕對路徑
|
||||||
|
6. **數值**: 標註「實際數字隨 UUID 而異」
|
||||||
|
7. **Translate**: 確認 LLM 服務狀態後更新 doc
|
||||||
47
docs_v1.0/M4_workspace/2026-05-12_docs_structure_proposal.md
Normal file
47
docs_v1.0/M4_workspace/2026-05-12_docs_structure_proposal.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# M5 vs M4 文件比對與整理建議
|
||||||
|
|
||||||
|
**Date**: 2026-05-12
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 比對結果
|
||||||
|
|
||||||
|
4 個檔案有差異(M5 正確,M4 過時):
|
||||||
|
|
||||||
|
| 檔案 | M5 (shared) | M4 (local) |
|
||||||
|
|------|:--:|:--:|
|
||||||
|
| `API_REFERENCE_V1.0.0.md` | ✅ 10 項修正已套用 | ❌ 舊版(無 unregister 範例、欄位名錯誤等) |
|
||||||
|
| `INTERNAL/API_DICTIONARY_V1.0.0.md` | ✅ `/api/v1/unregister` | ❌ `/api/v1/files/unregister` |
|
||||||
|
| `RELEASE/RELEASE_API_REFERENCE_V1.0.0.md` | ✅ `/chunk/:chunk_id` | ❌ `/chunks`(已移除) |
|
||||||
|
| `API_DOCUMENTATION.md` | ✅ `muser_xxx_xxx` | ❌ `muser_test_001`(舊 key) |
|
||||||
|
|
||||||
|
1 個檔案只在 M4:
|
||||||
|
|
||||||
|
| 檔案 | 說明 |
|
||||||
|
|------|------|
|
||||||
|
| `DEMO_GUIDE.md` | Demo 操作指南,M5 端沒有 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 建議:三層文件架構
|
||||||
|
|
||||||
|
```
|
||||||
|
docs_v1.0/
|
||||||
|
├── API_V1.0.0/ ← M5 維護(canonical),M4 唯讀
|
||||||
|
├── M4_workspace/ ← M4 維護(回報、測試報告)
|
||||||
|
├── M5_workspace/ ← M5 維護(handover、release notes)
|
||||||
|
└── REFERENCE/ ← 共用參考文件(硬體圖等)
|
||||||
|
```
|
||||||
|
|
||||||
|
### M4 端處理
|
||||||
|
|
||||||
|
1. **刪除 M4 的 `API_V1.0.0/` 目錄** — 改為直接參照 shared volume
|
||||||
|
2. **M4 只維護 `M4_workspace/`** — 放回報、測試記錄
|
||||||
|
3. **`DEMO_GUIDE.md`** 搬到 `M4_workspace/` 或通知 M5 同步
|
||||||
|
4. **M4 機器的 `docs_v1.0/`** 簡化為 `M4_workspace/` + `REFERENCE/`
|
||||||
|
|
||||||
|
### 好處
|
||||||
|
|
||||||
|
- 不再有 M4 自行修改 API 文件導致不一致的問題
|
||||||
|
- M5 維護 canonical 版本,M4 直接讀取
|
||||||
|
- M4 回報問題給 M5,由 M5 統一修正
|
||||||
313
docs_v1.0/M4_workspace/2026-05-12_package_receive_checklist.md
Normal file
313
docs_v1.0/M4_workspace/2026-05-12_package_receive_checklist.md
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
# 檔案內容包接收檢查清單
|
||||||
|
|
||||||
|
**Date**: 2026-05-12 (updated for v2 with identity support)
|
||||||
|
**From**: M4
|
||||||
|
**Usage**: M4 receives file package from M5 → run these checks before accepting
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 結構檢查
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 檢查 tar.gz 包含必要檔案
|
||||||
|
tar tzf file_{uuid}_{ver}.tar.gz
|
||||||
|
```
|
||||||
|
|
||||||
|
| 必要 | 檢查項 |
|
||||||
|
|:--:|------|
|
||||||
|
| ✅ | `data.sql` 存在且非空(videos + chunk + vectors + faces) |
|
||||||
|
| ✅ | `identities.sql` 存在且非空(**v2 新增**) |
|
||||||
|
| ✅ | `identity_bindings.sql` 存在且非空(**v2 新增**) |
|
||||||
|
| ✅ | `file_info.json` 存在 |
|
||||||
|
| ✅ | `checksums.md5` 存在 |
|
||||||
|
| ⬜ | `tkg.sqlite` (未來要求) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 完整性檢查
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 校驗 hash
|
||||||
|
md5 -c checksums.md5
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 檢查 data.sql + identities.sql + identity_bindings.sql 包含哪些 table
|
||||||
|
tar xzf file_{uuid}_{ver}.tar.gz -O data.sql | grep -E "^COPY|^INSERT INTO" | sort -u
|
||||||
|
tar xzf file_{uuid}_{ver}.tar.gz -O identities.sql | grep -E "^COPY|^INSERT INTO" | sort -u
|
||||||
|
tar xzf file_{uuid}_{ver}.tar.gz -O identity_bindings.sql | grep -E "^COPY|^INSERT INTO" | sort -u
|
||||||
|
```
|
||||||
|
|
||||||
|
| 必要 | Table | 來源檔案 |
|
||||||
|
|:--:|-------|---------|
|
||||||
|
| ✅ | `dev.videos` (1 row) | data.sql |
|
||||||
|
| ✅ | `dev.chunk` | data.sql |
|
||||||
|
| ✅ | `dev.chunk_vectors` | data.sql |
|
||||||
|
| ✅ | `dev.face_detections` | data.sql |
|
||||||
|
| ✅ | `dev.identities`(**v2 新增**)| identities.sql |
|
||||||
|
| ✅ | `dev.identity_bindings`(**v2 新增**)| identity_bindings.sql |
|
||||||
|
|
||||||
|
- `dev.videos.status` 應為 `completed`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 數量合理性
|
||||||
|
|
||||||
|
```bash
|
||||||
|
UUID="..."
|
||||||
|
# 計算 data.sql 各 table 行數
|
||||||
|
tar xzf file_{uuid}_{ver}.tar.gz -O data.sql | grep -c ",${UUID},"
|
||||||
|
|
||||||
|
# v2: 計算 identity 數量
|
||||||
|
tar xzf file_{uuid}_{ver}.tar.gz -O identities.sql | grep -c "INSERT INTO"
|
||||||
|
tar xzf file_{uuid}_{ver}.tar.gz -O identity_bindings.sql | grep -c "INSERT INTO"
|
||||||
|
```
|
||||||
|
|
||||||
|
| 檢查項 | 合理範圍 |
|
||||||
|
|--------|---------|
|
||||||
|
| videos rows | = 1 |
|
||||||
|
| chunk rows | > 1000 |
|
||||||
|
| chunk_vectors rows | > 1000 |
|
||||||
|
| face_detections rows | > 1000 |
|
||||||
|
| identities rows (**v2**) | > 0 |
|
||||||
|
| identity_bindings rows (**v2**) | > 0 |
|
||||||
|
| chunk vs vectors 數量 | 二者不應差太多 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Identity 完整性檢查(**v2 新版**)
|
||||||
|
|
||||||
|
### 匯入後檢查
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 1. 檢查 face_detections → identity_id 覆蓋率
|
||||||
|
SELECT
|
||||||
|
count(*) as total_faces,
|
||||||
|
count(identity_id) as faces_with_id,
|
||||||
|
round(100.0 * count(identity_id) / count(*), 1) as pct
|
||||||
|
FROM dev.face_detections WHERE file_uuid = '{UUID}';
|
||||||
|
```
|
||||||
|
|
||||||
|
| 覆蓋率 | 判定 |
|
||||||
|
|:---:|------|
|
||||||
|
| > 80% | ✅ 正常 |
|
||||||
|
| 20-80% | ⚠️ 偏低,確認 M5 pipeline |
|
||||||
|
| < 20% | ❌ 異常 |
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 2. 檢查 identity_id 是否指向存在的 identity
|
||||||
|
SELECT count(*) as orphans
|
||||||
|
FROM dev.face_detections f
|
||||||
|
LEFT JOIN dev.identities i ON f.identity_id = i.id
|
||||||
|
WHERE f.file_uuid = '{UUID}'
|
||||||
|
AND f.identity_id IS NOT NULL
|
||||||
|
AND i.id IS NULL;
|
||||||
|
```
|
||||||
|
|
||||||
|
> 0 → ❌ orphan identity reference,需回報 M5
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 3. 檢查 identity_bindings 綁定數量
|
||||||
|
SELECT count(*) as bindings
|
||||||
|
FROM dev.identity_bindings
|
||||||
|
WHERE identity_id IN (
|
||||||
|
SELECT DISTINCT identity_id FROM dev.face_detections
|
||||||
|
WHERE file_uuid = '{UUID}' AND identity_id IS NOT NULL
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**binding 數量應與 face_detections 中有 identity_id 的行數相近**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 4. 列出此檔案綁定的人物 Top 10
|
||||||
|
SELECT i.name, count(DISTINCT f.id) as unique_faces
|
||||||
|
FROM dev.face_detections f
|
||||||
|
JOIN dev.identities i ON f.identity_id = i.id
|
||||||
|
WHERE f.file_uuid = '{UUID}'
|
||||||
|
GROUP BY i.name ORDER BY unique_faces DESC LIMIT 10;
|
||||||
|
```
|
||||||
|
|
||||||
|
**應看到已知角色(如 Cary Grant, Audrey Hepburn)**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 5. 檢查 trace-level identity binding
|
||||||
|
SELECT
|
||||||
|
COUNT(DISTINCT trace_id) FILTER (WHERE identity_id IS NOT NULL) as bound_traces,
|
||||||
|
COUNT(DISTINCT trace_id) FILTER (WHERE identity_id IS NULL) as unbound_traces
|
||||||
|
FROM dev.face_detections WHERE file_uuid = '{UUID}';
|
||||||
|
```
|
||||||
|
|
||||||
|
| 判定 | bound / unbound |
|
||||||
|
|:--:|------|
|
||||||
|
| ✅ | bound traces 佔多數 |
|
||||||
|
| ⚠️ | unbound traces > 30% |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 匯入測試
|
||||||
|
|
||||||
|
```bash
|
||||||
|
UUID="..."
|
||||||
|
|
||||||
|
# 1. 備份現有資料
|
||||||
|
pg_dump -U accusys -d momentry --schema=dev \
|
||||||
|
--table=dev.chunk --table=dev.chunk_vectors \
|
||||||
|
--table=dev.face_detections --table=dev.videos \
|
||||||
|
--table=dev.identities --table=dev.identity_bindings \
|
||||||
|
--where="file_uuid='${UUID}'" > backup_before_import.sql
|
||||||
|
|
||||||
|
# 2. 刪除舊資料(含 identity tables)
|
||||||
|
psql -U accusys -d momentry <<SQL
|
||||||
|
DELETE FROM dev.chunk_vectors WHERE uuid = '${UUID}';
|
||||||
|
DELETE FROM dev.chunk WHERE file_uuid = '${UUID}';
|
||||||
|
DELETE FROM dev.face_detections WHERE file_uuid = '${UUID}';
|
||||||
|
DELETE FROM dev.identity_bindings WHERE identity_id IN (
|
||||||
|
SELECT id FROM dev.identities WHERE source = 'tmdb');
|
||||||
|
DELETE FROM dev.identities WHERE source = 'tmdb';
|
||||||
|
DELETE FROM dev.videos WHERE file_uuid = '${UUID}';
|
||||||
|
SQL
|
||||||
|
|
||||||
|
# 3. 匯入三個 SQL 檔案
|
||||||
|
psql -U accusys -d momentry < identities.sql
|
||||||
|
psql -U accusys -d momentry < identity_bindings.sql
|
||||||
|
psql -U accusys -d momentry < data.sql
|
||||||
|
|
||||||
|
# 4. 確認數量
|
||||||
|
psql -U accusys -d momentry -c "
|
||||||
|
SELECT 'chunk' as tbl, count(*) FROM dev.chunk WHERE file_uuid = '${UUID}'
|
||||||
|
UNION ALL SELECT 'vectors', count(*) FROM dev.chunk_vectors WHERE uuid = '${UUID}'
|
||||||
|
UNION ALL SELECT 'faces', count(*) FROM dev.face_detections WHERE file_uuid = '${UUID}'
|
||||||
|
UNION ALL SELECT 'videos', count(*) FROM dev.videos WHERE file_uuid = '${UUID}'
|
||||||
|
UNION ALL SELECT 'identities', count(*) FROM dev.identities WHERE source = 'tmdb'
|
||||||
|
UNION ALL SELECT 'bindings', count(*) FROM dev.identity_bindings
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. API 功能驗證
|
||||||
|
|
||||||
|
```bash
|
||||||
|
UUID="..." API_KEY="..."
|
||||||
|
|
||||||
|
for ep in \
|
||||||
|
"/api/v1/file/$UUID" \
|
||||||
|
"/api/v1/file/$UUID/video" \
|
||||||
|
"/api/v1/file/$UUID/thumbnail?frame=1000" \
|
||||||
|
"/api/v1/search/universal" \
|
||||||
|
"/api/v1/file/$UUID/face_trace/sortby" \
|
||||||
|
"/api/v1/file/$UUID/trace/1/faces?limit=3" \
|
||||||
|
"/api/v1/identities" \
|
||||||
|
"/api/v1/file/$UUID/identities"
|
||||||
|
do
|
||||||
|
code=$(curl -s -o /dev/null -w "%{http_code}" -H "X-API-Key: $API_KEY" "http://localhost:3003$ep")
|
||||||
|
printf "%-50s %s\n" "$ep" "$code"
|
||||||
|
done
|
||||||
|
|
||||||
|
# chunk detail
|
||||||
|
curl -s -o /dev/null -w "%{http_code}" -H "X-API-Key: $API_KEY" \
|
||||||
|
"http://localhost:3003/api/v1/file/$UUID/chunk/0"
|
||||||
|
echo " (expect 200)"
|
||||||
|
curl -s -o /dev/null -w "%{http_code}" -H "X-API-Key: $API_KEY" \
|
||||||
|
"http://localhost:3003/api/v1/file/$UUID/chunk/nonexist"
|
||||||
|
echo " (expect 404)"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 影片檔案檢查
|
||||||
|
|
||||||
|
```bash
|
||||||
|
FILE_PATH=$(psql -U accusys -d momentry -t -c \
|
||||||
|
"SELECT file_path FROM dev.videos WHERE file_uuid = '${UUID}'" | tr -d ' ')
|
||||||
|
ls -lh "$FILE_PATH" || echo "❌ 影片檔案不存在!"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 預期問題記錄
|
||||||
|
|
||||||
|
| 已知問題 | 處理方式 |
|
||||||
|
|---------|---------|
|
||||||
|
| COPY 格式 column mismatch | 先跑 migration SQL |
|
||||||
|
| 影片檔案路徑不存在 | 跟 M5 要影片檔 |
|
||||||
|
| duplicate key (version conflict) | DELETE 後再匯入 |
|
||||||
|
| status 不是 completed | 檢查 `file_info.json` 或手動 UPDATE |
|
||||||
|
| orphan identity_id | 確認 identities.sql 已匯入 |
|
||||||
|
| identity_bindings 數量異常 | 確認 identity_bindings.sql 已匯入 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 上架驗證(Go-Live Verification)
|
||||||
|
|
||||||
|
匯入成功後的完整上架確認。
|
||||||
|
|
||||||
|
### 9.1 API 層
|
||||||
|
|
||||||
|
```bash
|
||||||
|
UUID="..." API_KEY="..."
|
||||||
|
HOST="http://localhost:3003"
|
||||||
|
|
||||||
|
echo "===== 1. File in /files list ====="
|
||||||
|
curl -s -H "X-API-Key: $API_KEY" "$HOST/api/v1/files?page=1&page_size=50" | \
|
||||||
|
python3 -c "import json,sys; d=json.load(sys.stdin);
|
||||||
|
found=[f for f in d.get('data',[]) if f['file_uuid']=='$UUID'];
|
||||||
|
print('✅ found' if found else '❌ NOT IN LIST')"
|
||||||
|
|
||||||
|
echo "===== 2. File in /files/scan ====="
|
||||||
|
curl -s -H "X-API-Key: $API_KEY" "$HOST/api/v1/files/scan" | \
|
||||||
|
python3 -c "import json,sys; d=json.load(sys.stdin);
|
||||||
|
found=[f for f in d.get('files',[]) if f.get('file_uuid')=='$UUID'];
|
||||||
|
print(f'✅ found, is_registered={found[0][\"is_registered\"]}' if found else '❌ NOT IN SCAN')"
|
||||||
|
|
||||||
|
echo "===== 3. Status = completed ====="
|
||||||
|
curl -s -H "X-API-Key: $API_KEY" "$HOST/api/v1/file/$UUID" | \
|
||||||
|
python3 -c "import json,sys; d=json.load(sys.stdin); print('✅ completed' if d.get('status')=='completed' else f'⚠️ {d.get(\"status\",\"no status field\")}')"
|
||||||
|
|
||||||
|
echo "===== 4. Full API test suite ====="
|
||||||
|
bash /Volumes/docs_v1.0/M4_HANDOVER/api_test.sh 2>&1 | tail -5
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 Portal 層
|
||||||
|
|
||||||
|
| 頁面 | URL | 驗證點 |
|
||||||
|
|------|-----|--------|
|
||||||
|
| FilesView | `http://localhost:1420/files` | ✅ Charade 顯示「已就绪」 |
|
||||||
|
| SearchView | `http://localhost:1420/search` | ✅ 搜尋檔案下拉有 Charade |
|
||||||
|
| Search Play | Search "Audrey Hepburn" → Play | ✅ 影片片段播放 |
|
||||||
|
| Trace View | 點 trace → faces | ✅ 人臉列表 + trace video |
|
||||||
|
| Identities | `http://localhost:1420/identities` | ✅ Cary Grant / Audrey Hepburn 顯示 |
|
||||||
|
|
||||||
|
### 9.3 資料核對
|
||||||
|
|
||||||
|
| 檢查項 | 方法 | 標準 |
|
||||||
|
|--------|------|:--:|
|
||||||
|
| 影片可播放 | `GET /api/v1/file/{uuid}/video` | 200, content-type: video/mp4 |
|
||||||
|
| 縮圖可顯示 | `GET /api/v1/file/{uuid}/thumbnail?frame=1000` | 200, image/jpeg |
|
||||||
|
| chunk detail 存在 | `GET /api/v1/file/{uuid}/chunk/0` | 200 |
|
||||||
|
| face trace video | `GET /api/v1/file/{uuid}/trace/{id}/video` | 200 |
|
||||||
|
| face trace faces 3D | `GET /api/v1/file/{uuid}/trace/{id}/faces?dimension=3d` | 200, has z_rel |
|
||||||
|
| identity detail | `GET /api/v1/identity/{id}` | 200 |
|
||||||
|
| files/scan 標記 registered | `GET /api/v1/files/scan` | is_registered = true |
|
||||||
|
|
||||||
|
### 9.4 上架通過標準
|
||||||
|
|
||||||
|
| 條件 | 狀態 |
|
||||||
|
|------|:--:|
|
||||||
|
| API test 39/39 | ✅ |
|
||||||
|
| Portal FilesView 顯示已就绪 | ✅ |
|
||||||
|
| Portal Search 可搜到 + 可 Play | ✅ |
|
||||||
|
| Portal Face Traces 可查看 | ✅ |
|
||||||
|
| API /files/scan is_registered = true | ✅ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 接受/退回標準
|
||||||
|
|
||||||
|
| 狀態 | 條件 |
|
||||||
|
|:--:|------|
|
||||||
|
| ✅ 接受 | 1-8 全過,identity 覆蓋 > 80%,無 orphan;9.1 全過 |
|
||||||
|
| ⚠️ 暫存 | 1-5 過但 identity 覆蓋偏低,或綁定數偏少 |
|
||||||
|
| ❌ 退回 | hash fail、table 缺漏、匯入失敗、identity 全 NULL、9.1 失敗 |
|
||||||
|
|
||||||
|
退回時附 log 和失敗原因。
|
||||||
177
docs_v1.0/M4_workspace/2026-05-12_tkg_unified_spec.md
Normal file
177
docs_v1.0/M4_workspace/2026-05-12_tkg_unified_spec.md
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
# PG + SQLite TKG 統一分析與檔案內容包要求
|
||||||
|
|
||||||
|
**Date**: 2026-05-12
|
||||||
|
**From**: M4
|
||||||
|
**To**: M5
|
||||||
|
**Priority**: High
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. PG TKG 現狀
|
||||||
|
|
||||||
|
```
|
||||||
|
dev.tkg_nodes dev.tkg_edges
|
||||||
|
────────────── ──────────────
|
||||||
|
id (PK, bigint) id (PK, bigint)
|
||||||
|
node_type (varchar) edge_type (varchar)
|
||||||
|
external_id (varchar) source_node_id (FK → tkg_nodes.id)
|
||||||
|
file_uuid (varchar) target_node_id (FK → tkg_nodes.id)
|
||||||
|
label (varchar) file_uuid (varchar)
|
||||||
|
properties (jsonb) properties (jsonb)
|
||||||
|
created_at (timestamptz) created_at (timestamptz)
|
||||||
|
```
|
||||||
|
|
||||||
|
- 資料量:2,414 nodes / 1,320 edges
|
||||||
|
- 建立方式:`tkg_builder.py` (Python),由 `job_worker.rs:824` 呼叫
|
||||||
|
- **查詢方式**:目前 Rust 程式碼中**沒有** `WITH RECURSIVE` 圖查詢
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. SQLite TKG 設計
|
||||||
|
|
||||||
|
表結構與 PG 完全一致:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE tkg_nodes (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
node_type TEXT NOT NULL,
|
||||||
|
external_id TEXT NOT NULL,
|
||||||
|
file_uuid TEXT NOT NULL,
|
||||||
|
label TEXT,
|
||||||
|
properties TEXT DEFAULT '{}',
|
||||||
|
created_at TEXT DEFAULT (datetime('now')),
|
||||||
|
UNIQUE(file_uuid, node_type, external_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE tkg_edges (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
edge_type TEXT NOT NULL,
|
||||||
|
source_node_id INTEGER NOT NULL REFERENCES tkg_nodes(id) ON DELETE CASCADE,
|
||||||
|
target_node_id INTEGER NOT NULL REFERENCES tkg_nodes(id) ON DELETE CASCADE,
|
||||||
|
file_uuid TEXT NOT NULL,
|
||||||
|
properties TEXT DEFAULT '{}',
|
||||||
|
created_at TEXT DEFAULT (datetime('now')),
|
||||||
|
UNIQUE(file_uuid, edge_type, source_node_id, target_node_id)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 圖查詢(Recursive CTE,PG 和 SQLite 通用)
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- 2-hop traversal:從 Audrey Hepburn 出發走兩步
|
||||||
|
WITH RECURSIVE walk AS (
|
||||||
|
SELECT target_node_id, 1 AS depth
|
||||||
|
FROM tkg_edges
|
||||||
|
WHERE source_node_id = (
|
||||||
|
SELECT id FROM tkg_nodes WHERE label = 'Audrey Hepburn'
|
||||||
|
)
|
||||||
|
UNION ALL
|
||||||
|
SELECT e.target_node_id, w.depth + 1
|
||||||
|
FROM tkg_edges e JOIN walk w ON e.source_node_id = w.target_node_id
|
||||||
|
WHERE w.depth < 3
|
||||||
|
)
|
||||||
|
SELECT n.label, w.depth
|
||||||
|
FROM walk w JOIN tkg_nodes n ON w.target_node_id = n.id;
|
||||||
|
```
|
||||||
|
|
||||||
|
**這份 SQL 在 PG 和 SQLite 上完全一致,無需修改。**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 檔案內容包要求
|
||||||
|
|
||||||
|
每個 `file_{uuid}_{ver}.tar.gz` 必須包含 TKG:
|
||||||
|
|
||||||
|
```
|
||||||
|
file_aeed71342a899fe4b4c57b7d41bcb692_v1.0.tar.gz
|
||||||
|
├── data.sql ← PG dump (videos, chunk, vectors, faces)
|
||||||
|
├── tkg.sqlite ← SQLite TKG (new requirement)
|
||||||
|
├── file_info.json
|
||||||
|
└── checksums.md5
|
||||||
|
```
|
||||||
|
|
||||||
|
### 匯出方式
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# From PG: export node/edge data to SQLite
|
||||||
|
sqlite3 tkg.sqlite "ATTACH ...; CREATE TABLE ...; INSERT INTO ..."
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Or: tkg_builder.py generates both PG INSERT and SQLite
|
||||||
|
python3 tkg_builder.py build \
|
||||||
|
--uuid {uuid} \
|
||||||
|
--pg-output pg_tkg.sql \
|
||||||
|
--sqlite-output {uuid}.tkg.sqlite
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Momentry Core 實作要求
|
||||||
|
|
||||||
|
### trait 定義
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub trait TkgStore: Send + Sync {
|
||||||
|
async fn get_nodes(&self, file_uuid: &str) -> Result<Vec<TkgNode>>;
|
||||||
|
async fn get_edges(&self, file_uuid: &str) -> Result<Vec<TkgEdge>>;
|
||||||
|
async fn traverse(&self, node_id: i64, max_depth: i32) -> Result<Vec<TkgPath>>;
|
||||||
|
async fn neighbors(&self, node_id: i64) -> Result<Vec<(TkgNode, TkgEdge)>>;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 兩個實作,同一份 SQL
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// PG backend
|
||||||
|
struct PgTkg { pool: PgPool }
|
||||||
|
impl TkgStore for PgTkg {
|
||||||
|
async fn traverse(&self, node_id: i64, max_depth: i32) -> ... {
|
||||||
|
sqlx::query_as(SAME_RECURSIVE_CTE_SQL) // WITH RECURSIVE ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SQLite backend
|
||||||
|
struct SqliteTkg { conn: rusqlite::Connection }
|
||||||
|
impl TkgStore for SqliteTkg {
|
||||||
|
async fn traverse(&self, node_id: i64, max_depth: i32) -> ... {
|
||||||
|
conn.prepare(SAME_RECURSIVE_CTE_SQL) // WITH RECURSIVE ... (same SQL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 啟動時選擇後端
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let tkg: Box<dyn TkgStore> = match env::var("MOMENTRY_TKG_BACKEND")
|
||||||
|
.unwrap_or("pg".into()).as_str()
|
||||||
|
{
|
||||||
|
"sqlite" => Box::new(SqliteTkg::open("tkg.sqlite")?),
|
||||||
|
_ => Box::new(PgTkg::new(&pg_pool)),
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 一致性原則
|
||||||
|
|
||||||
|
| 面向 | 原則 |
|
||||||
|
|------|------|
|
||||||
|
| **Schema** | PG 和 SQLite 表結構完全一致 |
|
||||||
|
| **匯出** | pipeline 建 TKG 時同時輸出 PG + SQLite |
|
||||||
|
| **查詢** | Recursive CTE SQL 語法一致,兩邊通用 |
|
||||||
|
| **檔案包** | 每個 tar.gz 必須含 `tkg.sqlite` |
|
||||||
|
| **分發** | PG 在 API server,SQLite 隨包分發 |
|
||||||
|
| **唯一異動** | `tkg_builder.py` 加 `--sqlite-output` 參數 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 好處
|
||||||
|
|
||||||
|
| | PG-only (現狀) | + SQLite TKG |
|
||||||
|
|---|:--:|:--:|
|
||||||
|
| 檔案包自帶 TKG | ❌ | ✅ |
|
||||||
|
| 離線圖查詢 | ❌ | ✅ |
|
||||||
|
| Recursive CTE trait | ❌ 無 | ✅ 兩端統一 |
|
||||||
|
| Desktop/Tauri | ❌ | ✅ 單檔即可 |
|
||||||
|
| 跨機器轉移 | dump/restore | 複製 .sqlite |
|
||||||
55
docs_v1.0/M4_workspace/2026-05-13_api_3002_test_report.md
Normal file
55
docs_v1.0/M4_workspace/2026-05-13_api_3002_test_report.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# API 測試報告 — 3002 (Production) vs API 文件
|
||||||
|
|
||||||
|
**Date**: 2026-05-13
|
||||||
|
**From**: M4
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 測試範圍
|
||||||
|
|
||||||
|
| 環境 | Port | Schema |
|
||||||
|
|------|:---:|--------|
|
||||||
|
| Production | 3002 | `public` (chunks, old schema) |
|
||||||
|
| Development | 3003 | `dev` (chunk, new schema) |
|
||||||
|
|
||||||
|
共測試 18 個 endpoint。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 結果:18/18 全過 (3002)
|
||||||
|
|
||||||
|
所有 endpoint 返回 200。無 500 或 404。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## DOC vs ACTUAL 格式差異(5 處)
|
||||||
|
|
||||||
|
與 API_REFERENCE_V1.0.0.md 的差異:
|
||||||
|
|
||||||
|
| # | Endpoint | DOC key | ACTUAL key | 影響 |
|
||||||
|
|---|----------|---------|------------|------|
|
||||||
|
| 1 | `/files` | `"files"` | `"data"` | Portal/Client 需改 |
|
||||||
|
| 2 | `/files` | 缺 `success` | `success:true` | |
|
||||||
|
| 3 | `/resources` | `"resources"` | `"data"` | |
|
||||||
|
| 4 | `/search/universal` | `"count"` | `"total"` | Portal 需改 |
|
||||||
|
| 5 | `/search/universal` | 缺分頁 | `page`, `page_size`, `took_ms` | |
|
||||||
|
| 6 | `/identities` | 缺分頁 | `count`, `page`, `page_size` | |
|
||||||
|
| 7 | `/faces/candidates` | ✅ | ✅ | 無差異 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3002 vs 3003 差異
|
||||||
|
|
||||||
|
| Endpoint | 3002 | 3003 | 原因 |
|
||||||
|
|----------|:--:|:--:|------|
|
||||||
|
| `/file/:uuid/chunks` | 200 | **404** | dev 已 rename chunks→chunk |
|
||||||
|
| `/search/universal` | 需 `uuid` | 可省略 | 不同 schema 行為 |
|
||||||
|
| Identity endpoints | 不同資料 | 不同資料 | dev 有 v2.0 含 identity |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 建議
|
||||||
|
|
||||||
|
1. **API doc 修正**:5 個欄位名錯誤(已回報 M4_workspace/2026-05-12_api_doc_verification.md)
|
||||||
|
2. **/chunks 不一致**:dev 已移除但 prod 仍可用 — M5 需決定遷移時程
|
||||||
|
3. **3002 schema 更新**:public schema 仍需 M5 v1.0 migration
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# api_test.sh v2.0 相容性回報
|
||||||
|
|
||||||
|
**Date**: 2026-05-13
|
||||||
|
**From**: M4
|
||||||
|
**To**: M5
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
v2.0 匯入後 api_test.sh 37/39。2 個 fail 是測試資料格式變更導致:
|
||||||
|
|
||||||
|
## Fail 1: Identity UUID 404
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/identity/2b0ddefe-e2a9-4533-9308-b375594604d5 → 404
|
||||||
|
```
|
||||||
|
|
||||||
|
**原因**:v2.0 重新匯入 identity(428 個 `PERSON_aeed7134_xxx`),舊 UUID 不存在。
|
||||||
|
|
||||||
|
**建議**:test script 改用 `GET /api/v1/identities` 取得最新 identity UUID,或使用固定 test UUID。
|
||||||
|
|
||||||
|
## Fail 2: chunk/0-01 404
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/file/aeed7134.../chunk/0-01 → 404
|
||||||
|
```
|
||||||
|
|
||||||
|
**原因**:v2.0 chunk ID 改為純數字(`0`, `1`, `2`...),不再使用 `{parent}-{seq}` 格式。
|
||||||
|
|
||||||
|
**建議**:test script 改用 `chunk/0` 或 `chunk/875`(現有 chunk ID)。
|
||||||
|
|
||||||
|
## 結論
|
||||||
|
|
||||||
|
兩個 fail 都是資料格式變更導致,非 bug。api_test.sh 需要更新對應 v2.0 資料格式。
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# public.chunk_vectors unique constraint 衝突
|
||||||
|
|
||||||
|
**Date**: 2026-05-13
|
||||||
|
**From**: M4
|
||||||
|
**To**: M5
|
||||||
|
**Priority**: High
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 問題
|
||||||
|
|
||||||
|
`public.chunk_vectors` 有兩個 unique constraint:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
"chunk_vectors_chunk_id_key" UNIQUE CONSTRAINT, btree (chunk_id) -- ❌ 衝突
|
||||||
|
"chunk_vectors_chunk_id_uuid_key" UNIQUE CONSTRAINT, btree (chunk_id, uuid) -- ✅ 正確
|
||||||
|
```
|
||||||
|
|
||||||
|
`chunk_vectors_chunk_id_key` 要求 chunk_id 在整個 table 中唯一。但兩個不同 UUID 的檔案可以有相同的 chunk_id(如兩者都用純數字編碼)。
|
||||||
|
|
||||||
|
### 實際衝突
|
||||||
|
|
||||||
|
```
|
||||||
|
aeed7134: chunk_id=2091 → chunk_vectors (aeed7134, 2091)
|
||||||
|
23b1c87 : chunk_id=2091 → INSERT FAILS (duplicate unique on chunk_id alone)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 影響
|
||||||
|
|
||||||
|
23b1c87 的 2,340 chunk_vectors 無法匯入 public schema。
|
||||||
|
|
||||||
|
## 修正
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ALTER TABLE public.chunk_vectors DROP CONSTRAINT IF EXISTS chunk_vectors_chunk_id_key;
|
||||||
|
```
|
||||||
|
|
||||||
|
`chunk_vectors_chunk_id_uuid_key` 已正確提供 `(chunk_id, uuid)` 複合唯一,不需要單獨的 chunk_id unique。
|
||||||
|
|
||||||
|
## 驗證
|
||||||
|
|
||||||
|
修正後 23b1c87 vectors 可成功匯入,aeed7134 vectors 不受影響。
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
# 空間與時間座標系統 Registry — M5 匯報
|
||||||
|
|
||||||
|
**Date**: 2026-05-13
|
||||||
|
**From**: M4
|
||||||
|
**To**: M5
|
||||||
|
**Ref**: `docs_v1.0/REFERENCE/SPATIAL_COORDINATE_REGISTRY.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 完成項目
|
||||||
|
|
||||||
|
M4 已完成 Momentry Core 所有空間與時間座標系統的清查、校準、文檔化:
|
||||||
|
|
||||||
|
| Registry | 系統數 | 檔案位置 |
|
||||||
|
|----------|:--:|------|
|
||||||
|
| 空間 (spatial) | 18 個系統 | `REFERENCE/SPATIAL_COORDINATE_REGISTRY.md` §座標系統清單 |
|
||||||
|
| 時間 (temporal) | 16 個系統 | 同上 §時間座標系統 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 一致的部分(✅)
|
||||||
|
|
||||||
|
- **Top-Left 原點**:所有儲存/API/渲染層一致
|
||||||
|
- **ffmpeg drawbox/crop**:與 `face_detections` 原點一致,直接 passthrough
|
||||||
|
- **3D 視覺化 Y 翻轉**:`SpaceTimeCube.vue` / `Face3DViewer.vue` 正確處理
|
||||||
|
- **FrameTime**:Rust 核心時間型別定義清晰,`.round()` 統一
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 需 M5 修正的問題(8 項)
|
||||||
|
|
||||||
|
### 🔴 High (3)
|
||||||
|
|
||||||
|
| # | 問題 | 影響 | 位置 |
|
||||||
|
|---|------|------|------|
|
||||||
|
| **B1** | **Python `int()` vs Rust `.round()` 不一致** | Python 用 `int` (truncate), Rust 用 `.round()`。1.999s@30fps: Python=59f, Rust=60f。pre_chunks 跨語言傳遞會有 ±1 frame 誤差 | 15+ Python scripts |
|
||||||
|
| **B2** | **15+ Python scripts hardcoded FPS** | `25.0`, `24.0`, `30.0` 硬寫,非從 probe 讀取。非 25fps 影片 chunk 時間全錯 | `story_pipeline_full.py` 等 |
|
||||||
|
| — | **Landmark 座標不匹配** | 已另案報告 `2026-05-13_landmark_coord_mismatch.md` | `face.json` |
|
||||||
|
|
||||||
|
### 🟡 Medium (3)
|
||||||
|
|
||||||
|
| # | 問題 | 影響 | 位置 |
|
||||||
|
|---|------|------|------|
|
||||||
|
| **B3** | **`register_single_file` vs `probe_by_uuid` total_frames 不一致** | 前者 `as u64` (truncate), 後者 `.floor()` | `server.rs:763,1136` |
|
||||||
|
| **B4** | **`timestamp_secs` 冗餘欄位** | 與 `frame_number` 可能不同步,建議 deprecated | `face_detections` |
|
||||||
|
| **B5** | **`format_sec_frame()` 用 `.ceil()` 處理 fps** | 29.97fps 當 30fps 處理 | `time.rs:99` |
|
||||||
|
|
||||||
|
### 🟢 Low (2)
|
||||||
|
|
||||||
|
| # | 問題 | 影響 | 位置 |
|
||||||
|
|---|------|------|------|
|
||||||
|
| **B6** | **TypeScript 預設 fps=30** | 非 30fps 影片前端 frame 計算錯誤 | `client.ts:273` |
|
||||||
|
| **B7** | **`start_time` 只到 0.1s 精度** | 高精度 seek 失真 | `trace_agent_api.rs` |
|
||||||
|
| **B8** | **`total_frames` 型別不一致** | `Option<i64>` / `u64` / `i64` 三種 | DB structs |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 建議 M5 行動
|
||||||
|
|
||||||
|
1. **Python second→frame 改用 `round()`** — 最高優先,消除 ±1 frame 差異
|
||||||
|
2. **Python FPS 改從 probe 讀取** — 不要 hardcode
|
||||||
|
3. **統一 `total_frames` 計算方式** — 註冊和探測用相同方法
|
||||||
|
4. **`timestamp_secs` 標記 deprecated** — 新增資料不再依賴
|
||||||
|
5. **更新 `api_test.sh`** — 驗證 frame 計算正確性
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# deploy.sh — REQUIRED_FILES 未定義
|
||||||
|
|
||||||
|
**Date**: 2026-05-13
|
||||||
|
**From**: M4
|
||||||
|
**To**: M5
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Bug
|
||||||
|
|
||||||
|
`deploy.sh` line 52 引用 `${REQUIRED_FILES[@]}`,但變數從未定義:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo "[1/8] Verifying package..."
|
||||||
|
MISSING=0
|
||||||
|
for f in "${REQUIRED_FILES[@]}"; do ← line 52: REQUIRED_FILES is undefined
|
||||||
|
```
|
||||||
|
|
||||||
|
執行時報錯:`REQUIRED_FILES[@]: unbound variable`
|
||||||
|
|
||||||
|
## Fix
|
||||||
|
|
||||||
|
在 for loop 前加入定義:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
REQUIRED_FILES=("data.sql" "file_info.json")
|
||||||
|
```
|
||||||
|
|
||||||
|
修正後行號 50-52 應為:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo "[1/8] Verifying package..."
|
||||||
|
REQUIRED_FILES=("data.sql" "file_info.json")
|
||||||
|
MISSING=0
|
||||||
|
```
|
||||||
68
docs_v1.0/M4_workspace/2026-05-13_deploy_script_fix.md
Normal file
68
docs_v1.0/M4_workspace/2026-05-13_deploy_script_fix.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Momentry Release Package — Deploy Script
|
||||||
|
# 原則: 一包一檔,只管內容所涵蓋的檔案
|
||||||
|
# Usage: bash deploy.sh
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
UUID=$(basename "$DIR")
|
||||||
|
PG_BIN="${PG_BIN:-/Users/accusys/pgsql/18.3/bin}"
|
||||||
|
DB_NAME="${DB_NAME:-momentry}"
|
||||||
|
DB_USER="${DB_USER:-accusys}"
|
||||||
|
DEMO_DIR="${DEMO_DIR:-/Users/accusys/momentry/var/sftpgo/data/demo}"
|
||||||
|
|
||||||
|
echo "=== Momentry Package Deploy ==="
|
||||||
|
echo "UUID: $UUID"
|
||||||
|
echo "Time: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# 1. Verify package — only this one file
|
||||||
|
echo "[1/4] Verifying package..."
|
||||||
|
REQUIRED_FILES=("data.sql" "file_info.json")
|
||||||
|
for f in "${REQUIRED_FILES[@]}"; do
|
||||||
|
if [ ! -f "$DIR/$f" ]; then
|
||||||
|
echo "ERROR: Missing required file: $f"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo " ✅ Package verified (single file: $UUID)"
|
||||||
|
|
||||||
|
# 2. Import data.sql (already contains videos.chunk.chunk_vectors.face_detections.identities...)
|
||||||
|
echo "[2/4] Importing DB data..."
|
||||||
|
"$PG_BIN/psql" -U "$DB_USER" -d "$DB_NAME" -f "$DIR/data.sql" 2>&1 | tail -3
|
||||||
|
echo " ✅ Data imported"
|
||||||
|
|
||||||
|
# 3. Set video status to completed (已處理)
|
||||||
|
echo "[3/4] Setting deployment status..."
|
||||||
|
"$PG_BIN/psql" -U "$DB_USER" -d "$DB_NAME" -c "
|
||||||
|
UPDATE videos SET status = 'completed' WHERE file_uuid = '$UUID'
|
||||||
|
" > /dev/null 2>&1
|
||||||
|
echo " ✅ Status set to 'completed'"
|
||||||
|
|
||||||
|
# 4. Copy video to demo dir (only this package's video, not scanning others)
|
||||||
|
VIDEO_FILE=$(ls "$DIR"/*.mp4 "$DIR"/*.mov "$DIR"/*.avi "$DIR"/*.mkv 2>/dev/null | head -1)
|
||||||
|
if [ -n "$VIDEO_FILE" ]; then
|
||||||
|
VIDEO_NAME=$(basename "$VIDEO_FILE")
|
||||||
|
DEST="$DEMO_DIR/$VIDEO_NAME"
|
||||||
|
if [ ! -f "$DEST" ]; then
|
||||||
|
cp "$VIDEO_FILE" "$DEST"
|
||||||
|
echo "[4/4] Video copied: $VIDEO_NAME"
|
||||||
|
else
|
||||||
|
echo "[4/4] Video already present, skipping"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "[4/4] No video file in package, skipping"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify
|
||||||
|
CHUNKS=$("$PG_BIN/psql" -U "$DB_USER" -d "$DB_NAME" -t -A -c "SELECT COUNT(*) FROM dev.chunk WHERE file_uuid='$UUID'" 2>/dev/null || echo "?")
|
||||||
|
FACES=$("$PG_BIN/psql" -U "$DB_USER" -d "$DB_NAME" -t -A -c "SELECT COUNT(*) FROM dev.face_detections WHERE file_uuid='$UUID'" 2>/dev/null || echo "?")
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== Deploy Complete ==="
|
||||||
|
echo " UUID: $UUID"
|
||||||
|
echo " Status: completed"
|
||||||
|
echo " Chunks: $CHUNKS"
|
||||||
|
echo " Faces: $FACES"
|
||||||
|
echo ""
|
||||||
|
echo "Verify: http://localhost:3003/api/v1/file/$UUID"
|
||||||
38
docs_v1.0/M4_workspace/2026-05-13_deploy_sh_remaining.md
Normal file
38
docs_v1.0/M4_workspace/2026-05-13_deploy_sh_remaining.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Re: deploy.sh — 剩餘問題
|
||||||
|
|
||||||
|
**Date**: 2026-05-13
|
||||||
|
**From**: M4
|
||||||
|
**To**: M5
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
已確認 M5 更新版本。修正項目 ✅ 通過:
|
||||||
|
- chunk_type filter 已移除
|
||||||
|
- identity count 驗證已加入
|
||||||
|
- TKG 驗證已加入
|
||||||
|
- schema prefix (`dev.chunk` 等) 已加入
|
||||||
|
|
||||||
|
## 三個剩餘問題
|
||||||
|
|
||||||
|
### 1. ⚠️ 影片複製區塊重複(行 40-65)
|
||||||
|
|
||||||
|
行 40-53 和行 54-65 是同一段邏輯的重複貼上。執行時會複製兩次。
|
||||||
|
|
||||||
|
**修正**:刪除行 54-65。
|
||||||
|
|
||||||
|
### 2. ❌ 匯入後未設定 status = 'completed'
|
||||||
|
|
||||||
|
內容包已處理完畢,匯入後該檔案應為「已處理」。目前 deploy.sh 沒有 `UPDATE videos SET status`。
|
||||||
|
|
||||||
|
**修正**:在 import 後加入:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
"$PG_BIN/psql" -U "$DB_USER" -d "$DB_NAME" -c \
|
||||||
|
"UPDATE dev.videos SET status = 'completed' WHERE file_uuid = '$UUID'"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. ⚠️ 仍提示啟動 pipeline(行 99-100)
|
||||||
|
|
||||||
|
內容包已為完整處理後狀態,不需要再觸發 pipeline。提示會誤導使用者。
|
||||||
|
|
||||||
|
**修正**:刪除或改為驗證提示。
|
||||||
54
docs_v1.0/M4_workspace/2026-05-13_landmark_coord_mismatch.md
Normal file
54
docs_v1.0/M4_workspace/2026-05-13_landmark_coord_mismatch.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# Face Landmark Coordinate Mismatch
|
||||||
|
|
||||||
|
**Date**: 2026-05-13
|
||||||
|
**From**: M4
|
||||||
|
**To**: M5
|
||||||
|
**Priority**: High
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Discovery
|
||||||
|
|
||||||
|
`face.json` contains landmark data for ALL 70,691 face detections (right_eye 6pts, left_eye 6pts, nose 8pts). However, landmark coordinates do not align with their corresponding bounding boxes.
|
||||||
|
|
||||||
|
## Data
|
||||||
|
|
||||||
|
| Metric | Value |
|
||||||
|
|--------|------:|
|
||||||
|
| Total faces | 70,691 |
|
||||||
|
| Faces with landmarks | 70,691 (100%) |
|
||||||
|
| Total landmark points | 1,413,820 |
|
||||||
|
| **Points OUTSIDE bbox** | **1,239,047 (87.6%)** |
|
||||||
|
| **Faces with ≥1 outside point** | **64,673 (91.5%)** |
|
||||||
|
|
||||||
|
## Example Violation
|
||||||
|
|
||||||
|
```
|
||||||
|
frame=4572:
|
||||||
|
bbox: [x=0, y=870, 325×325] ← bottom of 1080p frame
|
||||||
|
left_eye: [(-11, 139), (2, 144), ...] ← top of frame
|
||||||
|
|
||||||
|
y difference: bbox y=870, eye y=139 → 731px apart in different regions
|
||||||
|
```
|
||||||
|
|
||||||
|
## Likely Causes
|
||||||
|
|
||||||
|
| # | Cause |
|
||||||
|
|---|-------|
|
||||||
|
| 1 | Landmarks use **frame-level coordinates**, bbox uses **face-cropped coordinates** |
|
||||||
|
| 2 | Landmark data and bbox data are **mismatched** (belong to different detections) |
|
||||||
|
| 3 | Y-axis may be flipped (different detector convention) |
|
||||||
|
|
||||||
|
## Impact
|
||||||
|
|
||||||
|
- ❌ Landmark visualization (eye/nose overlay) will be incorrect
|
||||||
|
- ❌ Landmark-based identity matching will fail
|
||||||
|
- ❌ Data unusable for downstream landmark analysis
|
||||||
|
- ⚠️ Demo showing face landmarks will display incorrectly
|
||||||
|
|
||||||
|
## Required Fix
|
||||||
|
|
||||||
|
1. Unify landmark and bbox coordinate system
|
||||||
|
2. Re-run face pipeline with corrected landmark output
|
||||||
|
3. Regenerate `face.json` and PostgreSQL data
|
||||||
|
4. Re-deliver updated file content package
|
||||||
31
docs_v1.0/M4_workspace/2026-05-13_release_binary_owner.md
Normal file
31
docs_v1.0/M4_workspace/2026-05-13_release_binary_owner.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# Decision #1 更正:Release Binary 由 M5 交付
|
||||||
|
|
||||||
|
**Date**: 2026-05-13
|
||||||
|
**From**: M4
|
||||||
|
**To**: M5
|
||||||
|
**Ref**: `2026-05-13_release_decision_response.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 更正
|
||||||
|
|
||||||
|
`momentry_core` 的 build pipeline 由 M5 維護。M4 只負責測試與部署,不負責 build。
|
||||||
|
|
||||||
|
## 修正後的 Release 分工
|
||||||
|
|
||||||
|
| Item | Owner | 說明 |
|
||||||
|
|------|:--:|------|
|
||||||
|
| **`momentry` release binary** | **M5** | M5 編譯交付(version 1.0.0, build 01742b2) |
|
||||||
|
| `.env.production` config | M4 | `DATABASE_SCHEMA=public` etc. |
|
||||||
|
| `aeed7134` package | M5 | 檔案內容包 |
|
||||||
|
| `23b1c87` package | M5 | 檔案內容包 |
|
||||||
|
|
||||||
|
## 其他 5 項決策不變
|
||||||
|
|
||||||
|
| # | 決策 | Owner |
|
||||||
|
|---|------|:--:|
|
||||||
|
| 2 | Schema: public.chunks → public.chunk (RENAME) | M4 執行 |
|
||||||
|
| 3 | Identity: 保留 prod 15 TMDB + merge dev data | M4 執行 |
|
||||||
|
| 4 | Downtime: Maintenance mode (503) | M4 執行 |
|
||||||
|
| 5 | Scope: both aeed7134 + 23b1c87 | M5 交付兩包 |
|
||||||
|
| 6 | Config: `.env.production` with `DATABASE_SCHEMA=public` | M4 準備 |
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Release SOP Decision Log — 請 M5 回覆
|
||||||
|
|
||||||
|
**Date**: 2026-05-13
|
||||||
|
**From**: M4
|
||||||
|
**To**: M5
|
||||||
|
**Ref**: `REFERENCE/RELEASE_SOP_V2.0.md` §8
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
SOP 第 8 章 Decision Log 有 6 項待 M5 決定。確認後即可執行 release:
|
||||||
|
|
||||||
|
| # | 問題 | 選項 |
|
||||||
|
|---|------|------|
|
||||||
|
| **1** | Production binary 來源? | A. M5 提供 release binary / B. M4 從 source 自編 `cargo build --release` |
|
||||||
|
| **2** | Schema: public.chunks → public.chunk? | A. 執行 RENAME / B. 保留 `chunks` 另做相容 |
|
||||||
|
| **3** | Identity merge? | A. 保留 prod 現有 15 TMDB + import dev data / B. 清空 prod 只用 dev data |
|
||||||
|
| **4** | Downtime 方式? | A. Maintenance mode (503 all requests) / B. Hard stop + restart |
|
||||||
|
| **5** | Release scope? | A. `aeed7134` only / B. both `aeed7134` + `23b1c87` (YouTube) |
|
||||||
|
| **6** | Production config? | A. `DATABASE_SCHEMA=public` via .env / B. Hardcoded in binary |
|
||||||
73
docs_v1.0/M4_workspace/2026-05-13_release_fixes_required.md
Normal file
73
docs_v1.0/M4_workspace/2026-05-13_release_fixes_required.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# Release 執行時發現的修正需求
|
||||||
|
|
||||||
|
**Date**: 2026-05-13
|
||||||
|
**From**: M4
|
||||||
|
**To**: M5
|
||||||
|
**Priority**: High
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 發現問題
|
||||||
|
|
||||||
|
aeed7134 deploy to `public` schema 時發生 3 個 error,均已當場手動修正後通過:
|
||||||
|
|
||||||
|
| # | Error | Root Cause | Fix Applied |
|
||||||
|
|---|-------|-----------|-------------|
|
||||||
|
| 1 | `videos_pkey` duplicate | 舊 `public.videos` data 未清除 | `DELETE FROM public.videos WHERE file_uuid = '{uuid}'` |
|
||||||
|
| 2 | `column "file_uuid" does not exist` | `public.identities` 缺 v2 schema column | `ALTER TABLE public.identities ADD COLUMN file_uuid VARCHAR(64)` |
|
||||||
|
| 3 | `identity_bindings_pkey` duplicate | 舊 bindings 未清除 | `DELETE FROM public.identity_bindings WHERE identity_id IN (...)` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 對 deploy.sh 的修正
|
||||||
|
|
||||||
|
### Step 2 pre-clean 需擴展
|
||||||
|
|
||||||
|
當前 pre-clean 只刪除 identities:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DELETE FROM identities WHERE file_uuid = '{uuid}';
|
||||||
|
```
|
||||||
|
|
||||||
|
需擴展為清除全部受影響 table:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
DELETE FROM chunk_vectors WHERE uuid = '{uuid}';
|
||||||
|
DELETE FROM chunk WHERE file_uuid = '{uuid}';
|
||||||
|
DELETE FROM face_detections WHERE file_uuid = '{uuid}';
|
||||||
|
DELETE FROM tkg_edges WHERE file_uuid = '{uuid}';
|
||||||
|
DELETE FROM tkg_nodes WHERE file_uuid = '{uuid}';
|
||||||
|
DELETE FROM identity_bindings WHERE identity_id IN (
|
||||||
|
SELECT id FROM identities WHERE tmdb_id IS NOT NULL OR name LIKE 'PERSON_{short_uuid}%'
|
||||||
|
);
|
||||||
|
DELETE FROM identities WHERE file_uuid = '{uuid}' OR source = 'tmdb';
|
||||||
|
DELETE FROM videos WHERE file_uuid = '{uuid}';
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 對 release SOP 的修正
|
||||||
|
|
||||||
|
### SOP Step 3 schema migration 需加入
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ALTER TABLE identities ADD COLUMN IF NOT EXISTS file_uuid VARCHAR(64);
|
||||||
|
```
|
||||||
|
|
||||||
|
(dev schema 已有此 column,v2.0 111614 package identity CSV 包含 file_uuid)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Release 現狀
|
||||||
|
|
||||||
|
aeed7134 已成功 deploy to public schema:
|
||||||
|
|
||||||
|
| Table | Count |
|
||||||
|
|-------|------:|
|
||||||
|
| chunk | 2,407 |
|
||||||
|
| faces | 70,691 |
|
||||||
|
| videos | 1 |
|
||||||
|
| TMDB identities | 15 |
|
||||||
|
| tkg_nodes | 6,457 |
|
||||||
|
| tkg_edges | 21,028 |
|
||||||
|
| status | completed |
|
||||||
111
docs_v1.0/M4_workspace/2026-05-13_release_notes.md
Normal file
111
docs_v1.0/M4_workspace/2026-05-13_release_notes.md
Normal 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) |
|
||||||
77
docs_v1.0/M4_workspace/2026-05-13_scan_status_issues.md
Normal file
77
docs_v1.0/M4_workspace/2026-05-13_scan_status_issues.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# 3002 Production — Scan 與 Files 狀態驗證
|
||||||
|
|
||||||
|
**Date**: 2026-05-13
|
||||||
|
**From**: M4
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Issue 1: Scan 覆蓋不足
|
||||||
|
|
||||||
|
| 指標 | 數值 |
|
||||||
|
|------|:----:|
|
||||||
|
| Demo 目錄實際檔案數 | **106** |
|
||||||
|
| Scan 回傳檔案數 | **23** |
|
||||||
|
| 缺漏 | **83** |
|
||||||
|
|
||||||
|
### 原因
|
||||||
|
|
||||||
|
`scan_directory_recursive` 只過濾 `["mp4", "mov", "mkv", "avi", "webm"]`(server.rs:1905)。Demo 目錄有大量 `.jpg`、`.png` 檔案(animal1.jpg, animal2.jpg 等)不在過濾清單中。
|
||||||
|
|
||||||
|
### 影響
|
||||||
|
|
||||||
|
- Portal FilesView 只顯示 23 個檔案
|
||||||
|
- 83 個圖片/非影片檔案完全看不到
|
||||||
|
- `.jpg` `.png` 應該加入 allowed_extensions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Issue 2: `/files` 狀態與事實不符
|
||||||
|
|
||||||
|
| | DB 實際狀態 | API 回報 |
|
||||||
|
|---|:--:|:--:|
|
||||||
|
| completed | 3 | — |
|
||||||
|
| processing | 2 | — |
|
||||||
|
| pending | 8 | — |
|
||||||
|
| failed | **23** | — |
|
||||||
|
| — | — | **ready: 36** |
|
||||||
|
|
||||||
|
### 原因
|
||||||
|
|
||||||
|
`identity_api.rs:97` hardcodes status:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
status: "ready".to_string(), // Hardcoded for now
|
||||||
|
```
|
||||||
|
|
||||||
|
API 回傳的 `status` 不是從 DB 讀取的,是寫死的。
|
||||||
|
|
||||||
|
### 影響
|
||||||
|
|
||||||
|
- Portal FilesView 顯示所有檔案為「已就绪」(綠色 ✅),即使 23 個是 `failed`
|
||||||
|
- 使用者看不到真實的處理狀態
|
||||||
|
- Portal 狀態過濾器(pending/processing/completed)全部無效
|
||||||
|
|
||||||
|
### 修正
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// identity_api.rs — 從 DB 讀取 status
|
||||||
|
let records = state.db.list_files(page_size, offset).await?;
|
||||||
|
let data = records.into_iter().map(|r| FileItem {
|
||||||
|
file_uuid: r.file_uuid,
|
||||||
|
file_name: r.file_name,
|
||||||
|
file_path: r.file_path,
|
||||||
|
status: r.status.unwrap_or("unknown".to_string()), // ← 改這行
|
||||||
|
}).collect();
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Issue 3: Scan registered vs /files list 不一致
|
||||||
|
|
||||||
|
| | Scan | /files list |
|
||||||
|
|---|:--:|:--:|
|
||||||
|
| Registered files | 21 | 36 |
|
||||||
|
|
||||||
|
**15 個已註冊檔案不在 Scan 結果中** — 這些檔案在 DB 中有紀錄,但 demo 目錄中對應的實體檔案已不存在(被刪除或移動)。
|
||||||
|
|
||||||
|
**2 個 Scan 中的檔案未註冊** — `Charade_YouTube_24fps.mp4` 和另一個檔案。它們在目錄中但未在 `public.videos` 中註冊。
|
||||||
110
docs_v1.0/M4_workspace/2026-05-13_v2_package_issues.md
Normal file
110
docs_v1.0/M4_workspace/2026-05-13_v2_package_issues.md
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
# V2.0 檔案內容包 — 問題與建議
|
||||||
|
|
||||||
|
**Date**: 2026-05-13
|
||||||
|
**From**: M4
|
||||||
|
**To**: M5
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 問題清單
|
||||||
|
|
||||||
|
### ❌ Issue 1: TKG 未匯入 PostgreSQL
|
||||||
|
|
||||||
|
TKG 資料(6,457 nodes, 21,028 edges)只存在 SQLite,未匯入 PG。`deploy.sh` 只跑 `data.sql`,沒有 TKG 匯入步驟。
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- PG: 0 tkg_nodes, 0 tkg_edges
|
||||||
|
-- SQLite: 6457 tkg_nodes, 21028 tkg_edges
|
||||||
|
```
|
||||||
|
|
||||||
|
**建議**:`data.sql` 應包含 `COPY dev.tkg_nodes` + `COPY dev.tkg_edges`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ⚠️ Issue 2: TMDB 匹配未套用
|
||||||
|
|
||||||
|
428 個 identity 全部命名為 `PERSON_aeed7134_xx`(原始 clustering 輸出),tmdb_id 全為 NULL。
|
||||||
|
HANDOVER_V2.0.md 提到「TMDB matched: Audrey Hepburn 843 traces, Cary Grant 482」,但實際身份表中無對應。
|
||||||
|
|
||||||
|
| | DOC says | ACTUAL |
|
||||||
|
|---|---------|--------|
|
||||||
|
| Audrey Hepburn | 843 traces | 0 |
|
||||||
|
| Cary Grant | 482 traces | 0 |
|
||||||
|
| Any tmdb_id | — | 0/428 |
|
||||||
|
|
||||||
|
**trace_to_identity.json 中有 trace→identity 映射**(5,483 records),但 identity 名稱仍是 `PERSON_xxx`。
|
||||||
|
|
||||||
|
**建議**:TMDB matching step 需在打包前執行,identity 名稱應更新為 TMDB 演員名。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ⚠️ Issue 3: identity_bindings 數量不一致
|
||||||
|
|
||||||
|
| 來源 | bindings 數 |
|
||||||
|
|------|-----------:|
|
||||||
|
| PG (after import) | 10,966 |
|
||||||
|
| SQLite | 5,483 |
|
||||||
|
| HANDOVER doc | 5,483 |
|
||||||
|
|
||||||
|
PG 數量是 SQLite 的 2 倍,因為 `data.sql` 的 COPY 匯入了**跨檔案**的全局 identity_bindings,不是此檔案專屬。
|
||||||
|
|
||||||
|
**建議**:`data.sql` 匯出時過濾 `WHERE identity_id IN (SELECT id FROM identities WHERE source = 'auto')` 或只匯出此檔案的 binding。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ⚠️ Issue 4: 只有 sentence chunks
|
||||||
|
|
||||||
|
| chunk_type | v1.0 | v2.0 |
|
||||||
|
|-----------|-----:|-----:|
|
||||||
|
| sentence | 4188 | 2407 |
|
||||||
|
| cut | 1130 | **0** |
|
||||||
|
| story | 280 | **0** |
|
||||||
|
| trace | 423 | **0** |
|
||||||
|
|
||||||
|
只匯出了 sentence chunk,缺少 cut/story/trace/visual 類型。
|
||||||
|
|
||||||
|
**建議**:確認 M5 pipeline 是否已完成 scene/story/trace chunking。若已完成,`data.sql` 應包含所有 chunk_type。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ❌ Issue 5: Landmark 座標不匹配
|
||||||
|
|
||||||
|
已另案報告 (`2026-05-13_landmark_coord_mismatch.md`)。
|
||||||
|
87.6% 地標點不在 bbox 範圍內。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### ⚠️ Issue 6: SQLite 向量無法直接查詢
|
||||||
|
|
||||||
|
sqlite-vec 資料表存在(chunk/face/voice embeddings),但需要 `vec0` extension 才能查詢。CLI 無法直接使用。
|
||||||
|
|
||||||
|
**建議**:包內附 `vec0.dylib` 或提供 `sqlite-vec` 安裝 script。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 建議優先級
|
||||||
|
|
||||||
|
| # | Issue | Priority | 說明 |
|
||||||
|
|---|-------|:--:|------|
|
||||||
|
| 1 | TKG → PG | 🔴 | 核心資料缺失 |
|
||||||
|
| 2 | TMDB matching | 🔴 | Identity 無法用於 Portal 顯示 |
|
||||||
|
| 5 | Landmark mismatch | 🔴 | 資料無法用 |
|
||||||
|
| 4 | Missing chunk types | 🟡 | 影響功能完整度 |
|
||||||
|
| 3 | identity_bindings count | 🟡 | 資料不乾淨 |
|
||||||
|
| 6 | SQLite vec0 query | 🟢 | 可用第三方工具 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 已確認正常
|
||||||
|
|
||||||
|
| 項目 | 狀態 |
|
||||||
|
|------|:--:|
|
||||||
|
| PG copy 匯入 (6 tables) | ✅ |
|
||||||
|
| SQLite 結構 + 資料 | ✅ |
|
||||||
|
| Face coverage 100% | ✅ |
|
||||||
|
| Face traces 5483 | ✅ |
|
||||||
|
| Chunk 2407 sentences | ✅ |
|
||||||
|
| Speaker map 899 | ✅ |
|
||||||
|
| Video file 存在 | ✅ |
|
||||||
|
| API 功能 (37/39) | ✅ |
|
||||||
|
| Demo 21/21 | ✅ |
|
||||||
35
docs_v1.0/M4_workspace/2026-05-14_deploy_force_flag.md
Normal file
35
docs_v1.0/M4_workspace/2026-05-14_deploy_force_flag.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# deploy.sh — 新增 --force 參數
|
||||||
|
|
||||||
|
**Date**: 2026-05-14
|
||||||
|
**From**: M4
|
||||||
|
**To**: M5
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 變更
|
||||||
|
|
||||||
|
`deploy.sh` 加入 `--force` 參數,跳過覆寫確認提示。
|
||||||
|
|
||||||
|
## 使用方式
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 互動式(有舊資料時會詢問確認)
|
||||||
|
SCHEMA=public bash deploy.sh
|
||||||
|
|
||||||
|
# 強制覆蓋(不詢問,直接取代)
|
||||||
|
SCHEMA=public bash deploy.sh --force
|
||||||
|
```
|
||||||
|
|
||||||
|
## 行為
|
||||||
|
|
||||||
|
| 情境 | 無 --force | 有 --force |
|
||||||
|
|------|:--:|:--:|
|
||||||
|
| 無舊資料 | 直接部署 | 直接部署 |
|
||||||
|
| 有舊資料 | ⚠️ 顯示警告 + [y/N] 確認 | 🔧 跳過確認,直接取代 |
|
||||||
|
|
||||||
|
## 檔案變更
|
||||||
|
|
||||||
|
| 檔案 | 說明 |
|
||||||
|
|------|------|
|
||||||
|
| `deploy.sh` | L15-16: 新增 `FORCE` 變數 + `--force` 參數解析 |
|
||||||
|
| `deploy.sh` | L80-82: 確認區塊加入 `FORCE` 跳過邏輯 |
|
||||||
37
docs_v1.0/M4_workspace/2026-05-14_inference_down.md
Normal file
37
docs_v1.0/M4_workspace/2026-05-14_inference_down.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# 2026-05-14_inference_down.md
|
||||||
|
|
||||||
|
## 問題
|
||||||
|
|
||||||
|
生產環境 (3002) 的 inference engine (gemma4 LLM) 無法連線。
|
||||||
|
|
||||||
|
## 環境資訊
|
||||||
|
|
||||||
|
| 項目 | 值 |
|
||||||
|
|------|-----|
|
||||||
|
| API Server | localhost:3002 (production) |
|
||||||
|
| Schema | public |
|
||||||
|
| 時間 | 2026-05-14 15:50 +08:00 |
|
||||||
|
|
||||||
|
## 現狀
|
||||||
|
|
||||||
|
### 正常
|
||||||
|
- **Local embedding** (embeddinggemma-300m): ✅ 運行中 (port 11436, MPS)
|
||||||
|
- **Local embedding** (bge-m3-q8_0): ✅ 運行中 (port 8082)
|
||||||
|
- **Local embedding** (mxbai-embed): ✅ 運行中 (port 8083)
|
||||||
|
- **CoreML face embedding**: ✅ 運行中 (port 11435)
|
||||||
|
|
||||||
|
### 異常
|
||||||
|
- **M5 inference** (gemma4): ❌ 無法連線
|
||||||
|
- `http://192.168.110.201:8081` → 無回應
|
||||||
|
- `http://192.168.110.201:11434` → 無回應
|
||||||
|
|
||||||
|
### 影響
|
||||||
|
- `/api/v1/stats/inference` 回報兩個 engine 皆 error(但 health check hardcode 了 localhost,未反映 M5 真實端點)
|
||||||
|
- 生產環境 searchable_chunks: **4 / 15,367** — embedding/summarization pipeline 已停滯
|
||||||
|
- 8 筆影片 pending,無法完成處理
|
||||||
|
|
||||||
|
## 請求
|
||||||
|
|
||||||
|
1. 確認 M5 上的 gemma4 inference 服務狀態
|
||||||
|
2. 重啟 inference 服務(port 8081 或實際使用的端點)
|
||||||
|
3. 確認後請回覆 `2026-05-14_inference_down_response.md`
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# 165302 包部署回報 — 數量差異
|
||||||
|
|
||||||
|
**Date**: 2026-05-14
|
||||||
|
**From**: M4
|
||||||
|
**To**: M5
|
||||||
|
**Ref**: `2026-05-14_pipeline_v2_delivery.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 部署結果
|
||||||
|
|
||||||
|
包已成功部署到 `public` schema,aed7134 status = completed。
|
||||||
|
|
||||||
|
## 數量差異
|
||||||
|
|
||||||
|
| Metric | Delivery Doc 宣稱 | 實際 Import | 差異 |
|
||||||
|
|--------|:--------:|:--------:|:----:|
|
||||||
|
| Face traces | 9,825 | 5,483 | -4,342 |
|
||||||
|
| TKG edges | 370,310 | 30,987 | -339,323 |
|
||||||
|
| TKG nodes | 9,900 | 9,900 | ✅ 一致 |
|
||||||
|
| chunk | — | 2,407 | ✅ |
|
||||||
|
| faces | — | 70,691 | ✅ |
|
||||||
|
|
||||||
|
TKG edge CSV 只有 30,991 lines,文件大小 8.5MB。與 doc 的 370,310 差距巨大。
|
||||||
|
|
||||||
|
## 改善項目
|
||||||
|
|
||||||
|
TMDB 演員從 7 位增加到 **15 位**(新增 Walter Matthau、Thomas Chelimsky、Marc Arian、Claudine Berg、Marcel Bernier、Albert Daumergue、Raoul Delfosse)。
|
||||||
|
|
||||||
|
## 問題
|
||||||
|
|
||||||
|
1. `identities_name_key` unique constraint 造成 import 失敗(已手動 DROP 解決)
|
||||||
|
2. TKG edge 數量與 delivery doc 不符
|
||||||
|
3. Face trace 數量與 delivery doc 不符
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# schemafix binary — still querying dev
|
||||||
|
|
||||||
|
**Date**: 2026-05-14
|
||||||
|
**From**: M4
|
||||||
|
**To**: M5
|
||||||
|
**Priority**: High
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Issue
|
||||||
|
|
||||||
|
`momentry_v1.0.0_schemafix` 仍查詢 `dev.face_detections`,trace 9530 回 0。
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
| Test | Result |
|
||||||
|
|------|--------|
|
||||||
|
| chunk/0 (uses schema::table_name) | ✅ 200 |
|
||||||
|
| trace/9530/faces | total=0 (DB has 434) |
|
||||||
|
| trace sortby total_traces | 5,483 (DB has 9,825) |
|
||||||
|
| Binary build_git_hash | NOT in /health |
|
||||||
|
|
||||||
|
## Likely Cause
|
||||||
|
|
||||||
|
Binary built **before** commit `e8f44d7` (trace_agent_api schema fix). File timestamp is delivery time, not build time.
|
||||||
|
|
||||||
|
## Request
|
||||||
|
|
||||||
|
Rebuild binary from HEAD (includes `e8f44d7`), verify `/health` shows `build_git_hash`, re-deliver as separate file.
|
||||||
42
docs_v1.0/M4_workspace/2026-05-14_trace_schema_hardcode.md
Normal file
42
docs_v1.0/M4_workspace/2026-05-14_trace_schema_hardcode.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# trace_agent_api.rs — dev.face_detections hardcode
|
||||||
|
|
||||||
|
**Date**: 2026-05-14
|
||||||
|
**From**: M4
|
||||||
|
**To**: M5
|
||||||
|
**Priority**: High
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Issue
|
||||||
|
|
||||||
|
`src/api/trace_agent_api.rs` 硬編碼 `dev.face_detections`,無視 `DATABASE_SCHEMA` 環境變數。
|
||||||
|
|
||||||
|
## 影響
|
||||||
|
|
||||||
|
Production (3002, `DATABASE_SCHEMA=public`) 無法讀取新版 face trace 資料。
|
||||||
|
|
||||||
|
| DB has | API returns |
|
||||||
|
|--------|-------------|
|
||||||
|
| trace 9530 = 434 faces | trace 9530 = 0 |
|
||||||
|
| total_traces = 9,825 | total_traces = 5,483 (old data) |
|
||||||
|
|
||||||
|
API 永遠查 `dev.face_detections`,不查 `public.face_detections`。
|
||||||
|
|
||||||
|
## Affected Lines
|
||||||
|
|
||||||
|
| Line | Current | Should Be |
|
||||||
|
|------|---------|-----------|
|
||||||
|
| 101 | `FROM dev.face_detections` | `FROM {schema}.face_detections` |
|
||||||
|
| 110 | `FROM dev.face_detections` | `FROM {schema}.face_detections` |
|
||||||
|
| 146 | `FROM dev.face_detections` | `FROM {schema}.face_detections` |
|
||||||
|
| 253 | `FROM dev.face_detections` | `FROM {schema}.face_detections` |
|
||||||
|
| 271 | `FROM dev.face_detections` | `FROM {schema}.face_detections` |
|
||||||
|
|
||||||
|
## Fix
|
||||||
|
|
||||||
|
```rust
|
||||||
|
let table = schema::table_name("face_detections");
|
||||||
|
sqlx::query(&format!("SELECT ... FROM {} WHERE ...", table))
|
||||||
|
```
|
||||||
|
|
||||||
|
而非硬編碼 `"dev.face_detections"`。
|
||||||
919
docs_v1.0/REFERENCE/MARKBASE_DESIGN_V2.0.md
Normal file
919
docs_v1.0/REFERENCE/MARKBASE_DESIGN_V2.0.md
Normal 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 server(port 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 | SHA256(file_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.0(CC0/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) |
|
||||||
|
|
||||||
|
不使用外部 CLI(ditto、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 mode(SHA256 驗證,非 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 {
|
||||||
|
/// 節點唯一 ID(UUIDv4)
|
||||||
|
pub node_id: String,
|
||||||
|
|
||||||
|
/// 顯示名稱
|
||||||
|
pub label: String,
|
||||||
|
|
||||||
|
/// 多語言別名
|
||||||
|
pub aliases: Aliases,
|
||||||
|
|
||||||
|
/// 關聯的 file_uuid(Momentry Core 來源)
|
||||||
|
pub file_uuid: Option<String>,
|
||||||
|
|
||||||
|
/// 父節點 node_id(root 為 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 Schema(user.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`、`10–100MB`、`100MB–1GB`、`>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** | 🌡️ | 10–500ms | 中等 | 中 | 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 |
|
||||||
730
docs_v1.0/REFERENCE/MARKBASE_DESIGN_v1.0.0.md
Normal file
730
docs_v1.0/REFERENCE/MARKBASE_DESIGN_v1.0.0.md
Normal 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 native(Rust + axum + Tauri WebView)|
|
||||||
|
| 授權 | Momentry 專屬工具,隨 momentry_core 發布 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 命名
|
||||||
|
|
||||||
|
**MarkBase** — Markdown + Display Base
|
||||||
|
|
||||||
|
> 承載所有內容類型的顯示基底。
|
||||||
|
> 簡短、好記、產品感。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 階段規劃
|
||||||
|
|
||||||
|
### Phase 0:Demo Display(MVP — 立即價值)
|
||||||
|
|
||||||
|
**目標**:取代 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 2:Knowledge Base
|
||||||
|
|
||||||
|
**目標**:從閱讀器升級為個人知識庫管理器
|
||||||
|
|
||||||
|
| 功能 | 說明 |
|
||||||
|
|------|------|
|
||||||
|
| 多文件索引 | 監控目錄,自動索引所有 .md |
|
||||||
|
| 全文檢索 | 跨文件模糊搜尋 + 標題索引 |
|
||||||
|
| 標籤管理 | YAML frontmatter tags → 標籤雲 |
|
||||||
|
| Backlinks | 文件間的雙向連結([[wiki-link]])|
|
||||||
|
| 收藏/書籤 | 標記常用文件 |
|
||||||
|
| 閱讀歷史 | 最近開啟 / 最近搜尋 |
|
||||||
|
|
||||||
|
### Phase 3:Collaboration
|
||||||
|
|
||||||
|
**目標**:多人協作與發布
|
||||||
|
|
||||||
|
| 功能 | 說明 |
|
||||||
|
|------|------|
|
||||||
|
| 評論/註釋 | 段落層級註解 |
|
||||||
|
| 版本歷史 | 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 控制 API(display 模式下啟用)
|
||||||
|
|
||||||
|
`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-hop:filter speaker = "Cary Grant"
|
||||||
|
{"filter": {"must": [{"key": "speaker", "match": {"value": "Cary Grant"}}]}}
|
||||||
|
|
||||||
|
// ❌ 2-hop:graph 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 | 評論、版本、靜態站點 |
|
||||||
111
docs_v1.0/REFERENCE/RELEASE_NOTES_v1.0.0.md
Normal file
111
docs_v1.0/REFERENCE/RELEASE_NOTES_v1.0.0.md
Normal 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) |
|
||||||
280
docs_v1.0/REFERENCE/RELEASE_SOP_V2.0.md
Normal file
280
docs_v1.0/REFERENCE/RELEASE_SOP_V2.0.md
Normal 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
|
||||||
418
docs_v1.0/REFERENCE/file_uuid_spec.md
Normal file
418
docs_v1.0/REFERENCE/file_uuid_spec.md
Normal 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
|
||||||
Reference in New Issue
Block a user