Files
yovision/docs/tasks/T-019.md
T
QiuSW bd964e8831
Harness governance / validate (pull_request) Has been cancelled
feat: implement T-019 reliable event ingress
2026-08-11 15:41:07 +08:00

13 KiB
Raw Blame History

id, title, phase, deps, status, created, issue, context_ref, claim_branch, work_branch, write_paths
id title phase deps status created issue context_ref claim_branch work_branch write_paths
T-019 建立 Brain 到 Bell 的可靠事件入站链路 3
T-015
T-017
DONE 2026-08-11 67 b7fe44eeb0 claims/T-019 agent/codex/T-019
docs/tasks/T-019.md
docs/contracts/
docs/raw/contracts/README.md
Brain/
docs/design/brain/index.html
Bell/
deploy/postgres/
scripts/test_postgres.ps1
docs/00-ai-start-here.md
docs/03-tech-stack.md
docs/04-architecture.md
docs/06-tasks.md
docs/api.md
docs/current-state.md
tests/test_brain_event_ingress_contract.py
tests/test_postgres_contract.py

问题 / 背景

T-017 已能产生匿名区域事件候选,T-015 已能在 Bell 内部校验并保存不可变事件,但两者之间没有可部署的传输、认证、逻辑身份映射或跨重启幂等确认。直接把 T-017 的 100 项内存事件环当作投递队列,会在进程退出、网络中断或 Bell 已接收但 Brain 尚未确认时丢失或重复事件;让 Brain 自报 Bell 平台 id 又会破坏冻结事件契约的所有权。

本任务建立一个默认关闭、可本地联调的可靠纵切:Brain 把完整 v0.1 候选先写入 SQLite Outbox,再通过内部 HMAC HTTP 端点投递;Bell 在一个 PostgreSQL 事务中验证身份、Area 隐私策略、候选语义并保存事件与永久来源收据。该纵切为后续证据切片、规则、Alert 与 ack 提供可信事件入口,但不提前实现这些能力。

关联需求与交互(如适用)

  • 用户故事:US-005 的“判定结果形成事件”基础链路;不在本任务内实现业务预警、处置或误报反馈。
  • 交互清单:不适用。本任务是内部事件 ingress 和后台可靠投递,不新增客户页面或公共路由。
  • 相关页面 / 路由:新增内部 POST /internal/v1/event-candidates;T-017 /brain-demo 只增加脱敏投递状态,不把内部地址、key 或 payload 暴露给页面。

方案

  1. 在 docs/contracts/brain-event-ingress-v1.openapi.json 冻结单事件 envelope、响应、稳定错误码和 HMAC 规则。请求包含 schema_version=1、producer_id 与不含 Bell id 的完整 event v0.1 candidate;Brain 提供 source_event_id,Bell 生成并返回平台 evt_ ULID。正文上限 1 MiB,deadline 10 秒。 同步把 T-017 已产生的 zone_entry 登记到 event v0.1 kind 注册表;不修改冻结 JSON Schema 或既有 kind 语义。
  2. 复用 T-016 已验证的 HMAC-SHA256 canonical 形式:method、path、Unix 秒、随机 nonce、body SHA-256 以换行连接;使用独立的仓库外 key 文件,并把每个 key 绑定到一个 producer_id。允许 300 秒时钟偏差,nonce 防重收据保留 600 秒。回环可用 HTTP,非回环必须 HTTPS;事件 key 与审计 relay key 不混用。
  3. 新增 Bell 所有的 event_ingress_bindings,把 (producer_id, tenant_id, site_id, device_id) 数字事件身份绑定到现有 Bell tenant/site/device/area 逻辑身份和 video 模态。入站必须 fail closed:绑定缺失、禁用、站点/Area 已删除、设备或 Area 归属不一致、非视频或 capture_policy != video_allowed 均拒绝。首版只提供受控 SQL 配置方式,不新增公共绑定管理 API。
  4. 新增永久 append-only event_ingress_receipts,以 (producer_id, source_event_id) 唯一,保存 canonical candidate SHA-256 与 Bell event ID;另用短期 event_ingress_nonces 防请求重放。Bell 用单事务和事务级 advisory lock 完成 nonce、来源收据、事件创建与响应:同来源且同 payload 返回原 Bell ID 和 duplicate,同来源不同 payload 返回 source_event_conflict,不得产生第二条事件。
  5. bell-api 增加默认关闭的事件 ingress 配置和独立 handler。handler 使用现有 event factory、数据库隐私策略与证据授权校验;候选无法通过 schema、代码级语义或隐私断言时返回不可重试 4xx,数据库暂时失败返回可重试 503。Bell 事件和来源收据无 UPDATE/DELETE 权限;只有 nonce 表允许按 TTL 清理。
  6. Brain 使用 Python 标准库实现 v0.1 mapper、SQLite Outbox、HTTP client 和 worker,不新增生产 ML 或消息总线依赖。启用配置只从仓库外绝对路径 JSON 读取,secret 另从仓库外文件读取;SQLite 文件也必须是仓库外绝对路径。默认关闭时 T-017 行为不变。
  7. mapper 将 EventCandidate 转为完整 v0.1 candidate,不发送 source_ref、摄像头 URI、凭据或 Bell 平台 id。fixture 明确映射为 outcome=test/outcome_source=auto,真实源先保持 outcome=unknown;主传感器固定为已绑定的视频设备,区域、track、配置版本和候选版本只进入契约允许字段。
  8. Outbox 先持久化后暴露成功,WAL 模式,有界待处理量 10,000、单 payload 1 MiB、1~300 秒指数退避、最多 100 次尝试。accepted/duplicate 标记 delivered;稳定校验/冲突进入 dead letter;网络、5xx 和认证故障重试直至预算耗尽。Bell 成功后 Brain 在本地 ack 前崩溃,重启重投必须得到同一个 Bell ID。
  9. 添加 Python mapper/outbox/client/worker/运行时测试、Go auth/handler/store 测试、OpenAPI 静态契约测试,以及隔离 PostgreSQL migration replay、最小权限、并发幂等和重启重试联调。代码图 MCP 本轮不可用时,将定向读取/rg 的降级和实际验证结果记录在执行证据中。

