Skip to content

Latest commit

 

History

History
399 lines (333 loc) · 30.5 KB

File metadata and controls

399 lines (333 loc) · 30.5 KB

开发进度记录(逐阶段实现)

规则:每完成一个阶段,先审核 + 跑测试验证无问题,再进入下一阶段。 依据:dev_docs/design-modules/14-dev-plan.md(WBS/验收)

M0 —— 契约定稿 + 工作区脚手架 ✅ 完成(已验证)

交付物

  • server/ Cargo workspace:12 个 crate(dsh-core 有真实实现,其余为占位)
  • server/crates/dsh-core:数据模型(Value 自定义 serde / Structure / BranchState / VersionRecord / PublishEvent / Ciphertext)、ErrorKind 错误体系、KV 键构造、Validator(类型/必填/结构约束/限额)、compute_diff + apply_diff、limits 常量
  • 契约三件套:proto/config.v1.protoapi/openapi.v1.yamlschema/storage.v1.schema.json(Value 增加 ciphertext 字段)
  • scripts/check-contracts.sh:proto/openapi/schema 三方 lint
  • .github/workflows/ci.yml:lint / unit / contract 三 job(后续阶段逐步补 stage 4~9)
  • rust-toolchain.toml.gitignore

验证结果(全部通过)

检查 结果
cargo fmt --check ✅ 干净
cargo clippy --all-targets --all-features -- -D warnings ✅ 无警告
cargo test --workspace ✅ 24 个测试全绿(core 13 单测 + 5 集成 + 11 占位)
scripts/check-contracts.sh ✅ proto OK / openapi 25 paths / schema 16 $defs

M0 验收标准对照:□ 契约三方 lint ✓ □ 决策记录(D1–D16,见 design-v2 §16)✓ □ 工作区脚手架 ✓ □ CI 骨架 ✓

M1 —— 单节点 + 集群共识 ✅ 完成(全部验证通过)

交付物

  • dsh-core 状态机(Command apply / 幂等 I10 / 必填校验 / GetConfig / 版本快照 / dump·restore)
  • dsh-storage:RocksDB(4 列族 + Store + checkpoint)
  • dsh-raft:openraft 0.9(LogStore/StateMachineStore/JSON 快照)+ 进程内直连网络(测试)+ HTTP 网络(多进程生产,reqwest+axum)
  • dsh-cli:--dev-single + 集群模式(--node-id/--bootstrap/--join),Raft RPC 服务、集群管理端点(members/join/promote)
  • watch SSE(dev-single 发布事件广播)
  • 脚本:dev-single-demo.sh(11 步)、cluster-demo.sh(7 步)

M1 验收结果(全部通过)

验收项 结果
建项目/分支→草稿→发布→GetConfig 新版本 ✅ dev-single + 集群
3 节点 join(learner→voter)→ 写→全节点复制 ✅ 进程内测试 + 多进程 e2e
kill 任一节点后多数派继续读写 ✅ 节点2 被杀后仍可写,存活节点可见
watch 收到发布事件 ✅ dev-single SSE
cargo fmt / clippy -D warnings
cargo test --workspace ✅ 40 passed / 0 failed

