Merge pull request '[T-016] 定义 Sense 到 Bell 审计 relay 任务' (#54) from bootstrap/T-016 into main
Harness governance / validate (push) Has been cancelled

This commit was merged in pull request #54.
This commit is contained in:
ila
2026-08-10 23:58:23 +08:00
+96
View File
@@ -0,0 +1,96 @@
---
id: T-016
title: 冻结并实现 Sense Outbox 到 Bell 全局审计幂等 relay
phase: 3
deps: [T-015]
status: TODO
created: 2026-08-10
issue: null
context_ref: null
claim_branch: null
work_branch: null
write_paths:
- docs/tasks/T-016.md
- docs/contracts/README.md
- docs/contracts/sense-audit-relay-v1.openapi.json
- Sense/cmd/sense-api/main.go
- Sense/internal/config/config.go
- Sense/internal/config/config_test.go
- Sense/internal/auditrelay/
- Sense/internal/store/audit_relay_postgres.go
- Sense/internal/store/audit_relay_postgres_test.go
- Sense/README.md
- Bell/cmd/bell-api/
- Bell/internal/audit/
- Bell/internal/store/audit_postgres.go
- Bell/internal/store/audit_postgres_test.go
- Bell/README.md
- deploy/postgres/014_audit_relay.sql
- deploy/postgres/015_privileges_audit_relay.sql
- deploy/postgres/tests/assertions.sql
- deploy/postgres/README.md
- scripts/test_postgres.ps1
- tests/test_sense_audit_relay_contract.py
- tests/test_postgres_contract.py
- docs/00-ai-start-here.md
- docs/03-tech-stack.md
- docs/04-architecture.md
- docs/06-tasks.md
- docs/api.md
- docs/current-state.md
---
## 问题 / 背景
T-010 已保证 Sense 设备创建、配置和期望态变更与脱敏 `sense.device_operation_outbox` 同事务,但 Outbox 仍不会离开 Sense;Bell 因而没有完整的跨系统全局审计事实。直接让 Sense 写 `bell` schema 会破坏三系统所有权,简单“POST 后标记成功”又无法处理崩溃重试、重复投递、多实例竞争、消息篡改或重放攻击。
T-015 已提供 Bell 的独立运行角色和不可变事实模式。本任务只冻结并实现设备操作审计 relay,不顺带定义 Brain 事件 transport、业务规则、Alert 或公共管理 API。
## 关联需求与交互(如适用)
- 用户故事:为 US-009、US-010 的全局审计追溯提供后端事实基础。
- 交互清单:IX-020 的审计归属后端基础;本任务不实现 `/audit` UI、筛选或深链页面。
- 相关页面 / 路由:公共 UI 不适用;新增仅内部使用的 `/internal/v1/audit-events:batch`。
## 方案
1. 冻结 `sense-audit-relay-v1.openapi.json`:Sense 通过 HTTP POST 批量发送 1~100 个 v1/v2 设备审计事实,Bell 返回与输入逐项对应的 `accepted | duplicate | rejected`;完整批次响应可按签名 nonce 幂等重放。
2. 请求使用 key ID、Unix 秒时间戳、随机 nonce 与 HMAC-SHA256。canonical string 固定为 `METHOD + path + timestamp + nonce + SHA256(body)` 的换行拼接;允许时钟偏差 5 分钟,nonce 收据保留 10 分钟。相同 key/nonce/请求摘要返回原响应,不同摘要返回 `409 replay_conflict`。密钥只从仓库外绝对路径文件读取,至少 32 个随机字节,不进数据库、日志或 Issue。
3. 非回环 transport 必须使用 HTTPS;回环 HTTP 只用于同机私有部署和测试,不提供跳过远端 TLS 的 flag。单次请求体最大 1 MiB、超时 10 秒,Bell 不回显原始 body、签名或敏感字段。
4. PostgreSQL v6 为 Sense Outbox 增加数据库时钟 lease、fencing token、last error 和 dead-letter 状态。最多 100 行用 `FOR UPDATE SKIP LOCKED` 领取;过期 worker 不能确认。`accepted/duplicate` 标记 delivered,逐项永久拒绝进入 dead letter,网络/5xx/整批认证失败按指数退避重试且不删除事实。
5. Bell 新增不可变 `bell.audit_events` 和可过期 `bell.audit_relay_receipts`。全局审计用 `(source_system,event_id)` 唯一;相同摘要为 duplicate,不同摘要逐项 `id_conflict`。审计事实没有 UPDATE/DELETE/TRUNCATE 权限并复用不可变 trigger;收据允许 Bell runtime 在限定表内维护过期记录。
6. 建立最小 `cmd/bell-api`,默认仅回环监听,暴露 health/ready 与内部 relay endpoint;数据库、监听地址、TLS 和 key 文件均来自私有环境。Sense relay 默认关闭,仅在 PostgreSQL v6、合法 Bell URL 和外部 key 文件齐备时启动后台 worker。
7. 单元测试覆盖签名向量、时间窗/nonce、body 限制、逐项确认、重复/冲突、配置失败关闭、退避和崩溃重领;隔离 PostgreSQL 17.10 测试覆盖 migration 重放、两端 repository、fencing、权限和安全清理。
## 不可变约束
- 阈值 / 数值边界:每批 1~100 项;body ≤1 MiB;HTTP deadline 10 秒;lease 30 秒;签名时钟偏差 ≤300 秒;nonce 收据 TTL 600 秒;退避从 1 秒指数增长并封顶 300 秒。16 不是审计批量或队列上限。
- 判定式 / 状态转换:只有 `accepted`/`duplicate` 可设置 `delivered_at`;`rejected` 必须带稳定错误码并 dead-letter;批次级网络/5xx/认证/响应缺项不得误标成功。所有完成/失败写入必须匹配 worker + fencing token + 未过期数据库 lease。
- 安全边界:非回环只允许 HTTPS;HMAC 使用至少 32 字节外部 secret 和 constant-time compare;时间戳、nonce、key ID、签名格式失败统一安全拒绝。Sense 不获得 Bell schema 权限,Bell 不读取 Sense Outbox;日志、响应、测试和指标不包含 secret、Authorization、原始 DSN、完整 payload 或租户/设备标签。
- 既有契约:本地事实继续符合 `sense-device-audit-v1/v2`,不原地修改两份 schema;relay v1 只增加传输 envelope/确认语义。Bell 全局审计是追加事实,不能改写 Sense 原事件或设备状态。
## 验收要点
- 任务相关验证:`go -C Sense test ./internal/auditrelay ./internal/store ./internal/config ./cmd/sense-api`、`go -C Bell test ./internal/audit ./internal/store ./cmd/bell-api`;`python -m unittest discover -s tests -p "test_sense_audit_relay_contract.py" -v`;OpenAPI JSON 可解析且签名/状态/阈值与实现一致。
- 完整门禁:因命中 Sense/Bell Go、PostgreSQL schema/权限和内部 HTTP 安全边界,运行 `./scripts/test_postgres.ps1 -PgRoot D:\pgsql17`、`./init.ps1`、三条 Python 治理命令及 `git diff --check`。临时 PostgreSQL 必须重放 `001`~`015` 两次,现有 5432 listener 不变。
- 人工 / 设备验收:不适用;回环 `httptest` 和隔离 PostgreSQL 足以验收协议与持久化。跨主机证书、客户网络、防火墙和密钥轮换演练留给部署任务,不能据此宣称生产网络已验收。
- 构建产物:`go -C Sense build ./...` 与 `go -C Bell build ./...`;Bell 最小 receiver 可由 `go -C Bell build ./cmd/bell-api` 构建,未配置 DSN/key 时必须拒绝启动。
## 边界(不改什么)
- 不实现 Brain→Bell 事件 transport、规则引擎、Alert/ack/升级、Bell 公共审计查询 API、Web/H5、JWT/OIDC、消息总线或客户网络部署。
- 不删除本地 delivered/dead-letter Outbox 行,不擅自冻结法务审计留存期;本版本只自动清理 10 分钟后 relay nonce 收据,全局审计事实不自动删除。
- 不提交 key 文件、DSN、客户标识、真实 payload 或私有实例配置;不为测试修改现有 PostgreSQL data 目录或 5432 服务。
## 协作约束
- 责任 Agent:codex
- 唯一写入者:codex
- 委派:不启用。
- Gitea:Issue、`context_ref`、claim 与工作分支在双向映射及 dispatcher 分配后回填。
任何新增写路径先检查与其他活跃任务是否重叠;同一时刻只有一个 Agent 修改本任务的 `write_paths`。
## 执行记录
- 2026-08-10:在 T-015 合并并关闭后拆出本任务;实现尚未开始。