Actions
이슈 #433
진행중
JL
HP
VfsService 포팅 — per-account 가상 파일시스템 (Read/Write/List/Mkdir/Delete/Stat/Exists/Lock/Unlock)
이슈 #433:
VfsService 포팅 — per-account 가상 파일시스템 (Read/Write/List/Mkdir/Delete/Stat/Exists/Lock/Unlock)
시작일:
2026/05/11
완료일:
진척도:
0%
추정시간:
설명
배경¶
alphea-ai-edge (Rust) 에 legacy VfsService 9 RPC 를 포팅한다. legacy 구현은 wallet-keyed vfs_nodes 테이블 + in-memory MemLocker 로 단순한 per-account 가상 파일시스템을 제공.
레거시 위치:
- proto:
~/Works/alphea-rpc-next/alphea-rpc/proto/alphea/v1/vfs.proto(9 RPC) - handler:
~/Works/alphea-rpc-next/alphea-rpc/internal/handler/stubs.go:51-330(~280 LoC) - store:
~/Works/alphea-rpc-next/alphea-rpc/internal/store/vfs.go(231 LoC) - domain locker:
~/Works/alphea-rpc-next/alphea-rpc/internal/domain/vfs/{locker.go,memlocker.go}(123 LoC)
목표¶
- 9 RPC 모두 작동:
Read,Write,List,Mkdir,Delete,Stat,Exists,Lock,Unlock. - legacy 와 기능 동등하지만 account-scope (user_id UUID) 로 전환 — wallet_address 키는 사용 안 함 (#421 정합성).
- in-memory locker 백그라운드 evictor 포함.
-
/modules,/sysread-only 경로 보호. - 처음 접근 시 default tree 자동 초기화 (
/home/node/{jobs,logs,workspace},/modules/demo.*,/sys/version등).
Proto 처리¶
proto/alphea/v1/vfs.proto 를 legacy 에서 복사하되:
-
option go_package라인 제거 (CLAUDE.md 규칙). - 기존 필드명
wallet_address는 wire 호환성을 위해 그대로 유지. 핸들러는 이 필드를 무시하고 JWTcaller_user_id를 사용 (silent override) — 향후 정리 시 별도 이슈에서user_id로 rename. -
build.rs에 vfs.proto 추가.
DB 스키마 (#254 refs)¶
신규 마이그레이션 migrations/0014_vfs_nodes.sql:
CREATE TABLE vfs_nodes (
id BIGSERIAL PRIMARY KEY,
user_id UUID NOT NULL,
path TEXT NOT NULL,
name TEXT NOT NULL,
node_type TEXT NOT NULL, -- 'file' | 'directory'
content TEXT,
mime_type TEXT,
size BIGINT NOT NULL DEFAULT 0,
readonly BOOLEAN NOT NULL DEFAULT FALSE,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
modified_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (user_id, path)
);
CREATE INDEX idx_vfs_user_path ON vfs_nodes (user_id, path);
(legacy 의 BIGINT unix-seconds 타임스탬프 → Rust 측 TIMESTAMPTZ 통일. proto wire 에서는 .timestamp() 로 변환.)
인증¶
-
auth_middleware::public_path_set()에 VfsService RPC 추가하지 않음 — 모두 protected. - 핸들러 본문에서
require_user_token(&request)?로 GoTrue user UUID 가져와user_id로 사용. - proto 의
wallet_address필드는 받아서 무시 (deprecated). - node 토큰은 거부 — VFS 는 사용자 계정 스코프 자원.
Lock 의미¶
-
holder_id는 클라이언트가 정하는 자유 문자열 (legacy 와 동일 — node_id, session id, 임의 UUID 모두 가능). - TTL 기본 60초, 커스텀 TTL 지정 가능.
- 동일 holder 의 재 lock 은 TTL 연장.
-
Write/Delete핸들러는 metadatax-holder-id(gRPC 소문자 헤더) 로 lock ownership 검증 — holder 가 다른 사람의 lock 키와 매치되면aborted반환.
파일 추가¶
-
proto/alphea/v1/vfs.proto(copy + go_package 제거) migrations/0014_vfs_nodes.sql-
src/store/vfs.rs—VfsRow,get_node,list_children,write,mkdir,delete,exists,ensure_init -
src/domain/vfs/mod.rs+src/domain/vfs/locker.rs—MemLocker(tokio::sync::Mutex<HashMap>기반, evictor task) -
src/handler/vfs.rs— 9 RPC
파일 수정¶
-
build.rs— proto 목록에 vfs.proto 추가 -
src/handler/mod.rs—VfsHandler노출 -
src/server/mod.rs—AppState.vfs_locker: Arc<MemLocker>추가, evictor spawn, gRPC 서비스 등록 -
src/main.rs— AppState 구성 -
src/store/mod.rs—pub mod vfs; -
src/domain/mod.rs—pub mod vfs;
완료 기준 (DoD)¶
- 9 RPC 모두 구현 (unimplemented stub 없음).
-
cargo buildclean. -
cargo clippy --lib신규 코드 경고 0. -
cargo test --lib전부 통과 + 신규 단위 테스트:-
vfs::locker::*(acquire / re-acquire same holder / blocked by other / TTL expiry / unlock). -
vfs::store::*(path 정규화 / list_children direct-child filter — 가능하면).
-
- CLAUDE.md 의 "Current Implementation Snapshot" 표에 VfsService 추가, "5 미포팅 서비스" 목록에서 제거.
관련 이슈¶
- #254 — DB 스키마 (마이그레이션 추가 시 refs).
- #421 — account-scope 전환. 본 이슈가 그 정책 위에서 동작.
-
#375 — JWT 전역 인터셉터.
require_user_token사용.
예상 작업량¶
- proto + migration + store: 1.5h
- locker + evictor: 1h
- handler 9 RPC: 2h
- 테스트 + wiring + CLAUDE.md: 1.5h
- 총: 6h
JL James Lee이(가) 6달 전에 변경
- 상태을(를) 신규에서 해결(으)로 변경되었습니다.
VfsService 포팅 완료¶
commit:server|fed4ea0
9 RPC 모두 구현, account-scope (user_id) 로 전환, in-memory locker + 백그라운드 evictor, default tree seed, read-only 경로 보호 — 단일 commit 으로 마무리.
변경¶
-
proto/alphea/v1/vfs.proto(신규) — legacy 에서 복사,option go_package제거.wallet_address필드는 wire 호환을 위해 유지하되 핸들러는 무시 (silent override). 향후 정리 시 별도 이슈에서user_id로 rename 가능. -
migrations/0014_vfs_nodes.sql(신규) —vfs_nodes테이블,(user_id, path)UNIQUE +(user_id, path)인덱스. legacy 의 BIGINT unix-seconds 대신 TIMESTAMPTZ 통일. -
src/store/vfs.rs(신규) —VfsRow+get_node/list_children/write/mkdir/delete/exists/ensure_init.is_direct_child헬퍼로 legacy directory listing 시맨틱 동등.ensure_init가 16개 default entry seed (/home/node/{jobs,logs,workspace},/modules/demo.*,/data/sample.json,/sys/{version,profile}등) 를 트랜잭션으로 처리. -
src/domain/vfs/mod.rs+locker.rs(신규) —Lockertrait (object-safe,LockFuture<'a, T>type alias 로 Pin<Box> 복잡도 감춤) +MemLocker(tokio::sync::Mutex<HashMap>). 10초 주기 백그라운드 evictor, 동일 holder 재-lock 시 TTL 연장, zero-TTL 입력 시 60s 기본값. -
src/handler/vfs.rs(신규) — 9 RPC.- 모든 RPC
require_user_token(node token 거부). -
Write/Mkdir/Delete는/modules,/sys와 그 하위 거부 (permission_denied). -
Write/Delete는 metadatax-holder-id가 있을 때만 lock 소유 검증 — 다른 holder 가 lock 보유 중이면aborted. -
Lock/Unlock은holder_id필수.
- 모든 RPC
-
build.rs— proto 목록에 vfs.proto 추가. -
wiring —
src/handler/mod.rs(VfsHandlerexport),src/store/mod.rs+src/domain/mod.rs(pub mod vfs;),src/server/mod.rs(AppState.vfs_locker+spawn_evictor+VfsServiceServer등록),src/main.rs(동일 wiring). -
CLAUDE.md— "Current Implementation Snapshot" 표에 VfsService 행 추가,vfs_nodes를 마이그레이션된 테이블 목록으로 이동, project structure 의domain/코멘트 갱신.
검증¶
-
cargo buildclean. -
cargo clippy --lib신규 코드 경고 0 (사전 admin 1건 무관). -
cargo test --lib103/103 통과 (9건 신규):-
vfs::store::direct_child_of_root,direct_child_of_subdir -
vfs::locker::acquire_then_unlock,same_holder_extends_ttl,other_holder_blocked,expired_lock_is_reacquirable,unlock_wrong_holder_fails,evict_expired_drops_overdue_entries,zero_ttl_uses_default
-
DoD 충족¶
- ✅ 9 RPC 모두 구현 (unimplemented stub 없음)
- ✅
cargo buildclean - ✅
cargo clippy --lib신규 경고 0 - ✅
cargo test --lib전부 통과 + locker 7건 + store 2건 단위 테스트 - ✅ CLAUDE.md "Current Implementation Snapshot" 표에 VfsService 추가, 미포팅 서비스 목록에서 제거 (← 이번 commit 으로 7/11 서비스 완료)
→ 상태를 해결로 전환.
CP Chris Park이(가) 6달 전에 변경
- 담당자을(를) James Lee에서 Henry Park(으)로 변경되었습니다.
Actions