工程记录

  • HTTP 网络:openraft RPC 经 axum /raft/* + reqwest JSON;错误以 500 返回(客户端按网络错误重试);快照分块由 openraft 默认 full_snapshot 处理
  • 集群写:经 raft.client_write(幂等键在命令内);读:本地状态机(M2.5 加线性一致读)
  • 演示脚本坑:--http-addr 不能带 http:// 前缀(bind 会 DNS 失败)

M2 —— 发布引擎完整 + 加密 + 渲染 + 后台任务 ✅ 完成(全部验证通过)

交付物

  • dsh-core 发布引擎完整语义:Rollback(历史不可变 I6/I9)、共享库(SharedDraft/SharedPublish 原子级联 D7/D15)、RefBind/RefUnbind(item 级引用 + 索引反查)、发布时引用物化、prune_versions(版本裁剪)
  • dsh-crypto:AEAD 信封加密(AES-256-GCM,KEK+每项 DEK)、主密钥加载(env/文件)、脱敏、wire 格式对齐 core::Ciphertext
  • dsh-render:YAML/TOML/JSON 渲染(Value→普通 JSON 树;secret 掩码/解密)、TOML 约束错误处理、格式等价性测试
  • dsh-jobs:JobScheduler(仅 leader)+ VersionRetention 版本裁剪
  • dsh-cli:secret 提交前加密(I8,保持 Raft apply 确定性)、读取解密、渲染端点 /v1/.../config?format=、rollback 端点、版本裁剪任务接线(--version-retention)

M2 验收结果(全部通过)

验收项 结果
回滚=新版本(rollback_of),历史不可变 ✅ core 测试 + API e2e(v3 回滚到 v1 内容)
共享库发布自动级联(原子) ✅ core 测试(引用分支版本推进+值更新)
引用物化:草稿无值时取共享值 ✅ core 测试
secret 加密存储→解密输出(I8) ✅ crypto 测试 + API e2e
YAML/TOML/JSON 渲染 + 等价性 ✅ render 测试 + API e2e
版本裁剪保留活动版本+最近 N ✅ jobs 测试
cargo fmt / clippy -D warnings
cargo test --workspace ✅ 49 passed / 0 failed
回归:dev-single / cluster 演示 ✅ 全过

工程记录

  • 加密在 API 层(提交命令前)执行,状态机只存密文 —— 保证 Raft apply 确定性(随机 nonce 不跨节点发散)
  • 确定性时间:apply 用日志序号作 now_ms(D16)
  • 组级引用(item_key=None)M2 暂不支持(validation 拒绝),后续迭代

M2.5 —— 集群 watch + 可观测性 + 单管理员会话 + 内嵌 Admin UI ✅ 完成(全部验证通过)

交付物

  • 集群 watch:StateMachineStore 在 Raft apply 时广播 PublishEvent(各节点本地)→ cli 转发到统一 broadcast → SSE;dev-single 与集群共用同一 watch 通道
  • 可观测性:/readyz(集群就绪检查)、/metrics(Prometheus 文本:dsh_projects/dsh_versions)、审计(内存环形缓冲 + GET /api/v1/audit)
  • 单管理员会话(I7):login/logout/heartbeat + 鉴权中间件(/api/v1/* 除 login/healthz/readyz/cluster-join 外需 Bearer);第二登录 409 ERR_SESSION_IN_USE
  • 内嵌 Admin UI(模块 09):rust-embed 内嵌 /admin 静态页(项目列表/配置查看/watch 事件流),单二进制交付

M2.5 验收结果(全部通过)

验收项 结果
集群 watch:follower 收到 leader 发布事件 ✅ SSE 事件(structure_publish + value_publish)
单会话:第二登录 409 / 无 token 401
/metrics /readyz /audit
Admin UI /admin ✅ 内嵌 HTML
回归:dev-single + cluster 演示(含鉴权) ✅ 全过
cargo fmt / clippy / 49 tests

工程记录

  • 集群 watch 语义:每个节点本地 apply 时广播(openraft 复制日志到所有节点,事件天然一致),SSE 订阅任一节点即可
  • /api/v1/cluster/join 豁免鉴权(节点加入前的引导调用);login 前无会话
  • 演示脚本鉴权改造:dev-single 用 AUTH 头;cluster 用按主机 token 文件(api() 助手)

M3 —— 三语言 SDK(TS/Go/Python)✅ 完成(契约测试全部通过)

交付物(sdk/ 目录)

  • TS SDK(sdk/ts):ConfigClient(端点池 failover + 退避)、get/getItem(/snapshot 数据面端点)、watch(SSE + 断线重连退避)、ConfigError;Node --experimental-strip-types 直接运行
  • Go SDK(sdk/go):configclient 包(Get/GetItem/Watch),bufio SSE 解析,断线重连
  • Python SDK(sdk/python):ConfigClient(urllib 标准库),get/get_item/watch(threading 停止)
  • 数据面快照端点 /v1/projects/{p}/branches/{b}/snapshot(无鉴权,纯值输出,含版本号)
  • scripts/sdk-contract-test.sh:三语言对同一 dev-single 的契约测试(get + watch)

M3 验收结果(全部通过)

验收项 结果
TS:get + watch ✅(get v3 → watch v4)
Go:get + watch ✅(get v12 → watch v13)
Python:get + watch ✅(get v15 → watch v16)
契约一致(同一服务/同端点) ✅ 三语言对拍
回归:fmt / clippy / 49 tests / dev-single / cluster

工程记录

  • 环境坑:~/.cache 只读 → GOCACHE=/tmp/dsh-gocache;GO 无外部依赖(stdlib)
  • 契约测试时序:循环发布消除"测试启动编译耗时"竞态;publish 用唯一 request_id(幂等去重)
  • Node 22 --experimental-strip-types 不支持 parameter property(TS SDK 避开该语法)

M4 —— 混沌 / 加固 / 发布 ✅ 完成 —— 项目全部里程碑(M0–M4)达成

交付物

  • scripts/chaos-test.sh:leader SIGKILL → 重新选举 → 继续写入 → 重启(同 data-dir 自动恢复)→ 追赶一致(含宕机期间写入);follower 击杀重启追赶一致
  • 重启自动恢复(auto-rejoin):raft-meta 非空时无需 --bootstrap/--join,openraft 从持久化状态 resume(学习更高 term 后让位并追赶)
  • 安全加固:安全响应头中间件(X-Content-Type-Options / X-Frame-Options / CSP)、JoinReq 宽松反序列化、tracing 日志接入
  • 发布资产:deploy/Dockerfile(多阶段)、deploy/docker-compose.yml(3 节点)、根 README.md(快速开始/能力/构建测试/文档索引)
  • 基准冒烟:读路径 GET /snapshot 6279 QPS(200 并发 × 4 万请求全成功)

M4 验收结果(全部通过)

验收项 结果
混沌:leader 击杀 → 选举 → 写入
混沌:节点重启 → 追赶一致(含宕机期间写入)
安全加固(CSP/安全头)
发布清单(Docker/compose/README)
基准冒烟(读 6279 QPS)
最终回归:fmt/clippy/49 tests/契约 lint/4 个 e2e 脚本 ✅ 全绿

✅ 项目完成总览(M0–M8 全部达成 · 终态快照)

里程碑 交付 验证
M0 契约+脚手架 proto/openapi/schema 契约、Cargo workspace 12 crates、dsh-core 数据模型 lint + 29 tests
M1 单节点+集群 状态机/发布/RocksDB/openraft 集成/HTTP 网络/--dev-single/--bootstrap/--join 49 tests + 2 e2e
M2 发布引擎+加密+渲染+任务 回滚/共享库级联/引用物化/AEAD 加密/三格式渲染/版本裁剪 core/crypto/render/jobs tests + e2e
M2.5 集群 watch+可观测+会话+UI raft apply 事件广播 SSE/metrics/readyz/审计/单管理员会话/内嵌 /admin M2.5 e2e
M3 三语言 SDK TS/Go/Python(get/getItem/watch/failover)+ 契约测试 三语言对拍全过
M4 混沌+加固+发布 leader 击杀/重启追赶混沌、安全头、Docker/compose/README、基准 全部脚本 + 49 tests
M5 会话/审计/快照/集群watch 会话落 Raft(I7 集群级单会话+leader 转发)、审计落库、快照持久化、集群 watch 自动化、Raft 错误传播 59 tests + 跨节点 409 e2e
M6 模块化+gRPC 4 crate 归位(cli 仅组装)、tonic ConfigService(GetConfig/GetItem/Watch/ListMembers :8383)+ 鉴权 65 tests + gRPC 集成测试
M7 组级引用+密钥轮换 整组绑定共享组(物化/级联/解绑)、KeyRing 轮换(rotate API/CLI/重包任务/CRY-002) 74 tests + 轮换 HTTP e2e
M8 CI/基准/SBOM 8 jobs 全绿流水线、bench.sh 基准、SPDX SBOM、release 产物 CI 全绿 + 实测达标

最终工程指标(M8 终态)

  • cargo test --workspace:74 passed / 0 failed;clippy -D warnings 零警告;fmt 干净
  • GitHub Actions 8 jobs 全绿:lint / unit / contract / raft / sdk / e2e / bench / release
  • 基准实测:读 35016 QPS(本机)/ 9419(CI);写 1620 QPS(本机,单写者串行);watch 12ms;内存 41MB ≤128MB;release 二进制 8.68MB ≤50MB
  • SBOM:Defing-release.spdx.json(SPDX,44KB)
  • 端到端脚本全部通过:dev-single-demo、cluster-demo、chaos-test、sdk-contract-test、check-contracts
  • 仓库:https://github.com/ipconfiger/Defing(main,含完整设计文档 dev_docs/design-* 与 progress/remaining-work 进度)

M5 —— 会话落 Raft + 审计持久化 + 快照持久化 + 集群 watch 自动化 ✅ 完成(全部验证通过)

交付物(依据 dev_docs/remaining-work.md R1–R4)

  • B2 会话落 Raft(I7 集群级单会话)AdminSession{token_hash,issued_at,expires_at} 入状态机(sess/admin 键);Command::SessionLogin/Logout/Heartbeat;token 仅存 SHA-256 哈希(明文不落库/日志);auth_middleware 校验状态机会话;login 非 leader 自动跟随 leader_hint 转发到 leader 公开端点(跨节点唯一);--session-ttl(默认 24h)
  • Raft 错误传播修复(使能项)TypeConfig::R = u64Result<u64, Error>,状态机 apply 错误随客户端响应返回(之前被吞掉返回 0);client_write 拆出单次版 try_client_write + WriteError::ForwardToLeader{leader_hint} + leader_http_addr 兜底解析
  • B4 审计持久化Command::AuditAppend → 状态机写 audit/{seq:020}(seq 单调,audit/seq 计数键);get_audit(action/since/limit) 读库;GET /api/v1/audit 实现 openapi 参数;管理写操作全量审计(project_create/branch_create/draft_update/publish/structure_publish/rollback/cluster_join/cluster_promote/login/logout);AuditRetention 任务(--audit-retention 默认 100k)+ 版本裁剪任务接线(--version-retention,此前未接线)
  • B5 快照持久化:build/install 快照落盘 snapshots 列族(meta+data),get_current_snapshot 内存为空时读盘(重启不再从 leader 重拉全量)
  • B7 集群 watch 自动化tests/cluster.rs 新增 follower 订阅 sm_store.subscribe() 收到 leader 发布事件断言;新增 forward_hint 契约测试(learner ForwardToLeader 携带 leader http_addr)

M5 验收结果(全部通过)

验收项 结果
二次登录 409(跨节点,含 HTTP e2e) ✅ 核心测试 + cluster-demo 断言
kill leader 后会话保持(日志复制) ✅ chaos 重启后旧 token 仍有效
审计落库可查(含 action/since/limit 过滤) ✅ core 测试 + HTTP 冒烟
审计/会话跨重启持久化(RocksDB) ✅ 重启后条目仍在、旧 token 有效
快照跨重启恢复(get_current_snapshot 非 None) ✅ snapshot_persist 测试
集群 watch 自动化测试 ✅ cluster_watch_events_reach_subscribers
cargo fmt / clippy -D warnings ✅ 零警告
cargo test --workspace ✅ 59 passed / 0 failed(原 49 + 10)
回归:dev-single / cluster / chaos / sdk-contract / check-contracts ✅ 全过

工程记录

  • 会话 TTL 语义:状态机存 API 层注入的墙钟 expires_at(仅数据),过期判定在 auth_middleware 用墙钟比较;确定性由"命令内携带时间戳"保证
  • leader 转发:login 是唯一"无会话"写命令,转发到 leader 的公开 /api/v1/login(同密码,免新增内部端点,不引入未鉴权 apply 面);其余写命令按 design 返回 ERR_LEADER_REDIRECT + leader_hint
  • e2e 脚本适配集群级单会话:cluster-demo/chaos/restart-test 改为登录一次、token 全集群共享;重启后复用旧 token(会话持久化)
  • 坑:NodeInfo.http_addr 无 scheme → 转发 URL 需补 http://MutexGuard 非 Send,handler 内须作用域化(不能跨 await)

M6 —— 模块化归位(B1)+ gRPC 数据面(A1) ✅ 完成(全部验证通过)

交付物(依据 dev_docs/remaining-work.md P1 两项)

  • B1 模块化归位(4 个占位 crate 全部填充,cli 仅组装):
    • dsh-api(模块 05/09):ApiState + 全部 HTTP handler + 鉴权中间件 + 错误映射 + Admin UI(rust-embed)+ build_router;HTTP 路由与行为与迁移前完全一致
    • dsh-publish(模块 04):PublishService(提交前 secret 加密 I8 / publish / rollback / publish_structure / update_draft 编排)
    • dsh-observability(模块 10):AuditLog(审计落库)+ metrics_text + is_ready + cluster_members_json
    • dsh-watch(模块 06):WatchHub(广播 + sender + raft apply 转发)+ watch_sse
    • dsh-raft:通用写路径下沉为 write_command(dev-single 直 apply / 集群 client_write + leader 转发提示);main.rs 从 1469 行降至 ~300 行组装器
  • A1 gRPC 数据面(proto/config.v1.proto 落地):
    • tonic 0.14 + tonic-prost-build 生成(build.rs,protoc 3.19.6);ConfigService(GetConfig/GetItem/Watch/ListMembers)挂 --grpc-addr(默认 :8383)
    • proto ↔ 内部模型转换(Value oneof / 事件 / diff→Change);secret 脱敏 + masked 标记(数据面不解密)
    • Watch:after_version 历史重放(相邻快照 diff 合成事件)+ 实时事件去重
    • 鉴权:metadata authorization: Bearer <token>--data-plane-token,静态表 MVP,未配置开放);HTTP 保留为降级通道

M6 验收结果(全部通过)

验收项 结果
迁移后 HTTP 行为不变 ✅ dev-single / cluster / chaos / sdk-contract / check-contracts 全过
gRPC GetConfig/GetItem(含 secret 脱敏、NotFound) ✅ grpc_data_plane 集成测试(真实 TCP + 生成客户端)
gRPC Watch(after_version 续传 + 实时事件)
gRPC 鉴权(无 token 401 / 正确 token 通过)
gRPC ListMembers(dev-single → FailedPrecondition)
新 crate 单测(publish 加密 / watch 广播) ✅ 2+2+2
cargo fmt / clippy -D warnings ✅ 零警告
cargo test --workspace ✅ 65 passed / 0 failed
集群脚本每节点独立 gRPC 端口(88xx) ✅ 避免 3 节点争用 8383

工程记录

  • tonic-build 0.14 API 变更:prost 编译移至 tonic-prost-buildcompile_protos);生成代码依赖 tonic-prost crate
  • 生成类型名(Value/EventType/ChangeKind)与 dsh-core 冲突 → grpc.rs 内 core 类型别名(CoreValue 等)
  • gRPC 服务经 Server::builder().serve(addr) 挂载于独立 task;bind 失败仅告警不影响 HTTP
  • e2e 脚本无需改动(HTTP 面行为不变;仅 cluster 脚本补充 --grpc-addr 独立端口)

M7 —— 组级引用(B3)+ 密钥轮换(B6) ✅ 完成(全部验证通过)

交付物(依据 dev_docs/remaining-work.md P2 两项)

  • B3 组级引用(R6/AC6.2 完整语义)RefBinding.item_key=None = 整组绑定共享组
    • bind 校验:结构组存在 + 共享组内有 ≥1 个与结构组 item key 匹配的已发布共享项(否则 Validation)
    • 发布物化:结构组内 item 按 key 取共享值填充草稿缺失项(草稿显式值优先)
    • 共享发布级联:新增 idx/refg/{shared_group}/{project}/{group} 组级索引,共享项发布时按结构组 key 匹配推进引用项目分支版本
    • 解绑:组级 ref + 组级索引删除
  • B6 密钥轮换(design-v2 §7.5 / CRY-002)
    • dsh-crypto:KeyRing(旧 KEK 列表 + 当前 KEK,dek_v 代际对齐)、rotate_master_keyrewrap_dek(edek 重包,数据不重加密)、环文件持久化({key-file}.ring.json,重启可解旧数据)
    • POST /api/v1/admin/rotate-master-key(鉴权 + 审计 rotate_master_key + 环文件持久化)
    • CLI 客户端:dsh --gen-master-keydsh --rotate-master-key <b64> --admin-endpoint <url> --admin-token <t>(单会话下 login 会 409,故支持直接传 token)
    • RewrapDeks 后台任务(jobs,5min,leader-local 扫描快照/共享项/草稿重包代际 < 当前的密文,幂等)
    • 启动加载:master key 文件 + 环文件 → KeyRing(轮换后重启数据仍可解)

M7 验收结果(全部通过)

验收项 结果
组级绑定校验(无匹配共享项被拒) ✅ core 测试
组级物化(草稿值优先 + 匹配项取共享值) ✅ core 测试
组级级联(共享发布推进引用分支) ✅ core 测试(含多分支)
组级解绑停止物化 ✅ core 测试
rewrap_deks 扫描快照/共享/草稿重写 ✅ core + jobs 测试
轮换后旧数据可解(CRY-002) ✅ crypto 测试 + HTTP e2e(轮换后 my-secret-1 仍可解)
rewrap_dek 数据不变、edek 更新 ✅ crypto 测试
轮换 API + 审计 + 环文件 ✅ HTTP e2e(generation=2、ring 2 entries、审计 1 条)
重启后数据可解(环文件加载) ✅ HTTP e2e
轮换后新写入可解 ✅ HTTP e2e
cargo fmt / clippy -D warnings ✅ 零警告
cargo test --workspace ✅ 74 passed / 0 failed(原 65 + 9)
回归:dev-single / cluster / chaos / sdk-contract / check-contracts ✅ 全过

工程记录

  • 组级引用语义:整组绑定共享组 SG,物化/级联按"项目结构组 item key ∈ 共享组已发布项"匹配(key 集合对齐),与 item 级引用并存(各自索引前缀 idx/ref、idx/refg)
  • 轮换流程:operator 准备新 KEK(--gen-master-key)→ dsh --rotate-master-key --admin-token <t> 调 API → 节点 KeyRing 追加 + 环文件持久化 → RewrapDeks 任务(leader)逐节点重包;旧 KEK 常驻环(可解旧数据)直到 operator 手动清理
  • 坑:单会话(I7)下 CLI 客户端 login 会 409 → 增加 --admin-token 直接复用会话;e2e 中发布需满足必填项(草稿漏 host 导致 422,非缺陷)
  • rewrap 为 leader-local 写(与 VersionRetention 同模型):状态机与 raft 日志的既有关系下,快照安装/重启持久化保持一致;旧 KEK 兜底可解,最终一致

M8 —— CI 全量流水线(A2)+ 正式基准(A3)+ SBOM(A4) ✅ 完成

交付物

  • A2 CI 全量流水线(GitHub Actions,stage 1~9 全落地).github/workflows/ci.yml 8 jobs
    • lint(fmt/clippy -D warnings/deny)、unit(cargo test --workspace)、contract(三方契约 lint)
    • raft(集群/快照持久化/转发契约测试)、sdk(三语言契约对拍)、e2e(dev-single + 3 节点集群 + 混沌)
    • bench(基准冒烟归档)、release(--release 构建 + SBOM + 产物上传)
  • A3 正式基准scripts/bench.sh + Go 基准客户端(读 QPS / 写 QPS / watch 延迟 / 二进制 / 内存)
  • A4 SBOM:anchore/sbom-action(SPDX JSON,随 release 产物归档)

A3 基准实测(design-v2 §12 目标对照)

指标 本机(debug) CI runner 设计目标
读 QPS(GET /snapshot,200 并发) 35016 9419 未设读上限
写 QPS(草稿+发布,单写者串行 apply) 1620 1048 ≥10k(design 注:写路径单写者串行)
watch 延迟(发布→SSE) 12ms 22ms ≤1s ✓
内存 RSS 40MB 41MB ≤128MB ✓
release 二进制 8.68MB ≤50MB ✓

CI 修复记录(首次上云踩坑)

  • protoc 缺失:dsh-api/build.rs(tonic-prost-build)需要 protoc → 所有编译 job 补 arduino/setup-protoc
  • sdk 脚本硬编码 /home/alex 路径 → 改 $REPO(脚本相对仓库根解析)
  • sdk watch 首编窗口竞态:Go 首编 ~15s 错过 5 秒发布窗口 → 改为持续发布直到测试进程退出
  • 修复后:8 jobs 全绿(unit 74 tests / e2e 含 chaos / lint 零警告 / release+SBOM)

里程碑总览(M0–M8 全部达成):契约 → 单节点/集群 → 发布引擎/加密/渲染/任务 → watch/可观测/会话/UI → 三语言 SDK → 混沌/加固/发布 → 会话落Raft/审计/快照/集群watch → 模块化/gRPC → 组级引用/密钥轮换 → CI 全量/基准/SBOM


✅ 后 M8 收尾(P0–P3 补全 · 基于剩余工作清单与源码审计)

依据 dev_docs/remaining-work.md(M8 终态后重新审计)+ 上轮发现清单;每项实现→测试→验证闭环。

P0 —— 管理面契约补全 + secret 掩码策略 ✅

  • 11 个缺失 HTTP 端点(openapi 25→37 路径): 项目详情/删除(force)、分支详情/删除、分支对比 diff、值提升 promote、 共享库 CRUD + 发布(级联)、共享引用绑定/解绑、cluster/remove(voter RemoveVoters / learner RemoveNodes)
  • core 新增公共访问器:list_shared_published / list_shared_drafts / list_refs
  • /api/v1/cluster/members 补全成员表(对齐 openapi Member schema)
  • P0-b 安全修复:管理面/渲染/数据面默认掩码 secret;reveal=true 需会话+审计 (render 端点新增 reveal/version 参数;与 gRPC 数据面 masked 语义一致)
  • 验证:scripts/api-surface-test.sh(13 组断言全过);cluster-demo 增 remove-node 步骤

P1 —— 三语言 SDK gRPC 客户端 + Admin UI 控制台 ✅

  • gRPC 数据面客户端(TS/Go/Python):GetConfig/GetItem/Watch(断线 after_version 续传)/ ListMembers;Endpoint{grpc?,http?} 优先 gRPC,纯字符串降级 HTTP/SSE; TS 用 @grpc/proto-loader 动态加载;Go/Python 提交生成 stubs(configv1/、config/)
  • scripts/sdk-grpc-contract-test.sh:三语言对同一 :8383 契约对拍;CI sdk job 接入(补 setup-go 1.22)
  • Admin UI 管理控制台(单文件内嵌,替代原 72 行占位页):登录/登出、项目 CRUD、 结构草稿+发布、分支草稿表单编辑(含 secret)+发布、版本历史+回滚、分支对比+promote、 共享库 CRUD+发布+引用绑定、审计查询、SSE watch;prompt/confirm 改内嵌 modal; 切 tab 实时刷新。浏览器自动化全流程验证通过(含回滚 v3)
  • 移除重复的 dsh-cli/admin(实际嵌入源为 dsh-api/admin)

P2 —— CLI admin 子命令 + watch 增强 ✅

  • dsh admin {gen-master-key,rotate-master-key,force-logout,set-password,promote, remove-node,snapshot,retention-status}(全局 --admin-endpoint/--admin-token/--admin-password)
  • core 新增 Command::AdminSetPassword(哈希落状态机,集群一致;login 优先校验,回退节点配置); HTTP /api/v1/admin/{force-logout,set-password,snapshot,retention-status}(鉴权+审计)
  • watch 增强:SSE ?after_version=N 重放历史再转实时(与 gRPC 重放一致); 广播溢出(慢消费者)→ gRPC 发 snapshot_required=true 关流 / SSE 结束流(客户端续传)
  • 三语言 SDK HTTP watch 断线带 after_version 续传

P3 —— 指标/测试/stub/供应链 ✅

  • 指标 2→9 项:dsh_{projects,branches,versions,shared_items,shared_drafts, audit_entries,session_active,master_key_ok,raft_role,raft_term,raft_committed_index}
  • 测试补全:LIM-001 限额拒绝、AdminSetPassword 落库/读取;dsh-testkit 由占位改为真实夹具库 (demo_structure/seed_demo_project,grpc_data_plane 集成测试接入使用)
  • 删除 dsh-cli/src/lib.rs 占位;cargo-deny(server/deny.toml 许可白名单)+ CI deny step 去 no-op 兜底

终态指标(本机实测)

  • cargo test --workspace:76 passed / 0 failed;clippy -D warnings 零警告;fmt 干净
  • 全部 e2e 脚本通过:dev-single-demo / cluster-demo(含 remove-node)/ api-surface-test / sdk-contract-test(HTTP)/ sdk-grpc-contract-test(三语言 gRPC)/ check-contracts(37 paths)
  • Admin UI 浏览器自动化全流程验证(登录→建项目→结构→草稿→发布→对比→提升→回滚→共享→审计)

✅ 后 G5 收口(灰度端到端闭环 + D1 全落地 + 文档收口)

依据:dev_docs/remaining-work.md(review 检出 5 项未收口)逐项落地并验证。

  • 灰度 SDK 三语言适配(端到端最后一环):TS/Go/Python ConfigClient 增加 instance/labels 身份上报 (gRPC instance_id/labels + HTTP X-Dsh-Instance/X-Dsh-Labels)、响应/事件读取 gray/resolved_version、 watch「gray 事件永不按版本过滤」+ 断线 snapshot 拉取契约;Go/Python stubs 重新生成对齐 proto G3 字段。
  • gray-snap/ 回收:apply 路径(gray publish/promote/abort/结构发布 bump)删除旧序号灰度快照, 消除「随灰度发布累积」已知限制;新增 gray_snapshot_recycled_on_lifecycle 测试钉死(core 53 用例)。
  • 灰度 e2e 入 CIg1-policy-demo.sh / gray-demo.sh / gray-obs-demo.sh 三脚本加入 e2e job。
  • D1 全落地--watch-event-retain(进程内广播缓冲容量,默认 10000)+ --allow-no-master-key (启动强制 + 逃生阀;全部演示脚本/README 补 flag;api-surface-test/docker-compose 已自带主密钥无需改)。
  • 文档收口:remaining-work D1 闭环、g1-policy 默认 Stale 修订一致、gray-release §6 表回填、本 progress 段。

后 G5 收口(续):集群建群与恢复加固 ✅ 完成(全部验证通过)

交付物

  • join 重启/崩溃恢复幂等(对应踩坑 C3/C4,详见 dev_docs/defing-cluster.md):
    • dsh-cli:有持久化状态(raft-meta 非空)→ 忽略 --bootstrap/--join/--bootstrap-peers,直接 resume; join 收到 409(已在集群)视为幂等成功;join 命中 follower 跟随 428 leader_hint 切换目标;
    • dsh-api:/cluster/join 对已存在 learner 幂等成功(openraft add_learner 幂等 re-add), 已是 voter 保留 409(防劫持);非 leader 返回 428 + leader_hint(与写路径同约定);
    • 根因:join 端点对"已在成员表"的 node_id 返回 409 → 客户端 300ms 重试 30s → 崩溃循环。
  • 静态成员表建群 --bootstrap-peers(研究报告 dev_docs/research-cluster-bootstrap.md):
    • 三节点传完全相同三段式成员表(node_id@raft_addr@http_addr 必填),并行启动直接选举, 全员 voter,无需 join/promote;openraft 同 map 并发 initialize 安全(先到者首写, 其余收到良性 NotAllowed 后经复制追平);
    • 校验(启动即失败):三段式必填、raft/http 地址各自查重、拒绝 0.0.0.0/:: 通配、端口 1-65535、 本节点在 map 中且地址与本地参数一致;
    • A2:有状态且 seed 与持久化成员表不一致 → WARN 差异明细(不覆盖不阻断——成员表是共识复制数据, 单节点覆盖会分叉;运行期成员变更走 API,推倒重建先清卷);
    • B1:长时间无 leader(quorum 未达成)→ 15s 后每 10s 周期提示,消除静默空转。
  • 测试与回归:dsh-cli 解析/校验/diff 单测 5 项;dsh-raft 新增三节点静态 map 自举测试; dsh-api 新增 join 幂等集成测试(learner 重复 join 200 / voter 409 / 地址校验); scripts/seed-demo.sh(6 段全场景)入 CI e2e。

验证结果(全部通过)

检查 结果
cargo fmt --check ✅ 干净
cargo clippy --all-targets --all-features -- -D warnings ✅ 无警告
cargo test --workspace ✅ 全绿
scripts/seed-demo.sh(A1/A3 拒绝、B1 提示、建群、复制、同/异 seed 重启) ✅ 全过
scripts/cluster-demo.sh / chaos-test.sh / dev-single-demo.sh(回归) ✅ 全过
真实二进制冒烟:kill -9 后同命令重启 resume;seed 建群写 leader 三节点复制一致

文档:defing-cluster.md(C3 重写/C4 新增/两种建群方式/校验清单)、docker-compose.yml.demo 与 deploy/docker-compose.yml(seed 主推 + 坑 C1/C2/C3/C4 全部修正)、README(集群方式一 seed)、 research-cluster-bootstrap.md(设计 + 实施状态 + 实测修正)。