Files
yovision/docs/contracts/README.md
T
QiuSW a0d239811f
Harness governance / validate (push) Has been cancelled
Harness governance / validate (pull_request) Has been cancelled
feat(sense): implement Control API v1 [T-011]
2026-08-07 20:54:41 +08:00

8.1 KiB
Raw Blame History

Sense 控制面、准入投影与本地审计契约 v1

冻结日期:2026-08-07。Control API 契约版本:1.0.0。Sense 是设备期望态的提供方;Bell 是 Tenant、Site、Area、RBAC、配额与全局审计的所有者。T-011 已实现 Control API handler 与 PostgreSQL 一致性边界;Bell 管理服务、JWT/OIDC 和 Outbox relay 仍未实现。

契约文件

文件 生产者 / 所有者 消费者 用途
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 文件保持不变

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 不是 Bell relay 协议:传输端点、签名、批量确认、重放窗口和留存由后续任务冻结。

兼容与废弃

  • 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 unittest discover -s tests -p "test_sense_control_contract.py"
python -m unittest discover -s tests -p "test_sense_control_implementation.py"

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