Files
yovision/docs/contracts/README.md
T
QiuSW 8208118904
Harness governance / validate (pull_request) Has been cancelled
feat(bell): add alert acknowledgement vertical slice
2026-08-11 17:01:53 +08:00

14 KiB
Raw Blame History

YoVision 控制面、审计与内部事件传输契约 v1

冻结日期:2026-08-11。Control API、审计 relay、Brain 事件 ingress 与 Bell 回环 Alert 控制台契约版本:1.0.0。Sense 是设备期望态的提供方;Bell 是 Tenant、Site、Area、RBAC、配额、事件、Alert 与全局审计的所有者。T-016 已实现 Sense Outbox 审计 relay,T-019 已实现 Brain 事件 Outbox ingress,T-020 已实现 Bell 规则→Alert→ack/close 工程纵切;Bell 公共管理服务与 JWT/OIDC 仍未实现。

契约文件

文件 生产者 / 所有者 消费者 用途
sense-control-v1.openapi.json Sense Bell 管理面、受控集成方 设备查询、创建、修改、启停与批量操作
site-quota-v1.sql Bell Sense 单 PostgreSQL 实例内的站点视频配额只读投影
area-policy-v1.sql Bell Sense Area 归属与 capture_policy 只读投影
sense-device-audit-v1.schema.json Sense 本地 Outbox;Bell relay 脱敏设备操作审计事实,不包含传输协议
sense-device-audit-v2.schema.json Sense 本地 Outbox;Bell relay v1 后继,增加脱敏配置修改受理事实;v1 文件保持不变
sense-audit-relay-v1.openapi.json Bell Sense 内部批量端点、HMAC、逐项确认、nonce 防重与重试边界
brain-event-ingress-v1.openapi.json Bell Brain 单业务事件入站、producer/key 绑定、Bell ID 与跨重启幂等语义
bell-alert-console-v1.openapi.json Bell 回环工程值班台 Alert 分页/详情、首次 ack、确认后关闭与永久幂等收据

OpenAPI 的 /api/v1 路径是公共控制面边界;/healthz、/readyz 仍是非业务运维探针。v1 不提供设备删除:停用设备使用期望态接口,保留设备、操作和审计历史。Site、Area、配额、RBAC 和审计聚合不由 Sense 提供 CRUD。

HTTP 资源与操作

操作 路径 关键约束
列出 / 创建设备 GET/POST /api/v1/sites/{site_id}/devices 列表使用不透明 cursor,默认 50、最大 100;创建要求 Idempotency-Key,设备 ID 由服务端生成
读取 / 修改设备 GET/PATCH /api/v1/sites/{site_id}/devices/{device_id} 修改要求 If-Match,版本不匹配返回 412 etag_mismatch
修改期望态 PUT /api/v1/sites/{site_id}/devices/{device_id}/desired-state 要求 If-Match;受理不表示实际态已经收敛
批量修改期望态 POST /api/v1/sites/{site_id}/devices:batchDesiredState 要求 Idempotency-Key,最多 128 项,异步返回逐项结果
查询批量操作 GET /api/v1/operations/{operation_id} 只返回当前租户和站点可见的操作

列表按 created_at ASC, id ASC 稳定排序,cursor 是服务端生成的不透明位置标记。客户端不得解析或拼接 cursor;服务端可以在兼容范围内改变编码。批量不是跨设备全有或全无事务:每项独立接受或拒绝,成功项继续收敛,失败项带稳定错误码。请求内重复 device_id 视为对应项 invalid_request,不得用“最后一项覆盖”。重试失败项应使用新幂等键;原样重放整个请求必须返回原收据。

身份、租户与敏感信息

  • 所有 /api/v1 操作都需要 Bearer 认证;具体 token 格式由认证任务冻结。tenant_id 只从认证上下文取得,body、query 和 path 均不能自报 tenant。
  • 跨租户访问与资源不存在都返回 404 not_found,不得用状态码、消息或耗时泄露资源是否存在。授权范围不足但不涉及资源枚举时返回 403 forbidden。
  • endpoint_ref、credential_ref 和 profile_token 是 write-only 输入。不允许 userinfo 形式的完整 RTSP URI;响应、错误、日志与示例不得包含这些引用、密码、token、完整连接串或 MediaMTX 内部配置。
  • 创建设备显式提交 modality + capabilities;video 模态必须包含 video_capture,任何请求了 video_capture 的设备都必须同时提供 endpoint 与 credential 引用。服务端仍须由适配器验证能力,不能把客户端声明当作探测成功。
  • ETag 表示设备资源版本;If-Match 缺失返回 428 precondition_required,过期版本返回 412 etag_mismatch。相同期望态重复提交不增加 generation,但每次受理仍可产生审计记录。

