Files
mnote/design/12-vault/process/12-1-password-vault-dedicated-crud-workbench-v1.md
T
Agent Board 262e66b02e feat: purge legacy agent hosts and land vault Chrome extension path
Retire ACP/Hermes/OpenCode surfaces and rename hermes_tools to
mnote_agent_tools so Page AI stays on Pi Lab only. Add Chrome vault
extension + extension token route, pre-release purge design, and soft-retire
legacy smokes for the small-group production cut.
2026-07-25 14:25:37 +08:00

57 KiB
Raw Blame History

12-1 [process] 密码箱(Password Vault)专用 CRUD 工作台整体设计 + Checklist v1

创建时间:2026-07-19
修订时间:2026-07-19+ 密文簿 cipher-book、folderPath 树分组、同站多账号;review 修订:path deny 合入门、软删合同、secret PATCH、刷新模型、PR 重排;re-reviewresource/read 等表面、P0-S/E 门闩、hint 仅 reject;补全 live /api/local-folder/* 路由盘点表)
状态:PROCESS
Owner12-vault(跨 shell / AI / 安全的 系统空间产品不是 04-tree-domain 树节点能力,也非硬塞 07-ai
建议 repo 落点:design/12-vault/process/12-1-password-vault-dedicated-crud-workbench-v1.md

上位依据:

  • /mnt/Data1T/mnote/ARCHITECTURE.md
  • /mnt/Data1T/mnote/AGENTS.md
  • design/04-tree-domain/done/4-37-trash-as-modal-workbench-v1.mdworkbench 数据/API 组织参考;IA 不同:密码箱要独立 route)
  • design/07-ai/process/7-18-local-first-agent-file-editing-control-plane-v1.mdAiAccessScope / allowed roots / 禁止裸读敏感路径)
  • design/05-editor-mainline/done/5-34-local-markdown-attachment-ref-contract-v1.md(附件引用语义 启发vault 附件有独立 owner,见 §2.5)
  • design/04-tree-domain/done/4-48-local-folder-markdown-resource-lifecycle-contract-v1.md
  • design/10-review/process/21-mvp-post-architecture-closure-checklist-v1.md

Overview

MNote 需要一等公民的 密码箱(Password Vault:按当前用户当前 workspace 解析的稳定系统目录,提供 专用 CRUD 工作台页面(完整列表 / 搜索 / 筛选 / 新建 / 编辑 / 删除 / 复制字段 / 显示隐藏密码),而不是普通 Markdown 笔记页,也不是仅靠 FileTree 打开自由 .md

本设计将 vault 定为 local-first 系统空间{workspaceRoot}/.mnote/vault/,条目为带固定 frontmatter 模板的结构化 Markdownmnote.kind: credential)。

P0 实现 owner(对齐 trash 先例,避免「Kernel」过称)

P0 落点 说明
存储 + HTTP API + SSR + browser mnote-web 与 local trash 相同:系统空间 API + projection JSON不是 tree.* 图节点
协议类型(可选) core-protocol 最小 struct 仅 DTO / schema 常量;注册 bridge graph 节点
bridge-runtime command 总线 P0 不做 P1 再评估是否挂 vault.* 到 bridgeP0 以 /api/vault/* 为唯一写入口

前端:/vault 独立 route + SSR workbench + browser runtime。
任何 通用文件面(/api/local-folder/files/open|stat/api/local-folder/resource/read|writeassets/upload、upload/write、Pi mnote.local_file.* / resolve_file_path)必须 server-side deny vault 路径——在首次可写入 secret 的 PR merge 之前 落地(见 §5.2、PR Plan);优先挂 shared resolve choke
AI 不得用通用 file 工具扫 vault,只能走分层 mnote.vault.*P1)。LightRAG / local search 排除 .mnote/vault/**。一期不做密码学保险箱;安全分级 L0/L1/L2。


Background & Motivation

当前状态与痛点

  1. 非正式密码笔记(用户工作区观察,非仓库事实):部分用户工作区存在 个人/密码/ 下大量自由 Markdown 目录条目(用户环境曾观察到约 172 条量级),正文常含明文密码/token。无固定 schema、无统一入口、无 reveal 分级,且 仍可被 FileTree / 本地搜索 / LightRAG 索引——即使正式 vault 上线,在导入归档前该目录仍是泄露面(见 §7.4)。
  2. 左栏存在可改造入口layout.rs quick-actions 中 inventory_2 链到 /files。代码核实:/files 当前无注册 route(死链),适合改造为「密码箱」固定入口。
  3. 垃圾箱模式可借鉴但 IA 不同4-37 是 modal workbench;用户明确要求密码箱是 专门页面。可复用 trash 的数据层:系统目录 ensure、index、SSR workbench、rootUri 鉴权。
  4. AI / HTTP 安全缺口(已核实)
    • open_local_file / stat_local_file/api/local-folder/files/open|stat)在 workspace grant + path-escape 下可读 root 内 任意 相对路径, .mnote / vault deny。
    • read_local_resourceGET /api/local-folder/resource/read)经 resolve_local_file_open_path 返回 JSON "text" 全文,同等无 vault deny——比 open 更易被编辑器链路误用
    • write_local_resource / assets/uploadtargetRelativePath)可写任意相对路径。
    • Pi local_file_read / resolve_file_pathpage_ai_pi/runtime.rs)在 allowed_roots 内可读任意文件, vault 排除。
    • 因此仅做 JS intercept 不够;必须 server deny 清单 + shared resolve choke(§5.2)。
  5. 架构约束local-first、禁止 UI 拼第二套真相、浏览器主链禁止新增 setInterval、RAG 必须排除 vault。

代码锚点(已核实)

锚点 路径 / 符号 含义
左栏 quick-actions ssr/pages/layout.rs L145151 inventory_2/files(死链);改造目标
ensure local_folder_source.rs ensure_default_workspace_directories .mnote/trash
树忽略 .mnote should_ignore_entry FileTree 不可见 vault
open/stat open_local_file / stat_local_file 必须加 vault denystat resolve_local_open_path
resource read/write read_local_resource / write_local_resource `GET/POST .../resource/read
assets upload upload_local_markdown_asset / write_local_folder_file_upload POST .../assets/uploadtargetRelativePath 禁 vault
path resolve choke resolve_local_open_path / resolve_local_file_open_path open + resource 共用;PR-S 优先挂此
Watcher local_folder_watcher_registry.rs 路径分量含 .mnote 时不按普通树/索引消费;不能假设外部改 vault 会推产品事件
Trash SSR gateway.rs trash_entry 系统空间页先例
AiAccessScope core-protocol/src/ai.rs allowed_roots / allowed_file_paths
Pi tools page_ai_pi/runtime.rs PiLabToolDefinition + resolve_file_path / `local_file_read
RAG / search knowledge_rag.rs / local_search_index.rs 已跳过 .mnote;需 vault 单测锁死
DirectoryGrant control-plane capabilities_json 现有 ad-hoc "ai"/"share"vault.* P1 再落 grant 写入路径
Attachment 启发 design 5-34 页面 md 附件合同;vault 附件为 平行模型

Goals & Non-Goals

Goals

  1. 专用页面 /vault:完整 CRUD(列表 + 详情/表单),非 leptos-tiptap。
  2. 左栏固定入口「密码箱」:改造死链 /files,按当前用户 + workspace rootUri 解析。
  3. 固定 credential 模板:核心 title/url/username/password;可选 email/apikey/token/notes/tagscookies 等走附件(P1+)。
  4. 存储:{workspaceRoot}/.mnote/vault/frontmatter + mnote.kind: credential
  5. mnote-web 系统空间 APIvault.ensure|list|get|create|update|delete|restore|purge|revealP0)与稳定 projection JSONP1 增加 resolve / AI tools。
  6. 安全分级 L0/L1/L2通用文件面 path deny 为 secret 写入的硬前置
  7. RAG / local search / FileTree 默认不可见、不可索引 vault。
  8. 可选导入 个人/密码/;不静默覆盖;双轨期风险明示。
  9. AIP0 仅 deny 通用读;P1 mnote.vault.*P2 录入/自注册/cookies。
  10. Checklist + PR Plan 可直接开工。

Non-Goals

  • 一期 不做 at-rest 加密保险箱。
  • 不做跨 workspace 云端 vault 同步。
  • 不把 vault 做成 FileTree / Page Tree 节点或 tree.* 命令对象。
  • 不把 vault CRUD 塞进 tiptap / Page Aggregate。
  • 不做 OS 级 / 浏览器原生级 自动填表 密码管理器(autofill 引擎非本设计目标)。
  • Chrome「保存到密码箱」扩展(登录/注册页确认后写入 vault)见独立稿:design/12-vault/process/12-3-chrome-extension-vault-save-v1.md12-3);与本 Non-Goal 不冲突——12-3 P0 仅保存、不做 autofill。
  • P0 不做 外部进程改 vault 文件后的 live FS 刷新(见 §4.2);禁止 setInterval 伪装。
  • 不把 vault 写入 LightRAG / evidence / 全局搜索 snippet。
  • 声称导入前 个人/密码/ 已与 vault 同等安全。

Proposed Design

1. 产品 IA

1.1 入口

左栏 quick-actions
  [搜索] [关系图] [导航] [帮助] [密码箱] [打开本地文件夹] [更多]
                              ↑
                    原 inventory_2 → /files(死链)
                    改为 lock 图标 + 「密码箱」
                    href="/vault?sourceKind=local_folder&rootUri=..."
  • 主入口layout.rs/files+inventory_2 改为 /vault + lock(默认;Open Q 可改 key),class:active={current_nav == "vault"}
  • hrefbrowser 注入当前 sourceKind + rootUri(读 data-mnote-root-uri 等)。无 root → empty state。
  • bottom_entriesworkspace_shell 的 templates/inventory_2 改 vault。
  • 禁止:通用 open 读 vault 文件(server deny);UI 若发现 vault 路径 → 引导 /vault?id= 或 403。

1.2 路由

Route 方法 说明
GET /vault SSR 全页 工作台;querysourceKindrootUri、可选 idimport=1status=active|deleted
/files 兼容 302 → /vault(保留 query 中 root 相关参数若可解析)

1.3 页面布局

┌─────────────────────────────────────────────────────────────┐
│ PageLayout                         topbar: 密码箱            │
│ ┌──────────┬──────────────────────────────────────────────┐ │
│ │ Sidebar  │  Vault Workbench                             │ │
│ │          │  toolbar: 搜索 | 标签 | 新建 | 导入 | 刷新   │ │
│ │          │  tabs: [活跃条目] [回收站]                    │ │
│ │          │  list (40%)  |  detail/form (60%)            │ │
│ └──────────┴──────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
能力 行为
列表 vault.list L0 projection
搜索 客户端过滤 L0;不把 password 写入任何浏览器持久索引
新建/编辑 create / get(mask) / update(§3.5 secret PATCH 语义)
删除 确认 → soft-deleteP0,§2.6
回收站 tab list status=deletedrestore / purgeP0
复制 / 显示密码 copy 或 reveal;默认掩码;不写 localStorage
刷新 (1) command 成功后 local patch / re-fetch(2) 工具栏「刷新」按钮。禁止 setInterval。依赖现有 local folder watcher 推 vault 变更

1.4 P0 UI 验收(最小)

  • ?id=cred_… deep-linkSSR/bootstrap 后选中对应行并加载详情。
  • 密码区默认掩码;未点「显示」前 DOM/HTML 无 password 明文。
  • dirty 表单点「取消」或切换条目:未保存则 confirm discard。
  • 必填:title 为空时保存 disabled + 行内错误;建议引导填写 url/username/password 至少一项。
  • 标签:逗号/Enter 添加 chip;删除 chip 即改 tags。
  • 回收站 tab 可见已删条目;restore 回活跃列表;purge 二次确认后磁盘无文件。
  • 窄屏:P0 可接受 list/detail 纵向堆叠(非阻塞);键盘快捷键非 P0。

Empty states:无 root / 0 条目(新建+导入提示,并 警告 源目录 个人/密码/ 若存在仍可能被搜索/RAG 索引)/ 无权限。


2. 数据模型

2.1 目录布局

{workspaceRoot}/
  .mnote/
    vault/                          # 系统 ensure
      vault-index.json              # L0 索引(含 status
      audit.jsonl                   # P0 唯一审计 sinkappend-only
      entries/{credentialId}.md
      attachments/{credentialId}/cookies-{stableId}.txt
      trash/
        entries/
        attachments/

ensure(扩展 ensure_default_workspace_directories + vault.ensure):

.mnote/trash
.mnote/vault
.mnote/vault/entries
.mnote/vault/attachments
.mnote/vault/trash
.mnote/vault/trash/entries
.mnote/vault/trash/attachments

访问 /vault 或任意 vault 写 API 前 ensure;失败返回明确错误。

2.2 Credential schema(磁盘 + 类型)

磁盘 frontmatter 示例relativePath 一律相对 vault root,禁止 ..):

---
mnote.kind: credential
schema: mnote.vault.credential.v1
id: cred_01HZX...
title: GitHub — work
url: https://github.com/login
username: alice
# 示例为模板字面量(非真实密钥):Li@[A]s3cret
password: <example-template>
passwordHint: "Li@[A]***"
email: alice@example.com
apikey: ""
token: ""
# 站内多账号(折叠组);顶层 username/password 为 accounts[0] 镜像
accounts:
  - id: acc_...
    label: work
    username: alice
    email: alice@example.com
    password: <example-template>
    passwordHint: "Li@[A]***"
# 登录态(Cookie)不写在本 frontmatter 新路径:
# 见 sessions/{id}/{accountId}.json12-3 §5.6);
# 旧字段 loginSession 仅兼容读。
# 附录密钥(API Key / Token);顶层 apikey/token 为首个同类镜像
secrets:
  - id: sec_...
    kind: apikey
    label: personal-token
    value: ""
tags: [work, github]
fields:
  otp_issuer: GitHub
attachments:
  - refId: att_cookies_1
    kind: cookies
    relativePath: attachments/cred_01HZX.../cookies-a1b2.txt
    label: login-cookies
createdAt: 2026-07-19T08:00:00Z
updatedAt: 2026-07-19T09:30:00Z
createdBy: user_xxx
updatedBy: user_xxx
---

## Notes

登录备注等自由文本(P0 按 L0 返回;勿把真密码只放 notes)。

磁盘 secret 字段password / apikey / token明文 string(一期)。

API 投影 SecretFieldStatewire

// 仅出现在 API 响应,永不作为磁盘存储类型
type SecretFieldState =
  | { state: "absent" }           // 磁盘无值或空
  | { state: "masked" }           // 有值但不返回明文
  | { state: "revealed"; value: string; expiresAt?: string }; // 仅 reveal/resolve 短时

存储侧 Rustmnote-web vault_storeP0

pub struct VaultCredentialRecord {
    pub id: String,
    pub title: String,
    pub url: Option<String>,
    pub username: Option<String>,
    pub password: Option<String>,       // 磁盘明文;序列化到 API 时变 SecretFieldState
    pub password_hint: Option<String>,  // L0 可展示;禁止等于 password
    pub email: Option<String>,
    pub apikey: Option<String>,
    pub token: Option<String>,
    pub tags: Vec<String>,
    /// P0: **仅非密钥元数据**(如 otp_issuer、environment)。
    /// 禁止放入 recovery_code / otp_secret 等;此类应使用 token/apikey 或后续 secretFields。
    pub fields: BTreeMap<String, String>,
    pub attachments: Vec<VaultAttachmentRef>,
    pub notes_markdown: String,
    pub status: VaultItemStatus, // active | deleted
    pub created_at: String,
    pub updated_at: String,
    pub relative_path: String, // entries/{id}.md 或 trash/entries/{id}.md
}

pub struct VaultAttachmentRef {
    pub ref_id: String,
    pub kind: String, // cookies | session | other
    pub relative_path: String, // vault-root-relativenormalize 后不得含 ..
    pub label: String,
    pub content_type: Option<String>,
}

fields P0 合同

  • 仅 L0 非密钥元数据。
  • 服务端 create/update:若 key 命中拒绝列表(password/token/apikey/secret/otp_secret/recovery 等,大小写不敏感)→ 400 vault_fields_secret_forbidden,提示改用标准 secret 字段。
  • 不提供对 fields 值的 reveal/resolve;避免旁路。

passwordHint

  • L0;可进 list index。
  • create/update:若 passwordHintpassword 在 trim 后相等 → 一律 400 vault_hint_equals_secret
    禁止 auto-clear / warn-and-continue 分支(P0 无第二路径)。

必填

  • title 非空。
  • create:建议至少 url|username|password 之一;UI 引导,服务端可 warning 但仍允许仅 title(产品可收紧)。

2.3 vault-index.json

{
  "schema": "mnote.vault.index.v1",
  "revision": 12,
  "updatedAt": "2026-07-19T09:30:00Z",
  "entries": {
    "cred_01HZX...": {
      "id": "cred_01HZX...",
      "title": "GitHub — work",
      "url": "https://github.com/login",
      "username": "alice",
      "email": "alice@example.com",
      "passwordHint": "Li@[A]***",
      "tags": ["work", "github"],
      "hasPassword": true,
      "hasApikey": false,
      "hasToken": false,
      "attachmentCount": 1,
      "relativePath": "entries/cred_01HZX....md",
      "updatedAt": "2026-07-19T09:30:00Z",
      "status": "active"
    }
  }
}

规则:

  • create/update/delete/restore/purge 原子更新 indextemp + rename)。
  • index 永不存 password/apikey/token 明文。
  • Rebuildindex 损坏或不一致时):
    1. 扫描 entries/*.mdtrash/entries/*.md
    2. L0-only allowlist 解析:只抽取 id/title/url/username/email/passwordHint/tags/has* 标志/attachments 计数/status丢弃 password/apikey/token 键,不得进入 debug map。
    3. 解析错误日志只含 credentialId / 相对路径 / error code禁止 frontmatter 原文或 secret 子串。
    4. 运维:删除损坏 index 后调用 POST /api/vault/ensurePOST /api/vault/reindexP0 可合并进 list 自动 rebuild)→ 重写 index。

2.4 密码槽展示约定

约定
存储 password 完整可用字符串(明文)
L0 提示 可选 passwordHint(不可逆示意,≠ 真密码)
UI 默认 ••••••••reveal 全显;复制走 reveal 或 copy-after-reveal
未来 加密字段延期

页眉文案:本地结构化密码箱,非加密保险箱

2.5 附件(cookies / session)— P1 起完整交付

  • 模型:受 5-34 启发,但 owner 是 credential,不是页面 .md;不是同一 AttachmentRef 解析链。
  • 路径relativePath 仅 vault-root-relative,如 attachments/{credentialId}/cookies-{id}.txtreject .. 与绝对路径。
  • 下载GET /api/vault/attachments/{credentialId}/{refId}?rootUri=(需 vault.view);禁止/api/local-folder/files/open
  • 上传POST /api/vault/attachments 或 command vault.attachment.putvault.edit)。
  • ensure 不要求预建每个 credential 子目录;put 时 create。

2.6 删除策略 — P0 冻结:软删除(方案 B

操作 P0 行为
vault.delete 条目 md + 其 attachments 移动.mnote/vault/trash/...index status: deleted
vault.list?status=active(默认) 仅 active
vault.list?status=deleted 回收站
vault.restore 移回 entries/ / attachments/status active
vault.purge 硬删 trash 内文件;index 移除键
全局 .mnote/trash 不使用

P0 UI 必须有回收站 tab(非「以后再说」)。
不做 P0 硬删作为默认 delete(避免与 K6 及回收体验不一致)。


3. mnote-web API(非 tree kernel

3.1 命令/查询清单

名称 HTTP 权限(P0 矩阵 §5.1 阶段
vault.ensure POST /api/vault/ensure workspace 可读+可建目录 P0
vault.reindex POST /api/vault/reindex view P0
vault.list GET /api/vault/list view P0
vault.get GET /api/vault/items/{id} view P0
vault.create POST /api/vault/items edit P0
vault.update PATCH /api/vault/items/{id} edit P0
vault.delete DELETE /api/vault/items/{id} edit P0 soft
vault.restore POST /api/vault/items/{id}/restore edit P0
vault.purge POST /api/vault/items/{id}/purge edit P0
vault.reveal POST /api/vault/items/{id}/reveal view + 登录会话 P0
vault.cipherBook.list GET /api/vault/cipher-book view P0
vault.cipherBook.put PUT /api/vault/cipher-book/{key} edit P0
vault.cipherBook.delete DELETE /api/vault/cipher-book/{key} edit P0
vault.cipherBook.reveal POST /api/vault/cipher-book/{key}/reveal view + 登录会话 P0
vault.resolve POST /api/vault/items/{id}/resolve resolve capability P1
vault.import.preview/apply POST /api/vault/import/* edit M
vault.attachment.* 见下 view/edit P1P2
vault.export export P2

3.1a 密文簿 / 分组 / 同站多账号(P0 产品补充)

  1. 密文簿(cipher-book

    • 磁盘:{workspaceRoot}/.mnote/vault/cipher-book.jsonschema mnote.vault.cipherBook.v1)。
    • 密码 / apikey / token 可写模板,如 Li@[A]s3cret[Key] 中 Key 为 [A-Za-z0-9_]{1,32}
    • 存储:条目里保留模板原文;reveal 时展开为明文 value,并可选返回 template / usedCipherKeys / missingCipherKeys
    • L0 list 仅返回 keys + hasValue永不投影片段明文。
  2. 站内多账号 / 多密钥(推荐,取代「同站新账号」拆条)

    • 一条 credential = 一个站点/服务(左栏一条)。
    • accounts[]:每组可折叠包含 username / email / password / passwordHint+ 可选 label);UI 用 + 账号 新增组。
    • secrets[]:附录 API Key / Token / otherkind + label + value);UI 用 + 密钥 新增。
    • 顶层 username/password/apikey/token 仍保留为 primary 镜像(首账号密码、首个 apikey/token),供 AI resolve/login 兼容。
    • reveal 支持 accountId(密码)与 secretId(附录密钥)。
    • 不再提供详情「同站新账号」按钮(多账号应写在同一条目内)。
    • 兼容:同 URL 仍允许多条 credential(历史数据);列表 meta 展示 N 账号 / N 密钥
  3. 分组折叠(folderPath

    • 逻辑字段 folderPath(如 个人/银行/招商),不是 vault 下真实子目录。
    • 左栏按路径多级折叠;折叠状态可存 sessionStorage。
    • 条目文件仍平铺在 entries/{id}.md
    • 表单 UX(已落地):下拉选择已有 folderPath + 文本直接输入新路径(无弹窗、无空组、无「在此新建」);组随条目出现。
  4. 同站 URL 折叠(历史兼容)

    • 同一 folderPath 节点下,相同非空 url 且 ≥2 条 时,再包一层可折叠「站点组」。
    • 新录入应优先合并到 accounts[]/secrets[],避免同站拆多条。
  5. 共享到 AI 密码本

    • POST /api/vault/items/{id}/share-to-ai:把条目(含 secret 模板)复制/更新到目标 actor 的 managed 默认工作区 vault。
    • 默认目标 actormnote-e2eenv MNOTE_AI_VAULT_ACTOR 可改)。
    • 副本加 tag ai-sharedfolderPath 默认 from-{sourceActor}
    • 双向链接(磁盘 frontmatter:源 sharedToAi{targetActorId,targetId,sharedAt,lastSyncedAt};副本 sharedFrom{sourceActorId,sourceId,sharedAt}
    • 再次共享同一源:更新已有 AI 副本(不重复建条);按钮文案变为「同步到 AI 副本」。
    • 编辑自动同步PATCH 源条目默认 syncAiShare=true,若有 sharedToAi 则推送到 AI 副本(密文 key 同步规则同 share)。
    • AI 密码本识别list 返回 isAiVault / vaultRoleAI 本顶栏标识「AI 密码本」,隐藏「共享到 AI」;副本展示「AI 副本」横幅/标签。
    • 用户本上已共享条目:标题旁轻量 chip「已共享」;按钮「共享 AI / 同步 AI」+「撤销共享」。
    • 撤销共享POST /api/vault/items/{id}/unshare-from-ai:清除源 sharedToAiAI 副本 soft-delete(进回收站);密文 key 不删。幂等。
    • 密文簿同步(默认开)syncCipherKeys 默认 true
    • 密文簿 按 workspace 隔离;单本内 key 唯一。
    • 编辑 secret 明文;查看态掩码;空字段默认不展示。
    • Skillskills/mnote-vault + CAPABILITY_PACKS 注册 id mnote-vaulthermes/reasonix 可发现)。

3.2 HTTP 映射(完整 P0 + 附件骨架)

POST   /api/vault/ensure
POST   /api/vault/reindex
GET    /api/vault/list?rootUri=&sourceKind=local_folder&status=active|deleted
GET    /api/vault/items/{id}?rootUri=
POST   /api/vault/items
PATCH  /api/vault/items/{id}
DELETE /api/vault/items/{id}
POST   /api/vault/items/{id}/restore
POST   /api/vault/items/{id}/purge
POST   /api/vault/items/{id}/reveal     # body: { "field": "password"|"apikey"|"token" }; expands [Key]
POST   /api/vault/items/{id}/share-to-ai  # body: { rootUri, targetActorId? } → AI vault clone
POST   /api/vault/items/{id}/unshare-from-ai  # body: { rootUri, targetActorId? } → clear link + soft-delete AI copy
GET    /api/vault/cipher-book?rootUri=
PUT    /api/vault/cipher-book/{key}     # body: { rootUri, value }
DELETE /api/vault/cipher-book/{key}?rootUri=
POST   /api/vault/cipher-book/{key}/reveal
POST   /api/vault/items/{id}/resolve    # P1
POST   /api/vault/import/preview
POST   /api/vault/import/apply
GET    /api/vault/attachments/{credentialId}/{refId}   # P1
POST   /api/vault/attachments                            # P1
DELETE /api/vault/attachments/{credentialId}/{refId}     # P1

统一响应壳:{ ok, requestId, traceId, owner: "mnote-web", result }
错误码:vault_root_required / vault_forbidden / vault_item_not_found / vault_path_denied / vault_secret_denied / vault_fields_secret_forbidden / vault_hint_equals_secret / vault_revision_conflict / vault_mask_sentinel_rejected

3.3 Projection

mnote.vault.list.v1 / mnote.vault.item.v1secret 字段仅为 SecretFieldState(默认 masked/absent)。
mnote.vault.secret.v1reveal/resolve 用;access log 不得记录 value

3.4 实现落点(owner crate

落点
HTTP + storage mnote-web/src/routes/vault.rs + vault_store.rs(或同模块)
共享 path deny mnote-webvault_path.rsis_vault_sensitive_relative_path(rel) -> bool
调用方 open/stat/upload/write、page_ai_piresolve_file_path / local_file_read
SSR ssr/pages/vault.rs + gateway/page route
Browser browser/vault-workbench-runtime.js;在 layout.rs 与其它 workbench 一样加 script 标签,并由 mnote-ui-runtime.js import 注册(与 filetree/trash 模块同一模式)
core-protocol 可选 P0:仅 schema 字符串常量;不强制
bridge-runtime P0 不注册

原则:浏览器 禁止 直接写 vault 路径;唯一 writer = vault HTTP handlers。

3.5 Secret 字段 create / update 语义(强制

标准 secret 键password | apikey | token

PATCH vault.update bodycamelCase

客户端传入 服务端行为
字段 缺省JSON 键不存在) 保持磁盘原 secret
"password": null{ "password": { "clear": true } } 清空 secret
顶层 "password": ""(空字符串)或 { "set": "" } 清空 secret(与 clear 同义;禁止把空串当「未改」)
"password": { "set": "<new>" } 设为新明文(内部空格保留;仅拒纯 mask 哨兵)
"password": "••••" / "****" / "•"+ / "••••••••" 等掩码哨兵 400 vault_mask_sentinel_rejected,不写盘
顶层 "password": "plaintext"(非空裸字符串) 允许,语义同 { set }(便于表单);仍拒绝哨兵

仅改 title/tags 等 L0 字段body 不含 secret 键 → secrets 原样保留。单测:update title only keeps password

create

  • 可用裸字符串或 { set }
  • 缺省 secret → absent。
  • 禁止哨兵。

import apply:写入真实解析值;不经 mask 状态机。

乐观并发(P0 建议)

  • index / item 带 revisionupdate 可带 baseRevision;冲突 → 409 vault_revision_conflict(两 tab 编辑)。P0 最低:last-write-wins + UI 保存后 re-fetchchecklist 优先有 revision 更好。

4. 前端落点

4.1 模块地图

文件 职责
SSR ssr/pages/vault.rs VaultPagecurrent_nav="vault"
Page route gatewayroutes/vault_page.rs auth + rootUri + bootstrap list(无 secret
API routes/vault.rs CRUD + reveal + soft-delete
Path deny vault_path.rs + 挂入 open/stat/… §5.2
Styles ssr/styles 或 vault CSS workbench
Runtime browser/vault-workbench-runtime.js CRUD UI
入口 layout.rs 密码箱链接 + script 注入
拦截 resource-open 附加 UX 不能替代 server deny

4.2 刷新模型(修正原 watcher 误称)

来源 P0
vault API 成功 本地 patch list 或 re-fetch vault.list
工具栏「刷新」 re-fetch
现有 local folder watcher / WS tree events 用于 vault UI.mnote 变更本就不按产品树消费
外部编辑器改 vault 文件 非目标;用户按「刷新」。未来若做 vault.changed,只由 vault command 广播,永不把 secret body 送进 search 索引管线
setInterval 禁止

4.3 禁止

  • tiptap 表单、localStorage 存 password、UI 第二套真源(draft 仅编辑态)。

5. 安全分级与硬边界

5.0 L0 / L1 / L2

内容 通道
L0 title url username email tags passwordHint has* attachmentCount updatedAt notes(全文) fields(非密钥) list/get 默认
L1 password/apikey/token 明文 vault.reveal登录会话 + 审计宣称 OS 级用户手势/CSRF≠手势)
L2 同上供 AI vault.resolve;默认 deny(§5.1);瞬时;transcript 脱敏

5.1 P0 鉴权矩阵(冻结;capabilities_json 延后)

P0 解析 DirectoryGrant.capabilities 中的 vault.*(今日无写入路径与 enforcement helper)。映射:

主体 vault.view vault.edit vault.resolve vault.export
local_folder ownermanifest owner / 等价) P1 显式开)
directory grant write
directory grant read-only
share link read
anonymous

说明:

  • 协作方默认不可见 vault(含 read grant)。与「共享目录可读普通文件」分离:vault 是系统敏感空间。Open Q 若产品要 read grant ⇒ view,属 改矩阵 的产品决策,P0 实现按上表。
  • vault.resolve 默认 denyP1 在 AI policy / 显式设置中改为 askallow。L2 文案统一为 deny-by-default,不再写「生产建议 ask」作为默认。
  • revealauthenticated session + audit,非生物/OS 手势。

P1 capabilities:再在 grant / ai_policy 写入 vault.view|edit|resolve|export 并做 enforcement helper;不阻塞 P0。

5.2 通用路径 Deny — P0 合入门(全表面清单)

共享谓词(单一实现,禁止复制粘贴分叉):

/// rel: workspace-root-relative, `/` 分隔, 已 strip 前导 ./ , 拒绝 .. 段之后再判断
fn is_vault_sensitive_relative_path(rel: &str) -> bool {
    let n = normalize_rel(rel); // 大小写敏感 FS 按原样;比较用统一 trim
    n == ".mnote/vault" || n.starts_with(".mnote/vault/")
}

命中 → 403code vault_path_deniedbody 文件内容(resource/read 亦不得含 "text" 字段中的 secret)。

首选 choke pointPR-S 实现策略,减少漏挂)

共享 resolve 今日调用方(已核实) 动作
resolve_local_open_pathlocal_folder_source.rs open_local_file;经 resolve_local_file_open_pathread_local_resource / write_local_resource 在此调用 is_vault_sensitive_relative_path,一次覆盖 open + resource read/write
resolve_local_file_open_path 内部调 resolve_local_open_path 继承上列;勿再复制 matcher
Pi resolve_file_path / resolve_root_relative_pathpage_ai_pi/runtime.rs local_file_read / local_file_patch 在此 denyleaf tool 不各自拼规则

注意stat_local_file resolve_local_open_path(自建 canonical_root.join(path)),必须在 stat handler 显式 deny,不能假设 choke 自动覆盖。

P0 必须挂接的表面(实现前打勾;编号冻结)

# 表面 符号 / 路由(已核实) 动作
D1 GET /api/local-folder/files/open open_local_fileresolve_local_open_path denychoke
D2 GET /api/local-folder/files/stat stat_local_file独立 path join 显式 deny
D3 GET /api/local-folder/resource/read read_local_resourceresolve_local_file_open_path;返回 JSON text 全文 denychoke);合入门必测
D4 POST /api/local-folder/resource/write write_local_resourceresolve_local_file_open_path denychoke
D5 POST /api/local-folder/assets/upload upload_local_markdown_assetwrite_local_folder_file_upload / write_local_markdown_assetfiletree.folder.droptargetRelativePath 目标 rel 落 vault → deny
D6 其它 local 写路径:page body / mindmap / mkdir·rename·move·archive 若接受 root-relative 路径 至少:write_local_markdown_page_bodywrite_local_mindmap_datatree/local executor 中 rename/move/archive 入口 源或目标 vault → denyvault 只走 vault API
D7 mnote.local_file.read page_ai_pi local_file_read deny
D8 mnote.local_file.patch local_file_patch deny
D9 Pi path resolve resolve_file_path(及 root+rel→abs helper 敏感 rel → Err,不返回内容
D10 tree command / resource archive 指向 vault local tree executor / archive helpers deny 或明确 no-op 错误(若入口最终调 D1/D6 resolve,可标「由 shared resolve 覆盖」并单测一条)
D11 OnlyOffice / media 代理 onlyoffice.rsfileUrl/api/local-folder/files/open 由 D1 choke 覆盖;勿再开平行 open
D12 SSR / document local-md: document / gateway 解码 local-md: 相对路径后读盘 path 在 vault → 不渲染正文;可选 redirect /vault

Live /api/local-folder/* 路由盘点routes/mod.rs 已核实,避免 D 表再漏一等公民 content 面):

路由 Handler vault path deny
GET .../files/open open_local_file D1choke
GET .../files/stat stat_local_file D2(显式)
GET .../resource/read read_local_resource → JSON text 全文 D3choke,合入门)
POST .../resource/write write_local_resource D4choke
POST .../assets/upload upload_local_markdown_asset(含 filetree.folder.drop + targetRelativePath D5
GET .../events local-folder event bus / watcher SSE N/A content:事件不回文件正文;watcher 已滤 .mnote(§4.2 non-goal
POST .../shared-cache/record record_shared_cache N/A:写 .mnote/share-cache.json 元数据,不读任意 rel 正文
POST .../sync/pending-change record_sync_pending_change N/Async 元数据
POST .../sync/conflict-report write_sync_conflict_report N/A:报告写 .mnote/sync-reports
GET .../workspaces/default create_default_local_workspace N/Aworkspace bootstrap,无任意 path 读盘
ANY .../ocr/{jobs,status,read,insert,delete} retired_local_ocr_endpoint 已退役(指向 LightRAG);P0 新增 OCR vault 路径;若未来复活须走 is_vault_sensitive_relative_path
(非 local-folder 但相关)GET /api/tree/local-folder-watch legacy / 侧车 以 tree live 为准;作为 vault content read 面

结论:今日能回任意 workspace-relative 文件字节/全文 的 HTTP 面仅 open / stat / resource/read / resource/write / assets/upload——均已进 D1D5。其它 local-folder 路由不构成 secret 正文旁路。Pi / SSR / OnlyOffice / tree write 见 D6D12。

单元测试(每个 D1–D5、D7–D9 至少一条;D3 为合入门)

  • D1 open / D2 stat / D3 resource/read.mnote/vault/entries/x.md → 403 vault_path_denied;响应体无 fixture password、password: 样例串;D3 无 text 明文
  • D4 resource/write / D5 assets/upload 目标 vault → 403。
  • D7D9mnote.local_file.read / patch / resolve → deny,无 content。

原生 Pi / 非 MNote 文件工具残留风险

  • 强制:凡经 MNote tool bridge 的路径检查必须 deny。
  • 若 Pi 进程 cwd/sandbox 含 workspace root 且存在 绕过 MNote 的原生 read:属残留风险。P0 文档披露;缓解:Pi sandbox 不把 .mnote/vault 列入可写/可读 allowlist(若 runtime 支持 path deny 列表则配置);无法 100% 时在 AI 设置提示「请勿将 vault 目录加入 agent 原生工作区」。
  • 不把 vault 路径写入 AiAccessScope.allowed_file_paths / allowed_roots 的子路径 allow。

顺序PR-S(安全锁)在任意默认可写 secret 的 API 对非测试环境生效之前 merge(见 PR Plan)。Feature flag 默认 直至 P0-S 全绿(含 D1D5、D7D9;尤其 D3 resource/read)。

5.3 威胁模型(摘要)

威胁 严重度 缓解
HTTP open/stat 读 vault D1D2 + 测试合入门
HTTP resource/read 回全文 D3 + choke resolve_local_open_path
resource/write · assets/upload 写 vault D4D5
Agent local_file 读 vault D7D9
RAG 索引 vault exclude + 单测
list/get 回明文 mask + reveal 分端
日志打印 secret reveal/resolve/rebuild 错误路径禁 value
双轨 个人/密码/ §7.4 警告 + 可选 exclude
磁盘物理读取 高(接受) 非加密声明
掩码写回 sentinel reject
XSS 读 DOM 最短明文展示

6. AI 分阶段

P0

  • mnote.vault.* 工具暴露。
  • D7D9(及 HTTP D1D5,含 resource/readdeny 生效;AI / 通用 HTTP 读 vault 失败且无 body。

P1 — 多 agent 唯一策略(2026-07-20 冻结并落地)

策略名:单 AI 密码本 + 单 resolve 通道 + 多 agent 只消费 AI 本。

冻结
凭证池 仅 AI 密码本(MNOTE_AI_VAULT_ACTOR,默认 mnote-e2e
读密 mnote.vault.resolve / POST /api/vault/ai/items/{id}/resolve / CLI
禁止 通用 file / local_file / open / resource / RAG 读 .mnote/vault/**
用户 vault 人用 revealresolve 禁止vault_resolve_ai_only
长期 AI 本内条目即长期授权;撤权=删/软删 AI 本条目
快速 本机 HTTP/CLI,不经浏览器
策略 SSOT /home/lix/.agent-infra/vault-policy.md + $mnote-vault + Paseo appendSystemPrompt
  • 已落地list_ai / get_ai / resolve APIPi mnote.vault.list|get|resolve(默认 allowplan 模式 resolve deny);receipt 脱敏 valuescripts/mnote-vault-cli.js
  • transcriptagent 应使用 transcriptHint(已解析 field),不把 value 贴进聊天。
  • skillsskills/mnote-vault + 全局 symlink + capability pack。
  • 演进(读密 transport,未实现):策略语义不变;agent 读密改走本地 vaultd + capability token不依赖 mnote-web。见 design/12-vault/process/12-2-vaultd-local-token-agent-read-path-v1.md

P2

  • 协助录入确认流、attachment cookies、自注册回写、export;按 credential/run grant(可选)。
  • 浏览器侧录入主路径收敛到 12-3 Chrome 扩展;工作台内粘贴助手可后置或不做。

7. 迁移(个人/密码/

7.1 源形态

  • 惯例路径:{workspaceRoot}/个人/密码/**/*.md用户环境观察,数量因工作区而异)。
  • 自由 md;导入前 仍在 FileTree/RAG/search 范围内。

