111 lines
13 KiB
Markdown
111 lines
13 KiB
Markdown
---
|
||
id: T-020
|
||
title: 建立 Bell 规则到 Alert 与 ack 的最小可见纵切
|
||
phase: 3
|
||
deps: [T-015, T-019]
|
||
status: DONE
|
||
created: 2026-08-11
|
||
issue: 71
|
||
context_ref: 477afa6ba2def34224f2dfeedc5b14421ec27650
|
||
claim_branch: claims/T-020
|
||
work_branch: agent/codex/T-020
|
||
write_paths:
|
||
- docs/tasks/T-020.md
|
||
- Bell/
|
||
- deploy/postgres/
|
||
- scripts/test_postgres.ps1
|
||
- tests/test_postgres_contract.py
|
||
- tests/test_bell_alert_contract.py
|
||
- docs/contracts/
|
||
- docs/00-ai-start-here.md
|
||
- docs/03-tech-stack.md
|
||
- docs/04-architecture.md
|
||
- docs/06-tasks.md
|
||
- docs/api.md
|
||
- docs/contracts/README.md
|
||
- docs/current-state.md
|
||
- docs/routes.md
|
||
---
|
||
|
||
## 问题 / 背景
|
||
|
||
T-019 已把 Brain 候选可靠地写成 Bell 不可变 Event,但 Bell 尚未消费 Event、记录规则版本、创建独立 Alert 或接受值班员 ack。现有 Bell 原型能说明目标交互,却没有可运行数据/API;因此客户仍看不到从匿名区域事件到“有人确认处理”的业务闭环。
|
||
|
||
本任务建立默认关闭、只允许回环联调的最小可见纵切:Bell 从不可变 Event 运行一个受限规则内核,为匹配事件幂等创建 Alert,使用 append-only 状态迁移实现首次 ack 竞争者获胜与确认后关闭,并通过自包含值班台展示真实 PostgreSQL 状态。它为后续升级链、双路径通知、证据切片和正式公共认证提供基础,但不提前伪造这些事实。
|
||
|
||
## 关联需求与交互(如适用)
|
||
|
||
- 用户故事:US-003(处置业务预警)、US-005(配置规则与升级链中的规则版本边界)。
|
||
- 交互清单:IX-005(预警到达)、IX-006(ack 竞争)、IX-007(只展示实际已有的升级/投递事实)、IX-008(Event/Alert 双向关联与证据降级)、IX-013(权限与隐私)。
|
||
- 相关页面 / 路由:复用已确认的 `docs/design/bell/index.html` 值班台信息架构;新增默认关闭的回环工程路由 `/bell-console/` 与 `/bell-console/api/v1/*`。这些路由不是待冻结的 Bell 公共 `/api/v1`,不冻结最终前端框架。
|
||
|
||
## 方案
|
||
|
||
1. 在 `docs/contracts/bell-alert-console-v1.openapi.json` 冻结回环工程 API:按状态分页列出 Alert、读取详情、`ack` 和 `close`。列表默认 16、最大 100,稳定按 `created_at DESC,id DESC`;tenant/Site/actor 只来自启动时的仓库外控制台上下文,不接受客户端自报。
|
||
2. PostgreSQL 新增 append-only `rule_versions`、`event_rule_sweeps`、`rule_evaluations`、`alerts`、`alert_events`、`alert_transitions` 和 `alert_command_receipts`。Rule version、evaluation、Alert 身份、Event 关联、状态迁移与幂等收据均禁止 UPDATE/DELETE/TRUNCATE;当前状态由最新 transition 推导,不把 Event 和 Alert 合并。
|
||
3. 首版规则内核只支持精确 `event_kind`、最小严重度、可选 Site 范围和生效时间。每个稳定 `rule_key` 的最新生效版本决定 enabled/disabled;配置从仓库外绝对 JSON 文件加载并按 canonical hash 幂等发布,不提供公共规则编辑 API,也不实现任意表达式或场景 DSL。
|
||
4. 规则 worker 扫描尚无 sweep 的不可变 Event;在单个 PostgreSQL 事务内写 sweep、每条已考虑规则的 evaluation、匹配 Alert、初始 `open` transition 与 Event 关联。并发 worker 通过唯一键和事务级 advisory lock 收敛;无规则/未命中也写 durable sweep,避免无限重试。崩溃前未提交可重做,提交后不得生成重复 Alert。
|
||
5. Alert ID 由 Bell 生成 `alt_` ULID。首版每个命中 Rule/Event 生成一个 Alert,但关系表按多对多建模;Alert 保存命中时的 rule version、标题与严重度快照,后续规则变化不改写历史。
|
||
6. 状态机固定为 `open -> acknowledged -> closed`。`ack` 的首个成功事务固化 actor/时间;并发后到者返回 `409 already_acknowledged` 并带当前处置人和时间,不覆盖、不双成功。`close` 只允许从 acknowledged 进入 closed。写请求必须携带 8~128 字符 `Idempotency-Key`;同 key/同命令重放原响应,同 key/不同命令稳定冲突。
|
||
7. `bell-api` 以 `BELL_ALERTS_ENABLED=true` 显式启用规则 worker;规则文件必须是仓库外绝对路径。值班台另以 `BELL_ALERT_CONSOLE_ENABLED=true` 启用,只允许整个 Bell HTTP 监听地址为显式回环,并要求仓库外 token 文件、正整数 tenant/Site 与受限 actor ref。两项默认关闭,控制台 token 只进入页面内存和 `no-store` 响应,不记录。
|
||
8. `Bell/web/` 使用 Go `embed`、HTML/CSS/原生 JavaScript 实现,不增加前端依赖或 CDN。页面显示真实 Alert 队列、关联 Event、规则版本和 append-only 时间线;无证据时明确标注“证据切片尚未启用”,无升级/投递实现时不显示虚构倒计时或成功状态。ack/close 有 loading、成功、并发冲突、可重试错误与无权限反馈,颜色不是唯一状态表达;375px 与桌面均可操作。
|
||
9. 增加 Go 域/handler/store 测试、静态 OpenAPI/UI 契约测试,以及隔离 PostgreSQL migration replay、最小权限、规则幂等、并发 evaluation、并发 ack、幂等重放和重启后状态恢复验证。当前会话无 codebase-memory 图工具,代码发现降级为定向读取和 `rg` 并记录在执行证据。
|
||
|
||
## 不可变约束
|
||
|
||
- 阈值 / 数值边界:Alert 列表默认 16、最大 100;16 不是业务上限,站点仍允许 1~128 路。规则文件和控制台 token 均限制大小;actor/rule/idempotency 字段有显式长度与字符集边界。worker 必须可取消且空闲轮询有界,不按设备或 tenant 创建 goroutine。
|
||
- 判定式 / 状态转换:Rule 最新生效版本决定是否启用;严重度顺序固定为 `low < medium < high < critical`。每个 `(event_id, rule_version_id)` 最多一个 evaluation/Alert;`open -> acknowledged -> closed` 之外的迁移拒绝。首次 ack 获胜,后到者不得覆盖。Event、Rule version、Alert 事实和所有 transition 均 append-only。
|
||
- 安全边界:功能默认关闭;工程控制台只允许显式回环绑定。token、规则配置、DSN 与客户数据均在仓库外;页面/API/日志不得返回 Event 原始 payload、流 URI、凭据、内部 DSN 或 token。tenant/Site/actor 不来自请求体、查询参数或可修改浏览器存储。
|
||
- 既有契约:冻结 Event v0.1、T-019 ingress、Bell 事件 ULID/不可变性和 Sense/Bell schema 所有权保持不变。Alert 是独立实体并通过关系表关联 Event;本任务不修改 `docs/raw/contracts/event-v0.1.schema.json`。
|
||
|
||
## 验收要点
|
||
|
||
- 任务相关验证:`go -C Bell test ./...`、`go -C Bell vet ./...`、`go -C Bell build ./...`、`python -m unittest discover -s tests -p "test_bell_alert_contract.py" -v`、`python -m unittest discover -s tests -p "test_postgres_contract.py" -v`、`./scripts/test_postgres.ps1 -PgRoot D:\pgsql17`。
|
||
- 完整门禁:运行 `./init.ps1`、三项治理基线、`git diff --check`。PostgreSQL migration 必须从空库执行两遍;8 个并发 evaluator 对同 Event/Rule 只创建 1 个 Alert,8 个并发 ack 只有 1 个成功且其余观察同一处置人;同幂等 key 重放稳定,重启后状态/时间线不丢失;运行角色不能改写或删除任一规则/Alert 历史事实。
|
||
- 人工 / 设备验收:不需要摄像头、GPU、客户网络或通知供应商。页面沿用已由产品确认的 Bell 原型信息架构;任务责任人在本地以合成 Event 检查 375px/桌面、键盘、加载/空态/成功/冲突/错误和 reduced-motion。该工程验收不等于正式 Web/H5、JWT/RBAC、算法效果或生产 SLA 验收。
|
||
- 构建产物:Bell `bell-api`、嵌入式 `/bell-console/`、OpenAPI 和 PostgreSQL migration;不提交运行时规则文件、token、数据库或事件样本。
|
||
|
||
## 边界(不改什么)
|
||
|
||
不实现证据/pre-roll 切片、MinIO/S3、升级计时/重启续跑、联系人/排班、通知 provider、短信/语音/Webhook、本地声光、静默、交接班、误报反馈、正式公共 JWT/OIDC/RBAC、正式 Bell 前端框架、规则编辑器/试运行/回滚、生产模型或多路容量压测;不修改 Sense、Brain、MediaMTX、`_reference/` 或冻结 Event v0.1。
|
||
|
||
## 协作约束
|
||
|
||
- 责任 Agent:codex。
|
||
- 唯一写入者:codex。
|
||
- 委派:不启用。
|
||
- Gitea:任务定义先合入默认分支;创建唯一 Issue 并完成双向映射后,由 dispatcher 串行领取并回填 `context_ref`、claim / 工作分支。
|
||
|
||
任何新增写路径先检查与其他活跃任务是否重叠;同一时刻只有一个 Agent 修改本任务的 `write_paths`。
|
||
|
||
## 执行记录
|
||
|
||
### 2026-08-11 任务定义
|
||
|
||
- T-020 独立冻结规则扫描、Alert 身份/关系和首次 ack 竞争语义;证据、升级/投递和正式公共认证继续拆分,不用占位状态伪装完成。
|
||
- 复用已确认 Bell 原型及 US-003/US-005、IX-005~IX-008/IX-013;工程值班台不冻结最终前端框架。
|
||
- 当前会话未提供 codebase-memory MCP 图工具,按仓库规则降级为定向读取与 `rg`。任务定义前 `./init.ps1` 基线通过:77 项根测试、30 项 Brain 测试,以及 Sense/Bell generate/test/vet/build 全绿。
|
||
- 任务定义已合入默认分支并创建唯一 Gitea Issue #71;本映射合入默认分支后才允许添加 `status/todo` 并由 dispatcher 分配。
|
||
|
||
### 2026-08-11 领取
|
||
|
||
- dispatcher `ila` 已在 Issue #71 核对依赖、写路径与活跃任务,并分配给 `codex`。
|
||
- 基线提交:`477afa6ba2def34224f2dfeedc5b14421ec27650`;claim 分支:`claims/T-020`;工作分支:`agent/codex/T-020`。
|
||
|
||
### 2026-08-11 实现与自动化验收
|
||
|
||
- PostgreSQL 新增可重放 `018`~`019`,Bell schema 提升到 v6:规则版本、Event sweep、rule evaluation、Alert 身份、Alert/Event 多对多关系、transition 和命令收据全部只追加;`bell_runtime` 仅有 `SELECT/INSERT`,数据库 trigger 同时拒绝 owner 路径的意外 UPDATE/DELETE。Alert 对 `rule_evaluation_id` 唯一,数据库层保证一个 Event/Rule evaluation 最多一个 Alert。
|
||
- 规则配置从仓库外绝对 JSON 加载,限制 256 KiB/256 条、拒绝未知字段和重复 key;相同 canonical SHA-256 幂等复用版本。单一可取消 worker 使用 durable sweep 与事务级 advisory lock,多实例对同 Event 收敛;无规则和 no-match 同样落 sweep,规则发布不追溯重算已经 sweep 的历史 Event。
|
||
- Bell 生成 `alt_` ULID,命中 evaluation、Alert、Event 关联和初始 `open` transition 同事务提交。状态只允许 `open → acknowledged → closed`;永久命令收据按 tenant/idempotency key 重放原 status/body,同 key 改命令/actor/note 稳定冲突。并发后到的 ack 返回 `409 already_acknowledged` 和真实首位 actor/time,不覆盖历史。
|
||
- 冻结并实现 `bell-alert-console-v1` 回环工程 API:列表默认 16/最大 100,tenant/Site/actor 只取启动上下文;Bearer token 来自仓库外文件并用 constant-time 比较。规则 worker 与控制台分别默认关闭;控制台要求整个 Bell 监听地址显式回环,不返回 Event payload、流 URI、凭据、DSN 或 token。
|
||
- `Bell/web/` 使用 Go embed、自包含 HTML/CSS/原生 JavaScript,无 npm/CDN/浏览器持久存储。页面显示真实规则版本、关联 Event 和 append-only 时间线;证据、升级/投递明确为未启用。按 `ui-ux-pro-max` 检查落实骨架加载、空态/重试、冲突/无权限反馈、颜色+文字、44px target、键盘入口、`aria-live` 和 reduced-motion。
|
||
- Edge headless + 脱敏回环 fixture 完成 1440×900 与 CDP 375×812 渲染检查:桌面双栏、移动单栏均可操作,375 viewport 的 `innerWidth/scrollWidth/bodyWidth` 均为 `375`。首轮浏览器 QA 发现 `.workspace{display:grid}` 覆盖 `hidden` 导致授权前泄露空壳布局,已增加全局 `[hidden]{display:none!important}` 并复验;临时 fixture、token 和截图均未进入仓库。
|
||
- `./init.ps1` 最终通过:83 项根测试、30 项 Brain 测试,以及 Sense/Bell generate/test/vet/build 全绿。独立 Bell test/vet/build、OpenAPI JSON、5 项 Alert 静态契约、11 项 PostgreSQL 静态契约和 `node --check Bell/web/assets/app.js` 通过。
|
||
- `./scripts/test_postgres.ps1 -PgRoot D:\pgsql17` 最终通过:PostgreSQL 17.10 随机回环临时集群将 `001`~`019` 连续重放两遍;真实 repository 验证 rule publish/no-match、8 个 evaluator 只生成 1 个目标 Alert、8 个并发 ack 只有 1 个成功、late ack 观察同一 actor/time、ack 前 close 拒绝、ack 后 close、幂等冲突、重启恢复和不可变权限。临时集群已停止清理,现有 5432 listener 未改变。
|
||
- 本任务不需要摄像头、GPU、客户网络或通知供应商;结果不等于正式 Bell Web/H5、公共 JWT/RBAC、证据/通知、算法效果、真实多路或生产 SLA 验收。
|
||
|
||
### 2026-08-11 收尾
|
||
|
||
- 工作提交 `8208118` 已推送并创建 PR #73;Issue #71 的实现证据、提交、分支和任务文件一致。
|
||
- 自动化、隔离 PostgreSQL 与浏览器工程验收满足本任务门禁,无额外摄像头/客户网络人工门禁;任务标记 DONE,允许合并并关闭 Issue。
|