Idempotency-Key 的作用域是“认证主体 + tenant + site + operation + key”,服务端至少保存 24 小时。同作用域、同请求体重放返回首次状态码和响应;同 key 不同请求体返回 409 idempotency_conflict。幂等收据不等价于实际态完成。

配额投影与准入

Bell migration 最终创建 bell.site_quota_v1,列顺序和含义固定如下:

列 含义
tenant_id、site_id 与两个 schema 共享的稳定逻辑 ID
max_video_channels 默认 16,有效范围 1~128
source_version 站点投影每次变更后单调递增的版本
source_updated_at Bell 源记录的更新时间

视图由 bell_app 拥有;sense_app 只有 Bell schema 的 USAGE 和该视图的 SELECT,没有 INSERT、UPDATE、DELETE 或 Bell 源表权限。Sense 不得通过任何旁路写 Bell schema。首期使用同一 PostgreSQL 实例及 sense/bell schema;如果未来分库,必须发布新版本的网络契约,不能在 v1 下静默改变一致性和失败语义。

配额只统计 desired_state=enabled 且 capabilities 含 video_capture 的设备。创建已启用视频设备或把视频设备启用时,Sense 必须在同一设备写路径读取并记录所用 source_version,同时验证 Area 策略投影。降低配额不会自动停用已有设备;若当前占用已超限,后续创建/启用返回 409 quota_exceeded。配额行缺失、越界、版本回退或投影不可读时返回 503 quota_projection_unavailable,只阻止相关创建/启用,读取、非准入属性修改和停用仍允许,已有流保持运行。

T-010 冻结 bell.area_policy_v1 的列顺序为 tenant_id/site_id/area_id/capture_policy/source_version/source_updated_at,策略仅允许 video_allowed | non_imaging_only。Sense 对所有 PostgreSQL 新建设备验证 Area 归属;具有 video_capture 能力的设备在创建(包括 disabled 创建)和启用时检查策略。缺失、非法、版本回退或不可读映射为 area_policy_unavailable,non_imaging_only 拒绝成像变更并映射为 area_policy_denied。同库视图实时读取,不把长期未修改记录的 source_updated_at 年龄误判为过期。

本地设备操作审计

sense-device-audit-v1.schema.json 继续冻结创建与期望态两类事实且不原地扩展严格枚举。T-011 新增 v2 后继,兼容 v1 两类事件并增加 device.configuration.accepted;该 payload 只保存是否变化、字段名和 Area 逻辑 ID,不保存字段值。主体类型为 user | service | system,投影版本与 generation 随事实保存;endpoint、credential、profile token、path、密码、完整流 URI 或 MediaMTX 配置始终禁止进入审计。

PostgreSQL repository 必须在设备创建/期望态事务内写 sense.device_operation_outbox;Outbox 失败回滚业务写入。相同期望态不增加 generation,但仍产生独立审计事实。事件 schema 继续只定义事实;传输由 sense-audit-relay-v1.openapi.json 独立冻结。

Sense → Bell 审计 relay

Sense 向 /internal/v1/audit-events:batch 每批发送 1~100 个事件,请求体不超过 1 MiB、deadline 10 秒。请求用外部文件中的至少 32 字节 secret 做 HMAC-SHA256,canonical string 为 method、path、Unix 秒、随机 nonce 与 body SHA-256 的换行拼接;非回环地址必须使用 HTTPS。Bell 允许 300 秒时钟偏差并将 (key_id, nonce) 收据保留 600 秒:相同请求摘要返回原结果,不同摘要返回 409 replay_conflict。

Outbox 用 30 秒数据库时钟 lease、单调 fencing token 和 FOR UPDATE SKIP LOCKED 协调实例。accepted/duplicate 才标记 delivered;逐项 rejected 进入 dead letter;网络、5xx、认证失败或缺失结果以 1 秒起步、最多 300 秒指数退避。Sense 不直接访问 Bell schema,Bell 不读取 Sense Outbox;全局 bell.audit_events 不自动删除,只有短期 relay receipt 自动过期。

两端读取同格式的仓库外 key 文件;Bell 可同时接受多个 key,Sense 用 SENSE_AUDIT_RELAY_KEY_ID 选择一个,便于先加新 key、切换发送端、再移除旧 key。占位结构如下,secret_base64url 必须替换为至少 32 个随机字节的无填充 base64url,不能提交真实值:

{"version":1,"keys":[{"key_id":"sense-a","secret_base64url":"<external-secret>"}]}

Brain → Bell 业务事件 ingress

Brain 向 POST /internal/v1/event-candidates 单条投递完整 event v0.1 candidate;candidate 与冻结最终事件的顶层字段完全一致,只省略 Bell 所有的 id。envelope 额外携带 schema_version=1 和 producer_id。Bell 校验 HMAC、key 与 producer 的绑定、当前逻辑设备/Area 映射、event schema 和六项语义断言后生成 evt_ ULID;首次接受返回 201 accepted,同一来源事实重投返回 200 duplicate 和原 Bell ID。

