Files
yovision/docs/contracts
QiuSW b70df147ea
Harness governance / validate (push) Has been cancelled
Harness governance / validate (pull_request) Has been cancelled
docs(contract): freeze Sense control API v1 [T-008]
2026-08-07 17:12:15 +08:00
..

Sense 控制面与站点配额契约 v1

冻结日期:2026-08-07。契约版本:1.0.0。Sense 是设备期望态的提供方;Bell 是 Tenant、Site、Area、RBAC 与配额的所有者。本文冻结接口,不表示 HTTP handler、Bell 表或 PostgreSQL migration 已实现。

契约文件

文件 生产者 / 所有者 消费者 用途
sense-control-v1.openapi.json Sense Bell 管理面、受控集成方 设备查询、创建、修改、启停与批量操作
site-quota-v1.sql Bell Sense 单 PostgreSQL 实例内的站点视频配额只读投影

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,只阻止相关创建/启用,读取、非准入属性修改和停用仍允许,已有流保持运行。

Area/capture policy 的投影形态不在 T-008 中冻结;Sense v1 仍保留 area_policy_unavailable 与 area_policy_denied 稳定错误语义,后续契约不得放宽同写路径校验要求。

兼容与废弃

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

验证

从仓库根目录执行:

python -m json.tool docs/contracts/sense-control-v1.openapi.json | Out-Null
python -m unittest discover -s tests -p "test_sense_control_contract.py"

测试校验本仓库依赖的 OpenAPI 结构与安全不变量,并不替代后续实现任务对完整 OpenAPI 标准验证器、HTTP handler 和 PostgreSQL migration 的验证。