Files
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

8.1 KiB
Raw Permalink Blame History

事件契约 v0.1(已冻结)

冻结日期:2026-08-03 范围:推理侧(silver_pose 及后续推理内核)→ YoVision 平台侧的唯一数据契约。 依据:《07-事件契约比对-silver_pose.md》

文件 用途
event-v0.1.schema.json JSON Schema(draft 2020-12),规范本体
event-v0.1.example-current.json 现状基线:silver_pose 今天就能产出的形态,P1 缺口全为 null
event-v0.1.example-target.json 目标形态:P1 补齐后,含 keypoint 序列、clip、自动 outcome
event-v0.1.example-radar.json 非成像模态:卫生间纯雷达跌倒,snapshot_uris 为空数组,门磁佐证

三个 example 都是测试夹具,两侧仓库的单元测试都应对它们做校验。

修订记录

日期 变更 理由
2026-08-03 初次冻结 —
2026-08-03 新增 sensors[] 与 observation.signal_seq_uri;evidence.snapshot_uris 的 minItems 由 1 放宽为 0 感知系统后期接入毫米波雷达等非成像设备,原 schema 把「感知=摄像头」焊死在契约里,纯雷达事件无法表达。本次为原地修订,理由是当时零实现;首个生产者上线后,§1.2 的版本递增规则绝对生效,不再允许原地改

1. 冻结意味着什么

  1. 本目录内容在两个仓库各存一份,内容必须逐字节相同。 任何一侧修改,同一次提交内同步另一侧
  2. schema_version 为 "0.1"。破坏性变更必须递增版本并保留旧版本文件,不得原地修改已冻结的 schema
  3. 「破坏性」的定义:删除字段、收紧取值域、把可空改为必填、改变字段语义。新增可空字段与新增 kind 取值不算破坏性
  4. 实验性字段一律放 ext,不占用顶层命名空间,也不触发版本升级

2. 三条设计决定(及其代价)

① 所有顶层键必须存在,可为 null,但不得省略。 「省略」与「显式 null」在下游无法区分,这是这类系统最常见的排查陷阱——identity_status 就是被这个问题逼出来的字段。代价是生产者要写一串 null,但生产者只有一个 mapper 函数。

② 根对象 additionalProperties: false。 未登记字段一律拒绝。防的是推理侧偷偷加字段、平台侧偷偷依赖,几个月后没人知道它是不是契约的一部分。逃生口是 ext。

③ confidence 允许为 null,且现阶段必须为 null。 几何证据 + 时间窗状态机的判定链路没有天然置信度。伪造一个常量会污染所有下游阈值调优。等换成分类模型再填。

3. kind 注册表

新增 kind 不需要升 schema 版本,但必须在此表登记。

kind 含义 默认 severity 产出方 状态
fall 人员摔倒确认 high silver_pose FSM 进入 CONFIRMED ✅ 已上线
zone_entry 匿名 track 从区域外进入区域内 medium Brain 区域进入判定 ✅ T-019 ingress 已接入

4. silver_pose → 契约 映射表

mapper 的完整实现规格。左列出处见《07》§1。