不可变约束

  • 阈值 / 数值边界:正文与单 Outbox payload 最大 1 MiB;HTTP deadline 10 秒;时钟偏差 300 秒;nonce 保留至少 600 秒;Brain 待投递上限 10,000,重试 1~300 秒、最多 100 次。默认站点 16 路、可扩展 128 路不变,这些投递阈值不得成为设备路数硬上限。
  • 判定式 / 状态转换:queued -> delivering -> delivered | dead_letter;只有 Bell accepted/duplicate 可进入 delivered。(producer_id, source_event_id, candidate_hash) 相同返回原 Bell ID;来源相同但 hash 不同永久冲突。事件、来源收据 append-only,Bell 平台 ID 只能由 Bell 生成。
  • 安全边界:key、连接串、Brain 配置、SQLite Outbox、摄像头 URI/凭据和真实事件数据都在仓库外;非回环必须 HTTPS;producer 不能自报未绑定身份;未知/删除/策略拒绝必须 fail closed。响应、日志、页面和提交物不得回显 secret、完整 URI、原始 payload 或客户信息。
  • 既有契约:最终事件必须严格符合 docs/raw/contracts/event-v0.1.schema.json 与语义 README;未知顶层字段拒绝,只能通过 ext 扩展。Sense/Bell 现有逻辑身份、Area capture_policy、T-015 Bell ULID 与不可变存储、T-016 审计 relay 均保持兼容;事件 ingress 使用独立端点、表和 key。

验收要点

  • 任务相关验证:python -m unittest discover -s Brain/tests -p "test_*.py" -v、python -m compileall -q Brain、go -C Bell test ./...、go -C Bell vet ./...、go -C Bell build ./...、python -m unittest discover -s tests -p "test_brain_event_ingress_contract.py" -v、./scripts/test_postgres.ps1。
  • 完整门禁:运行 ./init.ps1、三项文档治理基线与 git diff --check。PostgreSQL migration 必须从空库执行两遍且旧 migration 指纹不漂移;并发同源投递只落一条 event 和一条永久 receipt,运行角色不能更新/删除 event 或 receipt。
  • 人工 / 设备验收:不需要新增摄像头或目标 GPU。使用 T-017 synthetic fixture 和本机隔离 PostgreSQL 完成端到端 smoke:首次返回 accepted、模拟 ack 前崩溃后重投返回 duplicate 且 Bell ID 相同;伪造签名、过期时间、重放冲突、未绑定身份和 Area 隐私拒绝均按契约失败。结果不构成真实多路、算法效果或生产 SLA。
  • 构建产物:Bell bell-api 可执行程序与 Brain 源码 worker;OpenAPI、migration 和仓库外配置/key 示例写入各 README。不得提交运行时数据库、真实 key 或事件样本。

边界(不改什么)

不实现录像/证据切片、对象存储、业务规则、Alert、ack/升级、误报反馈、公共 JWT/OIDC、绑定管理 UI/API、消息总线、生产模型、GPU pipeline 或 64/128 路压测;不修改 _reference/、MiBeeNvr、MediaMTX 数据面或冻结 event v0.1 文件。T-016 审计链路保持独立。

