# 5-36 OnlyOffice local-folder 编辑保存 callback 契约

## 目标

为 local-folder Office 编辑保存建立 callback 写回契约。P1 可以先落设计和测试骨架；若实现，必须覆盖 status 2/6 的下载写回和路径安全。

## 原因

ONLYOFFICE 官方保存链路要求 callback `status === 2` 或 `status === 6` 时，集成后端下载 `body.url` 并写回原文件。当前 `mnote-web` callback 仍代理 legacy Next，local-folder 写回未闭环。

## 允许修改

- `rust/crates/mnote-web/src/routes/onlyoffice.rs`
- `rust/crates/adapter-onlyoffice/src/lib.rs`
- 相关单测
- 如需记录设计，可补充本 checklist

## 禁止事项

- 不绕过 allowed roots 写任意路径。
- 不在没有冲突保护时覆盖用户无关文件。
- 不强行删除 legacy callback 代理。

## Checklist

- [ ] 明确 local asset callback 定位：从 `assetId` / `fileUrl` / session 推导 rootUri 与 path。
- [ ] status 非 2/6 时返回 `{ "error": 0 }`，不写文件。
- [ ] status 2/6 时下载 rewritten `body.url`。
- [ ] 写回前校验目标路径属于当前 local root allowed roots。
- [ ] 写回后触发 watcher / projection 刷新或说明现有 watcher 如何感知。
- [ ] 若本轮不实现完整写回，必须在编辑入口保留 guard，不让用户以为保存已支持。

## 验收

- `cargo test -p mnote-web onlyoffice_callback -- --test-threads=1`
- `cargo test -p mnote-web -- --test-threads=1`
- 后续浏览器编辑保存验收需单独设计，不纳入 P0。

## 本轮执行记录

- 2026-05-21：Reasonix worker C 分析完成。
  **结论：本轮不应实现完整写回。**

  **阻塞输入缺口：**

  1. callback URL 当前只携带 `assetId` 和 `userId`，缺少 `rootUri` 和 `path`。
     - `buildCallbackUrl()` 在 `onlyoffice.rs` 页脚 JS 中硬编码只传两个参数。
     - 对于 local-folder asset（assetId `local:asset:path/to/file.docx`），无法从 assetId 推导 rootUri。
     - 没有 rootUri 就无法 `resolve_onlyoffice_local_file_path` → 无法安全写回。
  2. `adapter-onlyoffice` 的 `prepare_callback` 不感知 local-folder 路径。
     - `OnlyOfficeCallbackPreparationInput` 没有 rootUri / path 字段。
     - `prepare_callback` 只做下载 URL rewrite 和 session 定位，不含路径校验。
  3. callback Rust handler 的 `proxy_legacy_onlyoffice_json` 目前代理 legacy Next；local-folder 无 legacy 时返回 NOT_IMPLEMENTED。

  **实现写回所需的最小增量（下轮实现）：**

  - `page()` 页脚的 `buildCallbackUrl()`：检测 local asset，追加 `rootUri` 和 `path` query 参数。
  - `OnlyOfficeCallbackQuery` struct：增加 `root_uri: Option<String>` 和 `path: Option<String>`。
  - `callback()` handler：检测到 local asset 时（assetId 含 `local:` / `local-file:` 前缀），从 query 提取 rootUri 和 path，调用 `resolve_onlyoffice_local_file_path` 校验路径在 allowed roots 内。
  - `adapter-onlyoffice`：在 `OnlyOfficeCallbackPreparationInput` 和 `prepare_callback` 增加 local path 字段。
  - status 2/6：下载 rewritten body.url，写回校验过的本地路径。
  - status 非 2/6：返回 `{"error": 0}`，不写文件。

  **当前 guard 状态：**
  - Worker B 已在 `layout.rs` 中实现 `withOfficeEditModeGuard`：confirm 对话框 + `data-mnote-last-office-edit-mode-requested` / `data-mnote-last-office-edit-mode-guard` 数据属性。
  - 编辑入口在 local-folder writeback 未闭环时带 guard 提示，符合安全策略。