7.2 向导

preview → 用户勾选 → apply;冲突 skip/副本;禁止静默覆盖;默认不删源。

7.3 启发式

frontmatter 优先;否则中英关键行;title=文件名 stem;低置信人工确认 password。

7.4 双轨残留风险(正式产品态度)

正式 vault 不能单独声称「工作区密码已卫生」。在源目录仍存在期间:

  1. Empty state / 导入页 固定警告:「个人/密码/ 等目录仍可能被搜索与知识库索引;请导入后归档。」
  2. 推荐 apply 后可选移动源到 个人/密码/_imported/,并将 _imported 或整树加入 knowledge/local-search excludePatterns(实现可复用已有 exclude 设置)。
  3. 可选Phase M+):内置 exclude 候选 个人/密码/**(默认 静默全局排除,避免误伤同名笔记;UI 一键启用)。
  4. 设计稿与验收 把「端到端密钥卫生」标为 P0 done,除非源已导入或 exclude。

8. Observability

P0 唯一审计 sink{workspaceRoot}/.mnote/vault/audit.jsonlappend-only)。

示例行:

{"ts":"2026-07-19T09:30:00Z","action":"reveal","actorId":"user_x","credentialId":"cred_…","field":"password","requestId":"…","ok":true}

禁止字段:value / password / body / frontmatter 原文。

Rebuild / 解析错误:同样只写 id/path/code。

可选 metricslist 延迟、reveal 计数、deny 计数。

运维index 损坏 → POST /api/vault/reindex 或 list 自动 rebuild;说明写入 support runbook 一节(本设计 §2.3)。


9. Rollout

策略
Feature flag MNOTE_VAULT默认 0(关)。允许默认开的门闩:P0-S 全绿(含 D1D5、D7D9 单测,尤其 D3 resource/read+ 建议 P0-E smoke 绿后再按环境打开。 P0-D 阶段名
入口 flag 关:隐藏 quick-action、API 404
密钥写入 禁止 在 flag 默认开且无 path denyP0-S)时合入
回滚 flag 关 + 隐藏入口;保留 .mnote/vault 数据
迁移 不自动 import;导入前建议用户备份源目录

10. Alternatives Considered

方案 描述 优点 缺点 结论
A. 普通 md + FileTree 继续 个人/密码/ 零开发 无分级/RAG 风险 否决
B. Control-plane DB 存密 Turso 表 易审计 违背 local-first 正文真相 否决
C. .mnote/vault + 专用页 + mnote-web API 本文 对齐 trash、可 deny、可分级 新模块 采用
D. Modal 如 trash 不改 URL 上下文保持 用户要专门页;表单空间不足 否决 IA
E. 一期加密 master password 真保密 工期/UX/AI resolve 延期
F. 单文件 SQLite/JSONL vault .mnote/vault/vault.db 索引快、无 per-file secret 扫描 人读救援差、与 md 生态/git diff 差 否决 P0(人读救援优先,K4
G. 先做 trash 式 modal 再升级全页 增量 UI 更快出壳 与已确认 IA 冲突、返工 否决

Security & Privacy

见 §5。补充:Clipboard 清空 best-effortSSR 首屏禁止 secretpath canonicalize + vault prefix deny。


Open Questions

  1. 图标 lock vs key?建议 lock。
  2. 是否永久废弃「文件总览」产品名?当前 /files 仅 redirect。
  3. 已冻结 P0read grant vault.view;若产品要协作者只读 vault → 改 §5.1 矩阵另开变更。
  4. 已冻结vault.resolve 默认 deny
  5. 导入候选路径是否扩展 密码/Passwords/?默认仅 个人/密码
  6. credentialId:建议 ULID/UUID。
  7. empty state 自动 ensure?建议是。

Key Decisions

# 决策 理由
K1 存储 {workspaceRoot}/.mnote/vault/ 系统空间;FileTree/RAG 已忽略 .mnote
K2 专用 /vault 全页 CRUD,非 modal/tiptap 用户明确要求
K3 死链 /files/inventory_2 → 密码箱 固定入口
K4 per-entry frontmatter md + L0 index 人读救援、git 友好;否决纯 SQLite P0
K5 index 只存 L0rebuild allowlist 解析 防 secret 进索引/日志
K6 P0 软删除 + 工作台回收站;不进全局 trash 降泄露;与 restore/purge API 同阶段
K7 L0/L1/L2reveal=会话+审计 最小暴露
K8 通用文件面 server deny vaultAI 仅 mnote.vault.*P1 堵住 open/stat/local_file 实锤缺口
K9 一期不加密 at-rest 工期与诚实威胁模型
K10 迁移可选、不静默覆盖;双轨风险明示 保护旧数据与预期
K11 刷新=command 结果 + 手动刷新; watcher;禁 setInterval 符合现网 watcher 行为
K12 大类 12-vault 04-tree 树 bug 默认 owner 系统空间产品
K13 P0 owner = mnote-web 系统空间 API;非 tree kernel / 非 P0 bridge command 对齐 trash;避免假 kernel
K14 Secret PATCH:省略=保留;clear/null=清空;set=写入;拒掩码哨兵 防擦密与存 ••••
K15 Feature flag 默认 P0-S(含 D3 resource/read)绿前不默认可写 secret 安全顺序;无 P0-D 阶段
K16 P0 审计 = .mnote/vault/audit.jsonl only 可执行、无双 sink 歧义
K17 P0 鉴权矩阵:owner/write⇒view+editread/share⇒无 vaultresolve 默认 deny 可实现;capabilities 延后 P1
K18 fields 仅非密钥 L0;标准 secret 三字段 + passwordHint L0hint==password → 仅 400 reject 防旁路;合同无 auto-clear
K19 附件 path 相对 vault root;专用 GET;禁 files/open 防 cookies 绕过
K20 path deny 优先挂 resolve_local_open_path / Pi resolveD3 resource/read 一等公民 防 leaf-only 漏挂

执行清单(Checklist

Phase 0 — 合同

  • 迁入 design/12-vault/process/;更新 design/README.md 主线(注明 12-vault 为系统空间,非 tree-domain
  • 冻结 schema / K1K20 / §5.1 矩阵 / §3.5 PATCH
  • 冻结 deny 表面 D1D12is_vault_sensitive_relative_pathchoke = resolve_local_open_path + Pi resolve + stat 显式

Phase P0-S — 安全锁(先于或与首个 secret 写入同 PR 合并门

  • 实现 is_vault_sensitive_relative_path + 单测
  • chokeresolve_local_open_path 内 deny(覆盖 D1 open、D3 resource/read、D4 resource/write
  • D2 stat_local_file 显式 deny(不走 resolve
  • D1 open / D2 stat / D3 GET .../resource/read → 403 vault_path_deniedD3 响应无 "text" 明文 / fixture password
  • D4 resource/write / D5 assets/upload 目标 vault → 403
  • D6 至少一条 page-body 或 tree 写路径 deny 单测
  • D7D9 local_file read/patch + resolve_file_path deny
  • knowledge_ragvault 路径不可 ingest(单测)
  • local_searchvault 不进 index;用户 includePaths 强指 vault → 拒绝
  • FileTree 不可见(.mnote + 断言)
  • MNOTE_VAULT 默认 0 时行为符合 §9(门闩引用 P0-S / P0-E,无 P0-D

Phase P0-A — 存储

  • ensure 含 vault + trash 子目录
  • vault_storeentry 读写、index 原子更新、L0 allowlist rebuild
  • soft-delete 移动文件 + status
  • restore / purge
  • audit.jsonl appendreveal/delete/…)
  • parse error 日志无 password 单测
  • cargo test -p mnote-web:模块名建议 vault_store / vault_path / vault_api(固定,不用「或等价」含糊)

Phase P0-B — HTTP

  • 注册 §3.2 全部 P0 路由
  • get/list maskreveal 审计无 value
  • update title-only keeps password 单测
  • mask sentinel → 400
  • fields 密钥键 → 400
  • passwordHint == password → 400 vault_hint_equals_secret(无 auto-clear
  • flag 关 → 404

Phase P0-C — SSR + 入口 + UI

  • VaultPage + mnote-vault-workbench
  • bootstrap list password 明文;HTML 单测/fixture 断言
  • layout 密码箱入口 + 明确 scriptlayout.rs 增加 vault-workbench-runtime.js module script(同侧其它 browser runtime),并在 mnote-ui-runtime.js 侧 import 初始化
  • deep-link ?id=、dirty discard、回收站 tab、手动刷新
  • 无 setInterval:对 vault-workbench-runtime.js 做静态扫描(rg setInterval
  • /files/vault 302

Phase P0-E — Smoke

  • scripts/task-vault-crud-smoke.js
    • 登录 e2e 账号;开 rootensure
    • create → list 见 L0 → get 无明文 password
    • update title only → 磁盘 password 不变
    • reveal → 得明文;audit.jsonl 有行且 rg 无 password 值
    • delete → trash 路径存在;restorepurge 干净
    • GET .../files/open?path=.mnote/vault/entries/... → 403
    • GET .../resource/read?path=.mnote/vault/entries/... → 403body 无 fixture password / 无 secret text
    • GET .../files/stat?path=.mnote/vault/entries/... → 403(或等价 deny,无敏感元数据滥用)
    • SSR /vault HTML 不含 fixture password 字符串
  • 禁止 sqlite3 直写 CP

Phase M — 迁移

  • preview/apply;冲突策略;双轨警告 UI
  • 可选 archive + exclude 提示
  • fixture 假密码导入 smoke3 条)

Phase P1 — AI tools + capabilities

  • mnote.vault.* 注册;resolve 默认 deny
  • grant/policy 写入 vault.* 可选
  • agent local_file.read vault deny smoke(回归)
  • attachment GET/POST

Phase P2 / 收口

  • 录入/cookies/exportARCHITECTURE + AGENTS 一行;design → done

Risks

风险 严重度 缓解
误认已加密 页眉声明
open/stat 合入漏挂 P0-S 门禁 + 全表面表
双轨 个人/密码/ §7.4
导入误解析 低置信人工确认
index 漂移 reindex + 原子写
原生 Pi 绕过 MNote 披露 + sandbox 建议
revision 冲突丢更新 低–中 re-fetch;可选 baseRevision

References

  • ARCHITECTURE.md / AGENTS.md
  • design 4-37 / 7-18 / 5-34 / 4-48
  • 代码:layout.rslocal_folder_source.rsensure/open/stat/resource read·write/upload/resolve_local_open_path)、local_folder_watcher_registry.rsgateway.rs trash、page_ai_pi/runtime.rscore-protocol AiAccessScope、knowledge_rag.rslocal_search_index.rs

PR Plan

PR-S — Vault path deny + index exclude locks安全门,可先于或与 PR1 同列车

  • Titlesecurity(vault): deny .mnote/vault on open/stat/resource/local_file and lock RAG/search
  • Filesvault_path.rs(新)、local_folder_source.rsresolve_local_open_path chokestat_local_file 显式、upload/write helpers)、page_ai_pi/runtime.rsresolve_file_path、local_file_read|patch)、knowledge_rag/local_search_index 测试
  • Dependencies:无
  • Description:实现 D1D12 中的可触达面;必须含 D3 resource/read 单测。优先 choke 而非 leaf 复制。 vault 业务写入。合并门:P0-S 红线单测绿。

PR1 — Vault store + ensure + soft-delete 文件语义

  • Titlefeat(vault): vault store, ensure dirs, soft-delete on disk
  • Fileslocal_folder_source ensure、vault_store.rs、audit.jsonl、单元测试 vault_store
  • DependenciesPR-S(或同 PR 内先 path deny
  • Description:目录、md 合同、index、soft-delete/restore/purge 文件层;无 HTTP 或仅测内函数。

PR2 — Vault HTTP APICRUD + reveal + restore/purge + mask PATCH

  • Titlefeat(vault): /api/vault CRUD, reveal, restore/purge with secret PATCH contract
  • Filesroutes/vault.rsroutes/mod.rs、flag MNOTE_VAULT 默认 0、API 测试
  • DependenciesPR-S、PR1
  • Description:§3.2 P0 路由;§3.5 语义;list/get mask;审计。flag 默认关。

PR3 — SSR /vault + /files redirect

  • Titlefeat(vault): SSR /vault workbench shell
  • Filesssr/pages/vault.rs、page route、styles、HTML 无 secret 测试
  • DependenciesPR2
  • DescriptionPageLayout 工作台壳 + bootstrap L0。

PR4 — Sidebar 入口 + vault-workbench-runtime

  • Titlefeat(vault): sidebar entry and vault workbench runtime
  • Fileslayout.rs(链接 + script 标签)、browser/vault-workbench-runtime.jsmnote-ui-runtime.js import、回收站 tab、deep-link、dirty discard
  • DependenciesPR3
  • Description:完整 P0 UI;手动刷新;rg setInterval 干净。

PR5 — Soft-delete UI 打磨与并发/revision(瘦身)

  • Titlefeat(vault): recycle-bin UX polish and revision conflict handling
  • Filesruntime + 少量 API(若 baseRevision
  • DependenciesPR4
  • Description不再塞 path deny / RAG(已在 PR-S)。专注回收站体验与两 tab 冲突。

PR6 — Browser smoke

  • Titletest(vault): browser smoke for vault CRUD and path deny
  • Filesscripts/task-vault-crud-smoke.js、TESTING_REFERENCE 条目
  • DependenciesPR4、PR-Sdeny 断言)
  • Description:§ Phase P0-E 全部断言。

PR7 — 迁移向导

  • Titlefeat(vault): import wizard from informal 个人/密码 notes
  • Filesimport API、UI、双轨警告、fixture 测试
  • DependenciesPR2 + PR4(稳定 create + UI
  • Descriptionpreview/apply;不静默覆盖。

PR8 — AI mnote.vault.*P1

  • Titlefeat(vault): Pi Lab mnote.vault tools with resolve default deny
  • Filespage_ai_pi/runtime.rs、skills、短交叉引用 7-18
  • DependenciesPR2、PR-S
  • Descriptiontools + 脱敏 + 回归 local_file deny。

PR9 — 附件 cookies强制依赖 PR8

  • Titlefeat(vault): vault attachment upload/download for cookies/session
  • Filesattachment API + UIvault_store attachments
  • DependenciesPR2、PR4UI 可后置);依赖 PR8
  • Description:专用 GET;禁 files/openpath 无 ..

PR10 — 文档收口

  • Titledocs(vault): ARCHITECTURE, AGENTS, design README; move 12-1 to done
  • FilesARCHITECTURE、AGENTS、design/README、迁 done
  • DependenciesPR6 绿;P0 可宣布
  • Description:系统空间定位写清,避免 bug 误派 04-tree。

Revision Summary(设计稿内)

2026-07-19 review 修订要点:

  1. 全表面 path denyopen/stat/write/Pi resolve/local_file+ 合入门 PR-S。
  2. 刷新模型改为 command/手动;撤销 watcher 假说。
  3. P0 软删除 + restore/purge HTTP/UI 对齐。
  4. Secret PATCH 语义与掩码哨兵拒绝。
  5. SecretField / passwordHint / fields L0 合同。
  6. Flag 默认关;PR5 瘦身;审计单 sink。
  7. 鉴权矩阵冻结;resolve 默认 denyreveal 非 OS 手势。
  8. Owner = mnote-web 系统空间,非假 kernel。
  9. 附件路径与专用 GET;5-34 为启发。
  10. 双轨 个人/密码/ 风险与 checklist/PR/K13K19 同步。

2026-07-19 re-review 修订:

  1. D3 GET /api/local-folder/resource/readread_local_resource)一等公民 denyD4 write / D5 upload 具名;表扩至 D1D12
  2. 首选 chokeresolve_local_open_path+ Pi resolve);stat_local_file 显式(不走 resolve)。
  3. §9 / checklist 去掉幽灵 P0-Dflag 门闩 = P0-S+ 建议 P0-E)。
  4. passwordHint == password 400 vault_hint_equals_secretK18)。
  5. K20smoke 含 resource/read + stat。
  6. §5.2 补 live /api/local-folder/* 路由盘点mod.rs):content 面仅 open/stat/resource/read|write/assets/uploadOCR 已退役;events/sync/cache 非正文旁路。