docs(tasks): define T-015 Bell immutable events
Harness governance / validate (push) Has been cancelled
Harness governance / validate (pull_request) Has been cancelled

This commit is contained in:
QiuSW
2026-08-10 23:36:01 +08:00
parent 5ab212cb5c
commit cefa6275bf
+85
View File
@@ -0,0 +1,85 @@
---
id: T-015
title: 建立 Bell 事件 v0.1 校验与不可变 PostgreSQL 存储
phase: 3
deps: [T-014]
status: TODO
created: 2026-08-10
issue: null
context_ref: null
claim_branch: null
work_branch: null
write_paths:
- docs/tasks/T-015.md
- Bell/
- deploy/postgres/012_bell_events.sql
- deploy/postgres/013_privileges_bell_events.sql
- deploy/postgres/tests/assertions.sql
- deploy/postgres/README.md
- scripts/test_postgres.ps1
- tests/test_bell_event_contract.py
- docs/raw/contracts/README.md
- 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
- AGENTS.md
- init.ps1
- init.sh
---
## 问题 / 背景
M3 要求 Bell 首次真实消费冻结的事件契约 v0.1,生成平台 ULID,并把事件事实保存为不可变记录。当前 Bell 只有目录占位,PostgreSQL 也只有 Site/Area 投影源表;如果先做规则或 Alert,会把未经校验、可被覆盖的事件数据变成后续状态机的错误基础。
冻结契约中还存在一处文档自相矛盾:`docs/raw/contracts/README.md` 的修订记录、雷达示例和 JSON Schema 均允许非成像事件的 `snapshot_uris=[]`,但 §5 的旧句仍把空数组列为 schema 可拒绝项。本任务先把该句校正为现有冻结事实,不修改 schema 或字段语义。
## 关联需求与交互(如适用)
- 用户故事:无直接 UI;为 US-003、US-004、US-006 的后续事件/处置流程提供数据基础。
- 交互清单:不适用;本任务不实现 Bell Web/H5 页面或公共查询 API。
- 相关页面 / 路由:不适用。
## 方案
1. 在 `Bell/` 建立独立 Go module,复用项目冻结的 Go 1.26.5、PostgreSQL 17.10 和 `pgx/v5 v5.10.0`;冻结 Draft 2020-12 校验器与 ULID 库版本、许可证和退出路线,不引入前端或消息总线。
2. 把冻结 schema 逐字节复制到 `Bell/contracts/event-v0.1.schema.json` 并以测试阻止漂移。Bell 内部 ingest factory 接收不含平台 `id` 的候选事实,拒绝上游自报 `id`,由 Bell 生成 `evt_` ULID 后再执行 schema 与代码级断言。Brain→Bell 的 HTTP/消息总线 transport 继续待定,本任务不借内部 Go 类型冻结公共网络协议。
3. 代码级校验覆盖时间顺序/延迟自洽、`confidence=null`、证据 URI 脱敏、唯一 primary 且与顶层设备一致,以及视频隐私准入。隐私与客户敏感名称通过必需 port 注入;解析/映射不可用时失败关闭,不使用 allow-all 生产默认值。
4. 新增 Bell event repository。`bell.events` 保存最终规范化 JSON、摘要和可查询的最小索引字段;平台 ID 冲突时仅允许“同 ID + 同摘要”的幂等重放,不同摘要稳定冲突。后续 outcome 写入独立的 append-only `bell.event_outcomes`,不得更新原事件事实。
5. migration 增加独立 NOLOGIN `bell_runtime` 最小权限角色。migration owner `bell_app` 持有对象;运行时只获得必需的 `SELECT/INSERT`,明确没有事件表的 `UPDATE/DELETE/TRUNCATE`,并以数据库 trigger 拒绝事实改写。生产/共享实例仍由管理员离线 migration,不由 Bell 高权限自迁移。
6. 扩展隔离 PostgreSQL 17.10 harness,使用 `bell_runtime` 登录运行真实 repository/权限/重放/不可变性集成测试;同步根启动入口、技术栈、架构、API 状态和当前仓库现实。
## 不可变约束
- 阈值 / 数值边界:请求体/单事件规范化 JSON 上限 1 MiB;延迟自洽误差严格小于 100 ms;事件列表/公共 API 不在本任务内;任何默认 16 路值不成为事件数量上限。
- 判定式 / 状态转换:Bell 是 `evt_` ULID 唯一生成者;候选事实不得携带 `id`;最终事实必须同时通过 Draft 2020-12 schema 和 README 六项代码级断言。相同平台 ID/相同摘要为幂等成功,相同 ID/不同摘要为冲突;原事件永不 UPDATE/DELETE,后续 outcome 只能追加。
- 安全边界:未知顶层字段拒绝;视频隐私映射或敏感名称策略不可用时失败关闭;普通错误和测试证据不回显事件全文、证据 URI、客户名、凭据或内部堆栈;`bell_runtime` 不拥有 migration 对象且没有事件事实改写权限。
- 既有契约:`docs/raw/contracts/event-v0.1.schema.json` 字段、枚举与 v0.1 语义不变;只修正文档中与 schema/示例冲突的旧句。Brain transport、规则、Alert、证据回捞、RBAC/JWT/OIDC 和事件公共 API 均另立任务。
## 验收要点
- 任务相关验证:`go -C Bell test ./...`、`go -C Bell vet ./...`、`go -C Bell build ./...`;`python -m unittest discover -s tests -p "test_bell_event_contract.py" -v`;验证三个冻结示例、负向 schema/六断言、ULID 所有权、幂等冲突和 append-only outcome。
- 完整门禁:因命中 Bell 脚手架、PostgreSQL migration、权限、根启动入口和冻结契约消费者,运行 `./scripts/test_postgres.ps1 -PgRoot D:\pgsql17`、`./init.ps1`、三条 Python 治理命令和 `git diff --check`。隔离测试必须证明现有 5432 listener 前后不变并自动清理临时集群。
- 人工 / 设备验收:不适用;本任务不依赖摄像头、客户网络、GPU、MinIO 或产品 UI。生产上线仍受法务/客户留存政策和 M3 现场 dry-run 门禁约束。
- 构建产物:`go -C Bell build ./...` 可构建全部 Bell 包;不生成可部署 Bell API 二进制,因为 Brain transport、认证和公共 API 尚未冻结。
## 边界(不改什么)
- 不实现 Brain mapper/投递、HTTP 或消息总线事件入口、规则引擎、Alert、ack、升级链、对象存储、证据切片、反馈回流、Bell Web/H5 或 JWT/OIDC。
- 不修改冻结事件 schema、示例字段或 Sense 业务代码;不把整数事件契约 ID 与现有文本逻辑 ID 的待定映射悄悄固化为跨系统规则。
- 不把 `bell_app` owner 凭据交给运行进程,不在仓库保存 DSN、密钥、客户数据、真实事件或真实证据 URI。
## 协作约束
- 责任 Agent:codex
- 唯一写入者:codex
- 委派:不启用。
- Gitea:Issue、`context_ref`、claim 与工作分支在双向映射及 dispatcher 分配后回填。
任何新增写路径先检查与其他活跃任务是否重叠;同一时刻只有一个 Agent 修改本任务的 `write_paths`。
## 执行记录
- 2026-08-10:按 M3 建议拆出本任务;完成仓库/远端状态检查和 `./init.ps1` 基线,现有 58 个 Python 测试及 Sense generate/test/vet/build 全部通过。实现尚未开始。