Files
yovision/docs/tasks/T-010.md
QiuSW 36786723e3
Harness governance / validate (push) Has been cancelled
Harness governance / validate (pull_request) Has been cancelled
feat(store): add Area admission and audit outbox [T-010]
2026-08-07 18:23:24 +08:00

107 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: T-010
title: 建立 Area 准入投影与设备操作审计 Outbox 基础
phase: 2
deps: [T-009]
status: DONE
created: 2026-08-07
issue: 35
context_ref: 0b40cf16c3e72f4670c33a497e7d533d663ba135
claim_branch: claims/T-010
work_branch: agent/codex/T-010
write_paths:
- docs/tasks/T-010.md
- docs/contracts/
- deploy/postgres/
- scripts/test_postgres.ps1
- Sense/internal/device/
- Sense/internal/store/
- Sense/README.md
- docs/04-architecture.md
- docs/06-tasks.md
- docs/api.md
- docs/current-state.md
- tests/test_postgres_contract.py
- tests/test_area_audit_contract.py
---
## 问题 / 背景
T-008 已要求 Sense Control API v1 的设备创建携带 `area_id`,并冻结 `area_policy_unavailable` / `area_policy_denied` 错误语义;T-009 只实现了 Site 配额投影,没有冻结或实现 Area 策略投影。直接实现 HTTP handler 会让必填 Area 只能依赖临时数据或跳过隐私准入。
架构同时要求高风险设备写操作与持久化审计 Outbox 在 Sense 本地事务内一起提交,不能先改期望态再尽力记录日志。当前 repository 没有 Outbox;跨系统 relay 的字段、签名、确认与留存也尚未冻结。本任务先补齐 API 实现所需的 Area 准入和本地审计事务基础,不顺带实现 HTTP 或 Bell relay。
## 关联需求与交互(如适用)
- 用户故事:US-008、US-010。
- 交互清单:IX-016;`non_imaging_only` Area 不允许新增或启用成像设备,既有设备不得被静默停用。
- 相关公共契约:`docs/contracts/sense-control-v1.openapi.json`;本任务新增 Area 只读投影和本地设备审计事件契约,不修改已确认 Sense/Bell HTML 原型。
## 方案
1. 冻结 `bell.area_policy_v1` 六列只读视图:`tenant_id/site_id/area_id/capture_policy/source_version/source_updated_at`。Bell 源表只接受 `video_allowed | non_imaging_only`,Area 的 tenant、ID 和所属 Site 不可静默改写,业务更新由 trigger 单调递增版本。
2. 以增量 PostgreSQL migration 扩展 T-009:Bell 新增 Area 源表/视图;Sense 设备记录 `area_id` 和所用 Area 策略版本,并持久化每个 Area 已观察的最高版本。`sense_app` 只能读取视图,不能读写 Bell 源表或 trigger。
3. PostgreSQL repository 对所有新设备要求非空且匹配 tenant/site 的 Area;非成像设备可进入两种策略,具有 `video_capture` 能力的设备在创建时和从 disabled 切到 enabled 时都必须重读策略。投影缺失、非法、版本回退或不可读时失败关闭;策略为 `non_imaging_only` 时稳定拒绝成像变更。
4. Area 校验先于 Site 配额校验,使用事务级 advisory lock 串行化同 tenant/Area 的投影版本观察;已有 Site 配额锁和 16/128 路语义保持不变。策略或配额变化不得自动修改已有设备。
5. 冻结本地 `sense.device_operation_outbox` 事件结构并在 PostgreSQL repository 的设备创建、期望态受理路径中同事务写入。事件只保存逻辑 ID、主体、原因、trace、generation、所用投影版本和脱敏动作数据;不得包含 endpoint、credential、密码、完整 URI、token 或 MediaMTX 配置。
6. 相同期望态的重复受理不增加 generation,但仍产生一条独立审计事实。Outbox 写入失败必须回滚对应设备变更;默认内部调用使用受控 `system/sense` 主体,后续 T-011 HTTP handler 必须用认证结果覆盖主体上下文。
7. SQLite 继续作为 M1 实验室路径,不宣称具备生产 Area/Outbox 一致性。后续公共控制 API 只在 PostgreSQL 生产路径启用;本任务不为 SQLite 复制 Bell 真相或伪造策略。
8. 使用 `D:\pgsql17` 二进制启动随机回环端口的隔离 PostgreSQL 17.10 集群,验证 migration 重放、最小权限、tenant/site/Area 隔离、策略允许/拒绝、缺失/回退失败关闭、并发版本观察、Outbox 原子性与脱敏。
## 不可变约束
- Area 策略枚举固定为 `video_allowed | non_imaging_only`;本阶段以 `video_capture` capability 作为成像判定,不新建第二套 `captures_image` 真相。
- `bell.area_policy_v1` 的六列顺序、含义和 Bell 所有权是 v1 契约;Sense 只有 Bell schema `USAGE` 与该视图 `SELECT`,没有 Bell 源表读取或任何写权限。
- 同库只读视图是实时读取,不以 `source_updated_at` 的年龄误判长期未变化的合法 Area 为过期;缺失、非法值、版本回退和读取失败均视为投影不可用。未来改成缓存或远程投影时必须另行冻结 freshness 规则。
- PostgreSQL 新建设备必须具有非空 `area_id` 且与认证 tenant、路径 Site 对应。`non_imaging_only` 允许非成像设备,但拒绝成像设备的创建,即使创建时为 disabled;启用已有成像设备时必须再次检查。
- 策略不可用或拒绝只阻止相关新写入,不删除、不移动、不停用已有设备。策略与已有成像设备冲突时仍由 Bell 走显式迁移或取消流程。
- Site 视频配额默认 16、有效范围 1~128;Area 校验不能绕过、扩大或替代配额检查。
- 设备创建和期望态受理必须与 Outbox 同事务;相同期望态不增加 generation但仍审计。失败事务不得残留设备、状态、投影观察或 Outbox 半成品。
- 本地事件契约不等于 Bell relay 协议;transport、签名、批量确认、重放窗口和留存不得在实现中自行猜测。
- PostgreSQL 仍固定为 `17.10`,使用现有 `D:\pgsql17\bin`;自动验证不得读取、停止或修改 `D:\pgsql17\data` 和现有 5432 服务。
## 验收要点
- 任务相关验证:`./scripts/test_postgres.ps1 -PgRoot D:\pgsql17` 完成隔离集群、增量 migration 连续重放、SQL 权限断言和真实 repository 集成测试;`python -m unittest discover -s tests -p "test_area_audit_contract.py"` 校验投影/事件契约、权限负例和敏感字段禁入。
- 完整门禁:运行 `./init.ps1`、`python scripts/validate_agent_context.py`、`python -m unittest discover -s tests -p "test_*.py"`、`python scripts/validate_harness_governance.py`、`go -C Sense test ./...`、`go -C Sense vet ./...`、`go -C Sense build ./...` 和 `git diff --check`,全部通过。
- 人工 / 设备验收:不需要摄像头、UI 或客户现场;核对隔离脚本未引用现有 data 目录、5432 listener 前后不变,失败响应和 Outbox 不含秘密或完整连接信息。
- 构建产物:Area/审计契约、增量 PostgreSQL migration、Sense Area 准入与 Outbox 事务代码、集成/静态测试和同步文档。
## 边界(不改什么)
不实现 Sense HTTP handler、Bearer 认证、幂等收据、ETag/cursor、批量 operation、Bell Area 管理服务/UI、Outbox relay、签名/确认/留存、Area 迁移处置、SQLite 生产对等语义、Brain、MediaMTX、16 路容量测试或 T-007 现场验收;不修改已确认 HTML 原型。
## 协作约束
- 责任 Agent:由 dispatcher 分配。
- 唯一写入者:同责任 Agent。
- 委派:默认不启用。
- Gitea:任务文件先进入默认分支,再创建唯一 Issue 并回填编号;领取时记录 `context_ref`、claim / 工作分支和全部允许写路径。
任何新增写路径先检查与其他活跃任务是否重叠;同一时刻只有一个 Agent 修改本任务的 `write_paths`。
## 执行记录
### 2026-08-07 完成 Area 准入与本地审计事务基础
- 新增 `005`~`007` 增量 migration:Bell Area 源表、不可变 tenant/Site/ID 与单调版本 trigger、六列 `bell.area_policy_v1`,以及 Sense Area 观察状态、设备投影版本、脱敏设备操作 Outbox 和最小权限;`001`~`007` 连续执行两遍通过。
- 冻结 `video_allowed | non_imaging_only` 和本地设备审计 JSON Schema。PostgreSQL 新建设备必须关联匹配 tenant/Site 的 Area;成像设备在创建(包括 disabled)和启用时检查策略,缺失/非法/回退失败关闭,策略变化不静默修改已有设备。
- Repository 按 Area→Site 固定顺序使用 transaction-scoped advisory lock;设备创建/期望态受理与 Outbox 同事务。相同期望态不增加 generation 但生成独立审计事实;Outbox 故障会回滚设备创建或状态变化,payload 不含 endpoint、credential、path、密码、完整 URI 或 MediaMTX 配置。
- 新增 9 个 T-010 PostgreSQL 集成场景,总计 18 个 `TestPostgres*`:策略允许/拒绝、缺失/版本回退、并发观察、跨 tenant 隔离、启用重检、既有设备保持、主体上下文/脱敏、创建与期望态两类 Outbox 回滚、过宽 Area 源权限拒绝。`./scripts/test_postgres.ps1 -PgRoot D:\pgsql17` 通过并清理随机回环端口临时集群,现有 `D:\pgsql17\data` 与 5432 listener 未被读取、停止或修改。
- `./init.ps1` 通过;`python -m unittest discover -s tests -p "test_*.py"` 共 40 项通过,其中 T-010 静态契约 7 项;上下文/治理校验、`go -C Sense test ./...`、`go -C Sense vet ./...`、`go -C Sense build ./...` 和 `git diff --check` 全部通过。不需要摄像头、UI 或客户现场验收。
- SQLite 继续作为 M1 实验室路径,不宣称具备生产 Area/Outbox 一致性;T-011 公共控制 API 必须只在 PostgreSQL 路径启用。Outbox relay 的 transport、签名、确认、重放窗口和留存仍是后续任务边界。
### 2026-08-07 领取任务
- dispatcher `ila` 将 Issue #35 分配给 `codex`;`context_ref` 为 `0b40cf16c3e72f4670c33a497e7d533d663ba135`,claim 为 `claims/T-010`,工作分支为 `agent/codex/T-010`。
- 已读回 Issue `status/doing`、结构化 CLAIM 与两个分支 SHA 一致;接受 frontmatter 中全部 `write_paths`,当前没有其他活跃任务占用这些路径。
### 2026-08-07 Gitea 映射
- 任务规格先合入默认分支,再创建唯一主 Issue #35;本提交只回填双向映射,映射合入并读回前不领取任务。
### 2026-08-07 任务定义
- 项目负责人要求创建并立即实施 T-010;任务依赖已完成的 T-009,优先补齐 Area 与审计事务基础,再进入 Sense Control API v1 handler 实现。
- 采用已批准的两值 `capture_policy`,首期同 PostgreSQL 实例内使用 Bell 所有的版本化只读视图;Outbox 只冻结本地事件与原子写入,跨系统 relay 保持后续任务边界。