# 私有角色跨端合同

## 2026-09-29 最终审查补充

目录写入失败仍继续拉取远端变化、处理独立的记录绑定；每条绑定自己的头像准备和提交失败都保留重试事实，游标继续前进，不阻断后续健康绑定。

冻结消息只依赖云端已确认的角色身份和自己的头像。使用已有 cloud_payload 判断确认，不新增状态；已确认且头像为稳定资产或为空时直接准备发送，不等待后来资料上传。仍含本机头像的快照复用现有账号串行上传回执；未确认角色仍先创建目录。

最新目标 dev 的 SDK 发言者类型导出与本功能 selfRoles 导出均保留。最终完整构建、打包、仓外 SDK 与真实 DSH/Record 验证见 meta 的 `docs/analysis/2026-09-29-self-role-final-release-review.md`。

角色数据仍由一个 Host 业务 owner 承接：`SelfRoleService` 管同步/头像依赖，`ArkmeService` 管当前账号和公开命令，`SelfRoleSyncStore` 管 SQLite 的属性时间与云端确认。现有正文发送 owner 只请求消息自己的冻结快照；不复制正文队列。

## 消费面

| 消费面 | 入口 | 验证 |
| --- | --- | --- |
| UI | 发给自己/个人主题/延展的角色选择器；创建、修改头像和名称、删除；后台自动同步，不显示待同步/冲突或人工选择 | picker/Host 测试及官方 DSH 中安装 tgz 后实际页面 |
| Tools | `arkme_self_roles_list`、`arkme_self_roles_write`；创建快记工具支持 role_id | 经正式工具注册、会话授权链，在官方 DSH 实际会话调用 |
| SDK | 公开 `@senguoyun/dsh-arkme/sdk`，selfRoles 能力发现，list/create/update/delete/bind | 仓外 Consumer 严格 TS 编译、运行与旧能力拒绝；真实 Host 调用 |
| Host | 统一 expectedUserId、来源限制、冻结快照与错误语义 | owner 失败恢复、账号切换、旧请求隔离测试 |

不向 Tools/SDK/浏览器暴露 Host 凭据。头像使用既有文件 owner；签名 URL 不进入角色持久化数据。角色只是显示身份，真实作者和权限不变，私聊/群聊不接受角色绑定。受保护延展不会被本地角色覆盖。

## 本地数据与离线恢复

扩展既有 self_role / self_role_record，不新建消息队列。目录行只保存本地属性、name_at/avatar_at/deleted_at 和最后 cloud_payload；没有冻结 pending 请求、sync_error 或人工 resolve API。名称和头像各按毫秒时间合并；同毫秒取 UTF-8 字节序较大的值，三端规则一致。删除只产生 deleted_at，墓碑不被资料编辑复活；迟到资料按各属性自己的时间合并。编辑入口省略未修改属性，避免旧编辑框回写他端更新。

时间在本地修改时产生，取 max(当前毫秒, 已观察角色各属性最大时间 + 1)，上传、重试和丢回包不改业务时间。设备时钟偏差可能影响未互相观察的两次修改顺序，本方案提供确定收敛，不宣称还原真实墙钟先后，也不增加校时状态机。上传回执只改变头像存储引用，不是一次用户换头像。单个角色失败保留本地事实并继续其他角色；下次同步重试。

绑定以 cloud_ack 区分本机冻结事实与已确认云端事实；已确认绑定不能取消或换到另一个记录 UID。旧本机绑定按 keyset 分页补齐云端元数据，不创建新正文。单个绑定冲突不阻断其他绑定。云端快照进入缓存，重启离线仍能显示。

self_role_avatar_asset 保存账号内本机头像到已完成资产的回执，以及上传完成后尚未确认的 complete-upload 载荷，防止角色请求丢回包后重新上传；不是角色历史或消息引用表。上传确认回包丢失或进程重启时重放原会话确认，不重新 PUT 或新建资产；不保存签名 URL。角色删除不删除旧消息的头像资产，不依赖消息是否发送完毕。启动、目录操作、投影失效和周期恢复均复用同一个账号串行执行器；普通无角色发送没有目录请求。

## 验证入口

- 仓内：`pnpm run typecheck`、`pnpm test`、`pnpm run build`、`pnpm pack`。
- 仓外 SDK：`node scripts/verify-self-role-consumer.mjs <已正式安装 tgz 的临时 Profile>`。
- 真实 DSH：`vitest.self-role-e2e.config.mts` / `tests/e2e/self-role.e2e.mjs`，通过环境指定官方 checkout、临时 Profile、本地 Record origin 和本地 TLS 测试证书。仅允许 Record loopback 地址。使用独立账号、存储和钥匙串前缀，不接触常驻 Profile。

目标验收版本为官方 `dsh-v0.1.5-rc.2`。已在 macOS 官方运行时验证；未声称 Android/iOS/Windows/Linux 全平台生产验收，也未修改 DSH 源码或插件版本。

## 2026-09-28 角色选择交互修复

角色行将选用与管理分为两个并列按钮：点击头像/名称直接选用；右侧“⋯”打开原生 DSH 操作菜单，编辑/删除该行角色。管理不隐式切换角色，删除非当前角色不清空当前选择。去掉列表底部和编辑页的同步说明。角色目录、Host API、Tools、SDK、持久化和发送协议均不变；本次纯 UI 调整，Tools/SDK 无新增能力。

原生 MenuItem 仅支持一个主动作，无法承载同行独立管理按钮，因此角色选择使用独立的轻量浮层；编辑/删除命令仍复用 ArkmeActionMenu，不修改公共菜单或 DSH。浮层按视口定位、限高滚动，支持方向键、Escape 分层关闭、外部点击以及 iframe 关闭事件。不会在 button 中嵌套按钮。

相关角色及菜单测试 42 项通过，覆盖直接选用、管理非选中角色、删除当前/非当前角色、键盘和外部点击。实际浏览器检查使用真实角色菜单组件及官方 dsh-v0.1.5-rc.2 的 Menu/主题样式；截图是隔离组件预览，不能替代常驻 DSH 安装或跨平台验收。本轮不修改/重启常驻 DSH。

本轮最终验证：全仓 816 个测试文件通过、11 个跳过；9,563 项通过、15 项跳过。typecheck、完整构建和 tgz 打包通过；清单未包含临时验收文件或本机 worktree 绝对路径。未对常驻实例进行安装验收。

创建/编辑角色只保留点击头像换图的入口，去掉“移除头像”按钮及其表单状态。未选新图片时更新请求省略头像字段，保留原头像；新图片仍经过原裁剪和账号绑定上传流程。

上述头像入口精简已通过 14 项角色 UI 测试、全仓 9,563 项测试（15 项跳过）、typecheck、构建及 tgz 清单检查。实际编辑页面另经 Chrome 隔离组件预览验证；图片解析使用测试数据，不作为头像远端链路验收。

## 角色操作菜单紧凑宽度

仅对现有“角色操作”命令菜单设置最小 120px、内容自适应宽度及视口最大宽度；角色目录、其他菜单和操作行高度不变。UI 范围调整，无新增 Host/Tools/SDK 能力、配置或版本变化。

全仓 9,563 项测试通过（15 项跳过），typecheck、构建与 tgz 打包通过。使用真实角色组件及未修改的官方 DSH Menu/主题进行 Chrome 预览，600px、320px 视口中菜单均为 120px、行高 40px，未越界，编辑入口可用；截图位于任务验证目录 role-ui/v8。未对常驻 DSH 进行重启或安装验收。
