fix: 串行化凭据所有权并响应桌面请求取消
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
# OpenNexus 文档索引
|
||||
|
||||
最新:[2026-09-08 验收报告与 Sync 测试部署](development/OpenNexus验收报告-2026-09-08.md)。全量验收未通过,含已复现阻塞项与逐 ID 结果。
|
||||
最新:[2026-09-08 验收修复与全量回归](development/OpenNexus验收修复与回归-2026-09-08.md)。两项已复现缺陷已修复,现有自动化测试全部通过;完整生产化验收仍未通过。原始证据见[验收报告与 Sync 测试部署](development/OpenNexus验收报告-2026-09-08.md)。
|
||||
|
||||
> 第二阶段收尾:标准 Agent/RAG Benchmark 与报告页、函数图预览、三格式快照导出及真实 Provider/MCP 结果见[实现与验收记录](development/第二阶段收尾实现与验收-2026-09-07.md)。当前分支尚未合并,不更改下文历史 main 基线。
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
| 命令 | 参数 / 返回 |
|
||||
| --- | --- |
|
||||
| `host_capabilities` | protocol=1,workspace/credentials=true;core 随进程状态返回;sync/extensions=false |
|
||||
| `core_request` | 代理认证 Sidecar 的 `/health` 与 `/api/*`;Host 注入会话和代际,支持 JSON 与限额二进制,不接受前端鉴权头 |
|
||||
| `core_request_prepare` | timeout_ms(1–600000),返回一次性 request_id;最多 64 个待执行/在途请求,默认前端超时 30000 ms |
|
||||
| `core_request` | request 对象含 requestId、method、path、body/bodyBase64、contentType、idempotencyKey;必须使用未过期的预登记 ID 且仅执行一次。代理认证 Sidecar 的 `/health` 与 `/api/*`,不接受前端鉴权头 |
|
||||
| `core_request_cancel` | request_id;取消未发送的预登记项或终止在途 HTTP Future,重复取消无副作用 |
|
||||
| `workspace_choose` | 原生选择,取消返回 null;成功返回 vault_id/path/name |
|
||||
| `workspace_open` | 只允许重开应用数据目录中已持久化授权的规范化路径 |
|
||||
| `workspace_recent` | 最近 20 个用户主动选择的 Vault;只保存身份、名称和路径 |
|
||||
@@ -21,7 +23,9 @@
|
||||
| `workspace_delete` | path、expected;正文保存在 `.ainote/trash` |
|
||||
| `workspace_mkdir` | path,相对当前授权 Vault |
|
||||
|
||||
当前错误通过 Tauri rejection 返回稳定 code,前端 `DesktopError` 保留该 code。主要 code 为 REVISION_CONFLICT、RECOVERY_CONFLICT、UNSAFE_PATH、PATH_CONFLICT、VAULT_ALREADY_OPEN、SCHEMA_INCOMPATIBLE、ATOMIC_REPLACE_FAILED。统一 request_id、取消标识及完整错误详情尚未固化。
|
||||
当前错误通过 Tauri rejection 返回稳定 code,前端 `DesktopError` 保留该 code。主要 code 为 REVISION_CONFLICT、RECOVERY_CONFLICT、UNSAFE_PATH、PATH_CONFLICT、VAULT_ALREADY_OPEN、SCHEMA_INCOMPATIBLE、ATOMIC_REPLACE_FAILED。常规 Core 请求新增 REQUEST_CANCELLED、REQUEST_TIMEOUT 及预登记错误;前端取消/超时错误带 request_id 和 outcome(not_sent/unknown)。已经发送的写入不能推断回滚,不自动重试;按业务 operation_id 确认提交结果的完整链路仍待实现。
|
||||
|
||||
凭据保险库解锁前取得独立 `.lock` 文件的 OS 排他锁,持有至手动锁定、错误后锁定或实例销毁。其他实例返回 CREDENTIALS_BUSY,不读取旧快照后继续覆盖写入;错误口令释放临时锁,进程退出由 OS 释放。Windows 禁止在持锁期间替换该锁文件。
|
||||
|
||||
## 写入及恢复
|
||||
|
||||
|
||||
@@ -0,0 +1,271 @@
|
||||
{
|
||||
"date": "2026-09-08",
|
||||
"base_commit": "6bf5127",
|
||||
"known_defects": {
|
||||
"F-01": "FIXED",
|
||||
"F-02": "FIXED"
|
||||
},
|
||||
"automated_regression": "PASSED",
|
||||
"production_acceptance": "NOT_PASSED",
|
||||
"counts": {
|
||||
"backend": {
|
||||
"tests": 894,
|
||||
"failures": 0,
|
||||
"errors": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"packaged-core": {
|
||||
"tests": 4,
|
||||
"failures": 0,
|
||||
"errors": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"sync": {
|
||||
"tests": 23,
|
||||
"failures": 0,
|
||||
"errors": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"community": {
|
||||
"tests": 13,
|
||||
"failures": 0,
|
||||
"errors": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"frontend": {
|
||||
"tests": 504,
|
||||
"failures": 0,
|
||||
"errors": 0,
|
||||
"skipped": 0
|
||||
},
|
||||
"rust": {
|
||||
"passed": 28
|
||||
}
|
||||
},
|
||||
"checks": [
|
||||
{
|
||||
"name": "rust-fmt",
|
||||
"command": [
|
||||
"cargo",
|
||||
"fmt",
|
||||
"--manifest-path",
|
||||
"frontend/src-tauri/Cargo.toml",
|
||||
"--check"
|
||||
],
|
||||
"exit_code": 0,
|
||||
"seconds": 0.383,
|
||||
"finished_utc": "2026-09-08T05:09:44.693513+00:00",
|
||||
"artifact_directory": ".build\\acceptance\\fixes-final-6bf5127"
|
||||
},
|
||||
{
|
||||
"name": "rust-tests",
|
||||
"command": [
|
||||
"cargo",
|
||||
"test",
|
||||
"--manifest-path",
|
||||
"frontend/src-tauri/Cargo.toml",
|
||||
"--features",
|
||||
"desktop",
|
||||
"--all-targets",
|
||||
"--locked"
|
||||
],
|
||||
"exit_code": 0,
|
||||
"seconds": 39.22,
|
||||
"finished_utc": "2026-09-08T05:10:23.925087+00:00",
|
||||
"artifact_directory": ".build\\acceptance\\fixes-final-6bf5127"
|
||||
},
|
||||
{
|
||||
"name": "rust-clippy",
|
||||
"command": [
|
||||
"cargo",
|
||||
"clippy",
|
||||
"--manifest-path",
|
||||
"frontend/src-tauri/Cargo.toml",
|
||||
"--features",
|
||||
"desktop",
|
||||
"--all-targets",
|
||||
"--locked",
|
||||
"--",
|
||||
"-D",
|
||||
"warnings"
|
||||
],
|
||||
"exit_code": 0,
|
||||
"seconds": 3.526,
|
||||
"finished_utc": "2026-09-08T05:10:27.480493+00:00",
|
||||
"artifact_directory": ".build\\acceptance\\fixes-final-6bf5127"
|
||||
},
|
||||
{
|
||||
"name": "backend",
|
||||
"command": [
|
||||
"uv",
|
||||
"run",
|
||||
"--directory",
|
||||
"backend",
|
||||
"pytest",
|
||||
"-q",
|
||||
"--junitxml=G:\\OSProject\\NotesAgent-phase3\\.build\\acceptance\\fixes-6bf5127\\backend.xml"
|
||||
],
|
||||
"exit_code": 0,
|
||||
"seconds": 199.768,
|
||||
"finished_utc": "2026-09-08T05:06:18.641433+00:00",
|
||||
"artifact_directory": ".build\\acceptance\\fixes-6bf5127"
|
||||
},
|
||||
{
|
||||
"name": "packaged-core",
|
||||
"command": [
|
||||
"uv",
|
||||
"run",
|
||||
"--directory",
|
||||
"backend",
|
||||
"pytest",
|
||||
"-q",
|
||||
"tests/test_sidecar_auth.py",
|
||||
"--junitxml=G:\\OSProject\\NotesAgent-phase3\\.build\\acceptance\\fixes-6bf5127\\packaged-core.xml"
|
||||
],
|
||||
"exit_code": 0,
|
||||
"seconds": 3.742,
|
||||
"finished_utc": "2026-09-08T05:06:22.396738+00:00",
|
||||
"artifact_directory": ".build\\acceptance\\fixes-6bf5127"
|
||||
},
|
||||
{
|
||||
"name": "frontend",
|
||||
"command": [
|
||||
"pnpm",
|
||||
"--dir",
|
||||
"frontend",
|
||||
"exec",
|
||||
"vitest",
|
||||
"run",
|
||||
"--maxWorkers=2",
|
||||
"--reporter=default",
|
||||
"--reporter=junit",
|
||||
"--outputFile.junit=G:\\OSProject\\NotesAgent-phase3\\.build\\acceptance\\fixes-final-6bf5127\\frontend.xml"
|
||||
],
|
||||
"exit_code": 0,
|
||||
"seconds": 56.45,
|
||||
"finished_utc": "2026-09-08T05:10:40.765672+00:00",
|
||||
"artifact_directory": ".build\\acceptance\\fixes-final-6bf5127"
|
||||
},
|
||||
{
|
||||
"name": "types",
|
||||
"command": [
|
||||
"pnpm",
|
||||
"--dir",
|
||||
"frontend",
|
||||
"type-check"
|
||||
],
|
||||
"exit_code": 0,
|
||||
"seconds": 7.819,
|
||||
"finished_utc": "2026-09-08T05:10:48.619856+00:00",
|
||||
"artifact_directory": ".build\\acceptance\\fixes-final-6bf5127"
|
||||
},
|
||||
{
|
||||
"name": "build",
|
||||
"command": [
|
||||
"pnpm",
|
||||
"--dir",
|
||||
"frontend",
|
||||
"build"
|
||||
],
|
||||
"exit_code": 0,
|
||||
"seconds": 34.154,
|
||||
"finished_utc": "2026-09-08T05:11:22.812302+00:00",
|
||||
"artifact_directory": ".build\\acceptance\\fixes-final-6bf5127"
|
||||
},
|
||||
{
|
||||
"name": "sync",
|
||||
"command": [
|
||||
"uv",
|
||||
"run",
|
||||
"--directory",
|
||||
"server sync",
|
||||
"pytest",
|
||||
"-q",
|
||||
"--junitxml=G:\\OSProject\\NotesAgent-phase3\\.build\\acceptance\\fixes-6bf5127\\sync.xml"
|
||||
],
|
||||
"exit_code": 0,
|
||||
"seconds": 9.874,
|
||||
"finished_utc": "2026-09-08T05:08:10.342428+00:00",
|
||||
"artifact_directory": ".build\\acceptance\\fixes-6bf5127"
|
||||
},
|
||||
{
|
||||
"name": "community",
|
||||
"command": [
|
||||
"uv",
|
||||
"run",
|
||||
"--directory",
|
||||
"community-server",
|
||||
"pytest",
|
||||
"-q",
|
||||
"--junitxml=G:\\OSProject\\NotesAgent-phase3\\.build\\acceptance\\fixes-6bf5127\\community.xml"
|
||||
],
|
||||
"exit_code": 0,
|
||||
"seconds": 3.162,
|
||||
"finished_utc": "2026-09-08T05:08:13.520864+00:00",
|
||||
"artifact_directory": ".build\\acceptance\\fixes-6bf5127"
|
||||
},
|
||||
{
|
||||
"name": "smoke",
|
||||
"command": [
|
||||
"G:\\OSProject\\NotesAgent-phase3\\backend\\.venv\\Scripts\\python.exe",
|
||||
"scripts/phase3-isolated-smoke.py"
|
||||
],
|
||||
"exit_code": 0,
|
||||
"seconds": 3.411,
|
||||
"finished_utc": "2026-09-08T05:08:16.933393+00:00",
|
||||
"artifact_directory": ".build\\acceptance\\fixes-6bf5127"
|
||||
},
|
||||
{
|
||||
"name": "docs",
|
||||
"command": [
|
||||
"python",
|
||||
"scripts/check-doc-links.py"
|
||||
],
|
||||
"exit_code": 0,
|
||||
"seconds": 7.01,
|
||||
"finished_utc": "2026-09-08T05:11:29.830898+00:00",
|
||||
"artifact_directory": ".build\\acceptance\\fixes-final-6bf5127"
|
||||
},
|
||||
{
|
||||
"name": "diff",
|
||||
"command": [
|
||||
"git",
|
||||
"diff",
|
||||
"--check",
|
||||
"--",
|
||||
".",
|
||||
":(exclude)backend/data/vault"
|
||||
],
|
||||
"exit_code": 0,
|
||||
"seconds": 0.119,
|
||||
"finished_utc": "2026-09-08T05:11:29.989493+00:00",
|
||||
"artifact_directory": ".build\\acceptance\\fixes-final-6bf5127"
|
||||
}
|
||||
],
|
||||
"remote_health": {
|
||||
"checked_at": 1788844300.226044,
|
||||
"url": "http://yui.kronecker.cc:18080",
|
||||
"checks": [
|
||||
{
|
||||
"path": "/health",
|
||||
"status": 200,
|
||||
"expected": 200
|
||||
},
|
||||
{
|
||||
"path": "/ready",
|
||||
"status": 200,
|
||||
"expected": 200
|
||||
},
|
||||
{
|
||||
"path": "/sync/v1/handshake",
|
||||
"status": 200,
|
||||
"expected": 200
|
||||
},
|
||||
{
|
||||
"path": "/sync/v1/vaults",
|
||||
"status": 401,
|
||||
"expected": 401
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
# OpenNexus 验收修复与全量回归(2026-09-08)
|
||||
|
||||
本次在 `feat/phase3-completion` 工作树、基线 `6bf5127` 上修复了原验收中复现的 F-01、F-02,并完成现有自动化测试的全量回归。原始失败记录保留在[首次验收报告](OpenNexus验收报告-2026-09-08.md);本报告不将尚未实现或缺少证据的生产化验收目标标记为通过。
|
||||
|
||||
## 修复内容
|
||||
|
||||
### F-01:多个凭据 Broker 覆盖快照
|
||||
|
||||
Stronghold 在读取快照前取得同目录独立锁文件的操作系统排他锁,并持有至凭据库锁定。第二个实例返回 `CREDENTIALS_BUSY`,设置页提供可理解的提示。锁文件独立于原子替换的快照文件;Windows 禁止其他句柄删除被持有的锁文件。解锁失败或进程退出时释放所有权。
|
||||
|
||||
回归测试验证两个实例交接后两次写入均保留、错误密码不遗留锁,以及真实子进程持锁时拒绝并发解锁、子进程被终止后能重新解锁并读取已提交凭据。
|
||||
|
||||
### F-02:桌面请求忽略取消和超时
|
||||
|
||||
前端和 Rust Host 使用先预留、后派发的请求协议。已取消请求不会派发;等待预留期间取消后,迟到的预留会被清理。Host 对请求使用独立截止时间和取消信号,限制最多 64 个预留,拒绝重复执行,并在退出时清理状态。取消正在读取的 HTTP 响应会丢弃请求 Future,真实 TCP 测试验证连接关闭。普通桌面请求默认超时 30 秒,可配置至 600 秒。
|
||||
|
||||
前端清理计时器和监听器,保留二进制传输。写请求派发后取消或超时会报告结果未知,提示先检查业务结果,不自动重试。取消不代表服务端事务回滚;业务提交结果查询仍未完成,不能据此认定完整 A-03 验收通过。协议详见 [Host v1 契约](../contracts/Host-v1契约.md)。
|
||||
|
||||
## 测试结果
|
||||
|
||||
| 范围 | 最终结果 |
|
||||
| --- | --- |
|
||||
| 后端 pytest | 894 通过 |
|
||||
| 前端 Vitest | 93 个文件,504 通过 |
|
||||
| Rust desktop 全目标测试(locked) | 28 通过 |
|
||||
| Sync pytest | 23 通过 |
|
||||
| Community pytest | 13 通过 |
|
||||
| 打包 Core 认证回归 | 4 通过(单独运行,和后端套件有重叠) |
|
||||
| 三服务隔离冒烟测试 | 通过 |
|
||||
| 前端类型检查、生产构建 | 通过;构建仍提示部分 chunk 大于 500 kB |
|
||||
| Rust fmt、Clippy 全目标 `-D warnings` | 通过 |
|
||||
| 文档链接、变更空白检查 | 通过 |
|
||||
|
||||
本地完整命令、退出码和日志位于 `.build/acceptance/fixes-6bf5127/` 与 `.build/acceptance/fixes-final-6bf5127/`。随文的 JSON 汇总保存命令和最终结果;原始日志属于本地构建产物。首轮新增 TCP 测试因同步等待阻塞 Tokio 清理而失败,改为异步等待线程结束;Clippy 发现 Host 参数数量超限,改为严格的结构化请求 DTO。修复后 Rust、前端及构建检查均重新通过,未改动的 Python 服务沿用本轮先前已通过结果。编辑器测试仅调整冷启动等待预算,保留原有断言。
|
||||
|
||||
## Sync 测试部署复核
|
||||
|
||||
对 [Sync 测试服务](http://yui.kronecker.cc:18080) 的公网复核结果为:`/health`、`/ready`、`/sync/v1/handshake` 返回 200,未认证访问 `/sync/v1/vaults` 返回 401。本次修复只涉及桌面端,未重新部署服务。此前 100 MiB 上传下载、并发 CAS、重启持久性等证据仍以首次验收报告为准,本次没有重复这些远程压力测试。
|
||||
|
||||
## 验收边界
|
||||
|
||||
本次结论为已知缺陷关闭、现有自动化回归通过。完整生产化验收仍未通过:操作系统级插件隔离、扩展管理、完整 Rust 同步客户端等规划能力仍有未完成项;本次也没有补充 MSVC 安装包、真实双设备桌面操作和完整故障矩阵的验收证据。不能将测试数视为规划中 30 个验收 ID 全部完成。用户 Vault 文件未纳入本次修改或提交。
|
||||
Reference in New Issue
Block a user