契约字段 来源 变换
schema_version — 常量 "0.1"
id — 平台侧生成 ULID,加 evt_ 前缀
source_event_id event_id 原样。截图文件即按它命名,是回溯本地证据的唯一钥匙
tenant_id / site_id / device_id source_id 经平台设备映射表解析。事件中不得出现 RTSP 地址或凭据
sensors source_id 单摄像头事件为 [{device_id, "video", "primary"}];多传感器融合由推理侧列全
kind event_id 的 FALL- 前缀 提升为显式字段 "fall"
severity — 按 kind 查注册表 → "high"
confidence — 恒 null(见 §2③)
detected_at confirmed_at_utc 原样
occurred_at 换算 confirmed_at_utc - latency_seconds。误差为单帧级
latency_seconds latency_seconds 原样
config_version config_version 原样
rule — null(推理侧无规则引擎),平台侧按 kind 反查补全
subject.class — 常量 "person"
subject.track_id track_id 原样
subject.attributes — {}
subject.anon_id / identity — null
subject.identity_status — 常量 "not_enabled"
observation.* — 全 null(P1 补 keypoint_seq_uri;非成像模态补 signal_seq_uri)
evidence.snapshot_uris screenshot 相对路径 → URI,单元素数组。非成像模态为 []
evidence.clip_uri / clip_range — null(P1 补)
dedup_key / aggregated_into — null,由平台侧构造
outcome state CONFIRMED → "unknown";RECOVERING 稳定后回传 "subject_recovered" + outcome_source: "auto"
diagnostics.fsm_state state 原样,仅供排查
diagnostics.*_monotonic 同名字段 原样。单调时钟跨进程无意义,不得用于任何时间计算
ext — {}

5. schema 拦不住的四件事(必须写代码校验)

已实测(REJECT = schema 能拦,ACCEPT = 拦不住):

schema 能拦:省略必填键 / 非法 ULID / 顶层多余字段 / outcome 越界 / config_version 空串 / 负 latency / kind 大写。snapshot_uris 空数组对非成像事件是合法值,见修订记录、雷达示例和 schema 的 minItems: 0。

schema 拦不住,需在 mapper 与平台入口各加一道断言:

# 校验 理由
1 detected_at >= occurred_at 跨字段约束,JSON Schema 表达不了。倒序时间会让 SLA 统计出负数
2 |detected_at - occurred_at - latency_seconds| < 0.1s 三者冗余,必须自洽。不一致说明 mapper 用错了时钟
3 confidence is null(现阶段) schema 只能约束 0–1 区间,拦不住伪造值
4 证据文件名不含 IP、端口、凭据、客户名 只允许 事件ID + 日期目录。文件名会出现在日志、URL 与工单里 —— 这是 silver_pose alerts.py 头注释的既有纪律,此处升格为强制校验
5 sensors 中恰有一个 role == "primary",且其 device_id == 顶层 device_id 数组约束,schema 表达不了。两个 primary 会让归因和去重都失去基准
6 若任一 sensors[].modality == "video",则该 device_id 所在区域 privacy_flag 必须为 false 隐私区域硬约束的运行时兜底。设备录入时已拦一道,此处是第二道

5.1 隐私区域约束的正确表述

原表述「隐私区域拒绝 device_type = camera」应改为按模态判定:

privacy_flag = true 的区域:
  允许 modality ∈ {radar, contact, button, wearable}
  拒绝 modality ∈ {video}

差别不是措辞。按设备类型判定,每接一种新设备就要重新问一次"它算不算摄像头";按模态判定,规则一次写死,新设备只需声明自己的 modality。《01》RQ-S1-05、《02》NFR-CMP-05、《03》§3.2 与落地清单四处的表述需同步。

6. 本地校验命令

pip install 'jsonschema>=4.18'
python3 - <<'PY'
import json
from jsonschema import Draft202012Validator, FormatChecker
s = json.load(open('event-v0.1.schema.json'))
Draft202012Validator.check_schema(s)
v = Draft202012Validator(s, format_checker=FormatChecker())
for f in ('event-v0.1.example-current.json', 'event-v0.1.example-target.json'):
    errs = list(v.iter_errors(json.load(open(f))))
    print(f, 'OK' if not errs else [e.message for e in errs])
PY

已于 2026-08-03 执行通过:schema 合法,两个 example 均校验通过。

7. 未决项

# 事项 决策人
1 source_id → device_id 映射表放在哪(平台 DB 表 / 边缘配置文件) 待定
2 投递通道:HTTP POST 起步还是直接上 ZeroMQ 待定,建议先 HTTP
3 outcome 回写的反向通道协议(平台 → 推理侧) 待定
4 silver_pose 侧本文件的落地方式(需按其 T-xxx 任务流程走) 待定