协作约束

  • 责任 Agent:codex。
  • 唯一写入者:codex。
  • 委派:不启用。
  • Gitea:Issue、context_ref、claim 与工作分支在双向映射及 dispatcher 分配后回填。

任何新增写路径先检查与其他活跃任务是否重叠;同一时刻只有一个 Agent 修改本任务的 write_paths。

执行记录

2026-08-11 任务定义

  • T-019 独立冻结 Brain→Bell 业务事件身份、持久重试、认证和幂等 ingress;不复用 T-017 内存事件环,也不扩张到规则、Alert 或证据链。
  • 当前会话未暴露 codebase-memory MCP 图工具,代码发现按仓库规则降级为定向读取与 rg;任务定义前主分支 ./init.ps1 基线通过:72 项根测试、19 项 Brain 测试,以及 Sense/Bell generate/test/vet/build 全绿。
  • 任务定义已合入默认分支并创建唯一 Gitea Issue #67;本映射提交合入后才添加 status/todo 并允许 dispatcher 分配。

2026-08-11 领取与基线

  • dispatcher ila 检查依赖和开放工单后,从默认分支 b7fe44eeb0caf8d41abef365ec2100c74373321f 创建并读回 claims/T-019 与 agent/codex/T-019;Issue #67 已分配给 ila,标签为 status/doing,结构化 CLAIM 评论与本文件 write_paths 一致。
  • T-007 保持 status/waiting,不构成活跃写路径预留;本任务不启用委派。

2026-08-11 实现与自动化验收

  • 冻结并实现 brain-event-ingress-v1 单事件 envelope。Bell event key 独立于审计 key,并把 key_id 绑定到唯一 producer_id;两端共享 method/path/timestamp/nonce/body-hash 五行 HMAC-SHA256,允许 300 秒时钟偏差,正文上限 1 MiB,非回环必须 HTTPS。Brain client 禁用环境代理和重定向,避免内部事件被旁路转发。
  • PostgreSQL 新增可重放 016~017:Bell 所有的数字→逻辑身份绑定引用现有 Site/Area/Sense Device;运行时只能读取设备 ID、Area 与 modality 列,不能读取 endpoint、credential、profile token 或 path。永久来源收据和事件不可更新/删除,只有 10 分钟 nonce 表允许清理。
  • Bell 在相同事务中提交最终 event、(producer_id,source_event_id,candidate_hash) 永久收据和成功 nonce 响应;同来源/同 canonical candidate 返回原 Bell ID,不同 candidate 返回稳定冲突。隔离 PostgreSQL 测试以 8 个并发重投确认只生成 1 条 event/1 条来源收据,并验证 Area 改为 non_imaging_only 后新视频事件失败关闭。
  • Brain 新增完整 v0.1-minus-id mapper、WAL/synchronous=FULL SQLite Outbox、HMAC client 和 worker。Outbox 首次打开绑定 producer/tenant/site/device,禁止换身份复用旧队列;候选持久化成功后才进入页面内存环。fixture 显式映射为 outcome=test/outcome_source=auto,URI/凭据/source_ref 不出 Brain。队列执行 1~300 秒退避、100 次/10,000 条边界,accepted/duplicate 才 delivered,稳定 4xx 进入 dead letter;租约崩溃恢复与 Bell 成功后本地未 ack 的重投均有测试。
  • Brain 工程原型在既有“最近候选事实”卡片内展示脱敏 Outbox 状态与待投递/投递中/已送达/死信计数;以文字和状态点共同区分未启用、正常、重试和死信,且明确单条候选的“仅内存观察”或“Outbox 已持久化”。状态无变化时不重复触发 aria-live,页面不暴露 Bell 地址、key 文件或 SQLite 路径。
  • zone_entry 已登记到 event v0.1 kind 注册表;冻结 JSON Schema 未修改。Bell/Brain/部署 README、API、架构、技术栈和当前状态均同步,未扩张到证据、规则、Alert、公共认证或生产模型。
  • 最终门禁通过:./init.ps1(77 项根测试、30 项 Brain 测试及 Sense/Bell generate/test/vet/build);独立 go -C Bell test/vet/build ./...、Brain compileall、4 项跨语言 ingress 契约测试和三项治理校验;./scripts/test_postgres.ps1 在 PostgreSQL 17.10 临时集群将 001~017 重放两次并通过真实 repository、权限、并发幂等、隐私和不可变性验证,随机端口/临时目录已清理且现有 5432 listener 未改变;git diff --check 通过。
  • 本任务不需要真实摄像头或目标 GPU;synthetic fixture mapper、SQLite 崩溃恢复、真实本地 HTTP HMAC client、Bell handler 和真实 PostgreSQL consumer 的联合自动化证据满足本地验收。结果不构成算法效果、真实多路或生产 SLA。