认证 canonical string 与审计 relay 使用相同五行算法,但 key 文件独立且每项增加 producer_id,不得混用审计 key。正文最多 1 MiB、deadline 10 秒、允许 300 秒时钟偏差,nonce 收据至少保留 600 秒;HTTP 只允许回环地址,非回环必须 HTTPS。key 文件形态为:

{"version":1,"keys":[{"key_id":"brain-a","producer_id":"brain-main","secret_base64url":"<external-secret>"}]}

Bell 的永久 event_ingress_receipts 以 (producer_id, source_event_id) 唯一并保存 canonical candidate SHA-256。相同 hash 重投返回原 event ID,不同 hash 返回 409 source_event_conflict;事件、来源收据和成功 nonce 响应在一个 PostgreSQL 事务中提交。event_ingress_nonces 只是 10 分钟防重表,可清理;来源收据和事件不自动删除、不可更新。Brain 必须先把 candidate 写入仓库外 SQLite Outbox,只有 accepted/duplicate 可标记 delivered;401、网络和 5xx 以 1~300 秒退避重试,稳定 4xx 进入 dead letter,最多 100 次、最多 10,000 条待投递。

数字 tenant_id/site_id/device_id 通过 Bell 所有的 event_ingress_bindings 映射到当前 Bell Site/Area 和 Sense Device 逻辑 ID。运行时只读取 Sense 设备的 ID、Area 与 modality 列,不获得 endpoint、credential、profile token 或 path;绑定缺失/禁用、设备或 Area 不一致、删除、非视频或 capture_policy != video_allowed 均失败关闭。首版绑定只由受控 migration/admin SQL 配置,没有公共 CRUD。

Bell 回环规则与 Alert 控制台

T-020 的 bell-alert-console-v1.openapi.json 只冻结工程 API,不占用未来 Bell 公共 /api/v1。整个 bell-api 必须显式监听回环,规则、token、tenant/Site 和 actor 上下文均从仓库外启动配置读取;请求不得自报这些上下文。列表按 created_at DESC,id DESC 稳定分页,默认 16、最大 100。

规则文件 v1 只支持精确 event_kind、最小严重度、可选 Site、启用状态和生效时间。相同 (tenant_id,rule_key) 的配置按 canonical SHA-256 幂等发布;变化生成不可变新版本。Event 无论有无规则/是否命中都会留下 durable sweep;每个被考虑版本写 matched/no_match evaluation,命中时 Bell 创建独立 alt_ ULID 和 Event 多对多关联。Event、规则版本、evaluation、Alert 身份、关联和 transition 均只追加。

Alert 状态机为 open → acknowledged → closed。ack 由数据库事务与 advisory lock 串行化,只有首个竞争者成功;后到者返回 409 already_acknowledged 和实际首位 actor/time,不覆盖历史。close 只允许从 acknowledged 进入。所有命令要求 8~128 字符 Idempotency-Key;同租户同 key/同命令永久重放原 status/body,同 key/不同命令返回 409 idempotency_conflict。当前详情固定返回 evidence_status=not_enabled、delivery_status=not_enabled,不得伪造切片、升级或送达事实。

兼容与废弃

  • v1 可增加不改变已有语义的可选响应字段和新错误细节;客户端必须忽略未知响应字段。
  • 删除/重命名字段、收紧已接受输入、改变状态码/幂等作用域/配额计数或把只读视图改为远程调用,均属于破坏性变化,必须发布新版本。
  • 废弃版本应先在 OpenAPI 标记并公告迁移窗口;服务端在所有已声明消费者完成迁移前继续提供旧版本。
  • OpenAPI 中的稳定错误码用于程序判断,message 只用于人读,不得依赖其字面内容。

验证

从仓库根目录执行:

python -m json.tool docs/contracts/sense-control-v1.openapi.json | Out-Null
python -m json.tool docs/contracts/sense-device-audit-v2.schema.json | Out-Null
python -m json.tool docs/contracts/sense-audit-relay-v1.openapi.json | Out-Null
python -m json.tool docs/contracts/brain-event-ingress-v1.openapi.json | Out-Null
python -m json.tool docs/contracts/bell-alert-console-v1.openapi.json | Out-Null
python -m unittest discover -s tests -p "test_sense_control_contract.py"
python -m unittest discover -s tests -p "test_sense_control_implementation.py"
python -m unittest discover -s tests -p "test_sense_audit_relay_contract.py"
python -m unittest discover -s tests -p "test_brain_event_ingress_contract.py"
python -m unittest discover -s tests -p "test_bell_alert_contract.py"

测试同时校验 OpenAPI 结构、生成 server glue、HTTP handler 与 PostgreSQL migration/事务;它不替代 Bell 消费方联合验收或客户现场容量验证。