2026-08-07 18:23:24 +08:00
|
|
|
|
# Sense 控制面、准入投影与本地审计契约 v1
|
2026-08-07 17:12:15 +08:00
|
|
|
|
|
2026-08-07 18:23:24 +08:00
|
|
|
|
> 冻结日期:2026-08-07。契约版本:`1.0.0`。Sense 是设备期望态的提供方;Bell 是 Tenant、Site、Area、RBAC、配额与全局审计的所有者。T-009/T-010 已实现两个只读投影和本地审计事务基础;HTTP handler、Bell 管理服务和 Outbox relay 仍未实现。
|
2026-08-07 17:12:15 +08:00
|
|
|
|
|
|
|
|
|
|
## 契约文件
|
|
|
|
|
|
|
|
|
|
|
|
| 文件 | 生产者 / 所有者 | 消费者 | 用途 |
|
|
|
|
|
|
| --- | --- | --- | --- |
|
|
|
|
|
|
| [`sense-control-v1.openapi.json`](sense-control-v1.openapi.json) | Sense | Bell 管理面、受控集成方 | 设备查询、创建、修改、启停与批量操作 |
|
|
|
|
|
|
| [`site-quota-v1.sql`](site-quota-v1.sql) | Bell | Sense | 单 PostgreSQL 实例内的站点视频配额只读投影 |
|
2026-08-07 18:23:24 +08:00
|
|
|
|
| [`area-policy-v1.sql`](area-policy-v1.sql) | Bell | Sense | Area 归属与 `capture_policy` 只读投影 |
|
|
|
|
|
|
| [`sense-device-audit-v1.schema.json`](sense-device-audit-v1.schema.json) | Sense | 本地 Outbox;未来 Bell relay | 脱敏设备操作审计事实,不包含传输协议 |
|
2026-08-07 17:12:15 +08:00
|
|
|
|
|
|
|
|
|
|
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`,只阻止相关创建/启用,读取、非准入属性修改和停用仍允许,已有流保持运行。
|
|
|
|
|
|
|
2026-08-07 18:23:24 +08:00
|
|
|
|
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` 冻结本地审计事实的逻辑 envelope。当前事件只有 `device.created` 和 `device.desired_state.accepted`;主体类型为 `user | service | system`,投影版本与 generation 随事实保存。`data` 只包含 Area、模态、能力和状态变化等脱敏字段,禁止 endpoint、credential、profile token、path、密码、完整流 URI 或 MediaMTX 配置。
|
|
|
|
|
|
|
|
|
|
|
|
PostgreSQL repository 必须在设备创建/期望态事务内写 `sense.device_operation_outbox`;Outbox 失败回滚业务写入。相同期望态不增加 generation,但仍产生独立审计事实。Schema 不是 Bell relay 协议:传输端点、签名、批量确认、重放窗口和留存由后续任务冻结。
|
2026-08-07 17:12:15 +08:00
|
|
|
|
|
|
|
|
|
|
## 兼容与废弃
|
|
|
|
|
|
|
|
|
|
|
|
- v1 可增加不改变已有语义的可选响应字段和新错误细节;客户端必须忽略未知响应字段。
|
|
|
|
|
|
- 删除/重命名字段、收紧已接受输入、改变状态码/幂等作用域/配额计数或把只读视图改为远程调用,均属于破坏性变化,必须发布新版本。
|
|
|
|
|
|
- 废弃版本应先在 OpenAPI 标记并公告迁移窗口;服务端在所有已声明消费者完成迁移前继续提供旧版本。
|
|
|
|
|
|
- OpenAPI 中的稳定错误码用于程序判断,`message` 只用于人读,不得依赖其字面内容。
|
|
|
|
|
|
|
|
|
|
|
|
## 验证
|
|
|
|
|
|
|
|
|
|
|
|
从仓库根目录执行:
|
|
|
|
|
|
|
|
|
|
|
|
```powershell
|
|
|
|
|
|
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 的验证。
|