vpp-ai-platform/docs/11-contracts.md
stewart hu 9d2ef6e432 Add interfaces and contracts reference (doc 11)
Consolidates plane-boundary ports, service/event contracts, and the
canonical domain-object table (merged with peer review §12), plus the
TS↔Python strategy: zod as schema source, committed JSON Schema
artifacts, generated pydantic models, golden-fixture contract tests
in both CIs, and cross-language data-representation rules.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019u5SLNweVio6ozJX7yfxQr

🔮 View transcript: https://logs.lojong.info/s/e8u90k3t33w590r7b5y7yzqh
2026-09-01 20:52:14 -04:00

161 lines
8.2 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.

# 11 · 接口与契约总册
> 全系统的边界接口清单 + 规范业务对象契约 + TypeScript↔Python 跨语言契约策略。
> 各篇的 YAML 片段是示意,本篇是接口的**权威索引**;字段级定义在代码仓 `packages/domain` 中。
## 1. 接口清单(按边界分类)
### 1.1 平面间端口(进程内 / 内部服务,TS interface)
借用同行评审(brainstorming.md §6)的端口风格——每个端口是一条信任边界的收口:
```ts
// 认知平面 → 可信执行链:唯一提交入口
interface ProposalSubmissionPort {
submit(proposal: Proposal): Promise<{ caseId: CaseId; lifecycleRunId: RunId }>
}
// 可信执行链内部依赖(确定性服务,不暴露给 LLM)
interface PolicyEnginePort {
check(proposal: Proposal, pack: PolicyPackRef): Promise<ValidationResult>
}
interface SimulationPort {
simulate(proposal: Proposal): Promise<SimulationResult>
}
interface EnvelopePort {
match(proposal: Proposal): Promise<EnvelopeMatch> // 包络命中与边界检查
}
// 授权环节:现势复核 + 许可签发(03 篇 §2.2,I4)
interface AuthorityPort {
authorize(p: Proposal, approvals: Approval[]): Promise<ExecutionPermit | StaleDenial>
revoke(permitId: PermitId, reason: string): Promise<void>
}
// 网关:只认 (Proposal, Permit) 对
interface GatewayPort {
dispatch(p: Proposal, permit: ExecutionPermit): Promise<ExecutionReceipt>
}
// 共享账本与证据
interface LedgerPort {
read(scope: LedgerScope): Promise<LedgerView> // 带版本号(乐观并发)
append(update: PositionUpdate): Promise<LedgerVersion>
}
interface EvidencePort {
record(evt: DomainEvent): Promise<EvidenceRef> // 事件溯源写入口
}
// 运营工单台(02 篇 §5)
interface CaseDeskPort {
open(kind: CaseKind, input: CaseInput): Promise<CaseId>
read(caseId: CaseId): Promise<CaseView>
apply(cmd: CaseCommand): Promise<CommandReceipt> // 审批、叉分情景、关单…
watch(caseId: CaseId): AsyncIterable<CaseEvent>
}
```
### 1.2 服务间接口(跨进程 / 跨语言)
| 接口 | 双方 | 协议 | 契约来源 |
|---|---|---|---|
| Skill 调用 | TS Runtime → Python Skill 服务 | HTTP/JSON(FastAPI) | domain 导出的 JSON Schema(见 §3) |
| 审批/工单 API | 前端/工单台 → Runtime | HTTP/JSON | 同上 |
| 决策案 API(外部系统) | 外部系统 → 平台 | HTTP/JSON,`start/act/observe` 三操作(同行方案 A) | 二期再开放,一期仅内部 |
| 联邦网关 | 省级平台 ↔ 他省 | 签名工件交换(10 篇) | FlexibilityEnvelope 等工件 schema |
| 外部适配器 | 平台 ↔ 交易平台/调度/计量 | 对方规定(文件/WebService/消息) | 防腐层内转换(06 篇),原始报文留痕 |
| 边缘控制链路 | 执行引擎 ↔ 边缘终端 | 国网标准协议栈(IEC 104 / MQTT + 国密等,按现场定) | **不属于** JSON 契约域;计划/包络的语义仍来自 domain 对象 |
### 1.3 事件契约
事件总线上的每类事件 = 一个 domain 对象 + 信封:
```ts
interface EventEnvelope<T> {
eventType: string; schemaVersion: string // 语义化版本
occurredAt: string // ISO8601 UTC
causationId: string; correlationId: string // 因果链(复盘/审计遍历用)
payload: T
}
```
## 2. 规范业务对象总表
合并本文档集与同行评审 §12 的清单,去重后的权威列表(★ = 已在各篇定义):
| 对象 | 篇 | 权威存储 | 说明 |
|---|---|---|---|
| Resource / ResourceProfile ★ | 02 | 关系库(复用既有档案/设备中心主数据) | 画像含可靠性评分 |
| FlexibilityEnvelope ★ | 04/10 | 关系库 | 能力向上;省间交换同 schema |
| PositionLedger / PositionUpdate ★ | 00/04 | 关系库(账本服务) | 约束级联载体 |
| Commitment | 10 | 关系库 | 用户承诺与省间承诺共用基型 |
| ForecastBundle | 05 | 时序库 + 快照 | 预测曲线 + 分位区间 + 模型版本 |
| SituationReport ★ | 02 | 事件日志 + 快照 | |
| Proposal ★(含 digest) | 03 | 关系库 + 事件日志 | 状态机见 03 |
| ConstraintSet | 03/05 | 随 Proposal 快照 | 优化器输入的约束集合 |
| ValidationResult / SimulationResult | 03 | 快照 | 血缘组成部分 |
| Approval ★ / ExecutionPermit ★ | 03 | 关系库 | 绑定 digest |
| DispatchOrder | 04 | 关系库 | 许可放行后的分解指令 |
| ExecutionReceipt / ExecutionReport ★ | 04 | 事件日志 | 幂等回执(I5) |
| Settlement | 06 | 关系库 | 结算事实 |
| DecisionCase ★ | 02 | 关系库 | 工单聚合(引用,不复制) |
| PolicyPack ★ | 05 | 版本库(随代码仓管理) | 可执行规则 + 测试 |
| Envelope ★(授权包络) | 03 | 关系库 | 与 FlexibilityEnvelope 严格区分 |
| ReviewFinding ★ | 02 | 语义记忆库 | |
## 3. TypeScript ↔ Python 契约策略
### 3.1 单一事实来源 + 双向生成
```
packages/domain (zod schemas, TS) ← 唯一手写处
│ 构建时导出
▼
contracts/*.schema.json (JSON Schema) ← 提交入库的中立工件(版本化)
│ datamodel-code-generator
▼
skills-py/.../models.py (pydantic) ← 生成物,禁止手改
```
- **为什么 zod 为源**:对象的产生地与主要校验点在 TS Runtime(工作流步骤 IO、
结构化输出、事件信封都用 zod);JSON Schema 作为中立层,Python 侧不依赖 TS 工具链;
- **双端校验**:TS 侧 `registerSkill` 出站校验(05/09 篇),Python FastAPI 用生成的
pydantic 模型入站校验——同一 schema,两端执行;
- **备选方案**(记录备查):JSON Schema 手写为源、两侧都生成——对 Python 团队更中立,
但失去 zod 的 TS 类型推导与重构联动;除非武大/电科院团队明确要求共管 schema 源文件,
维持 zod 为源。
### 3.2 契约测试(CI 强制)
- `contracts/fixtures/` 存每类对象的**黄金样例**(含边界值与历史真实脱敏样本);
- TS CI 与 Python CI 各自加载全部样例做校验——两端对同一样例结论不一致即构建失败;
- schema 变更走**兼容性检查**:对比上一版 JSON Schema,破坏性变更(删字段、改类型、
收紧约束)必须升主版本并附迁移说明;事件消费者按 `schemaVersion` 路由新旧处理器。
### 3.3 数据表示纪律(跨语言最容易翻车处)
| 项 | 约定 |
|---|---|
| 金额 / 电价 / 电量 | **字符串表示的定点小数**(`"123.45"`),禁止 JSON 浮点直传金额(TS number 与 Python float 的精度差异会在结算对账时爆炸);两侧分别用 decimal 库处理 |
| 单位 | 编进字段名:`power_mw`、`energy_mwh`、`price_yuan_per_mwh`——消灭「这个数是 kW 还是 MW」类事故 |
| 时间戳 | ISO8601 UTC 存储与传输;市场时段语义(96 点、申报截止)统一按 `Asia/Shanghai` 解释,时段用 `{date, interval_index}` 而非裸时间戳 |
| 曲线 | `{interval_minutes: 15, start: ..., values: [...]}` 定长数组,不用稀疏 map |
| 标识 | 全部字符串 ID(防 JS 大整数精度损失);主数据 ID 引用既有档案中心编码 |
| 枚举 | 全大写字符串字面量,两侧生成为枚举类型 |
### 3.4 为什么不用 gRPC / protobuf
当前调用量级(申报级频次,非遥测流)下 HTTP/JSON 足够,且:
血缘快照人类可读(审计员直接看)、合作方接入门槛低、与 FastAPI/OpenAPI 生态顺滑。
遥测流是另一个协议域(边缘侧,国网标准协议栈),本就不在 JSON 契约范围内。
二期若省间联邦工件量大再评估签名 + 二进制序列化。
## 4. 一期落实清单
1. `packages/domain` 建仓:先落 07 篇场景所需对象(Proposal / Approval / Permit /
PositionLedger / ForecastBundle / SituationReport / DecisionCase);
2. JSON Schema 导出与 pydantic 生成接入构建脚本;`contracts/` 目录 + 黄金样例;
3. 双端契约测试进 CI(M1 里程碑的一部分——数据先行包括契约先行);
4. 与武大/电科院约定:Skill 服务只依赖 `contracts/`(JSON Schema + 生成的 pydantic),
不依赖 TS 仓的任何内部代码——组织边界与代码边界对齐。