ADR-030:Outbox Dispatcher 与运维读模型
冻结事务事件可靠投递、版本化运维投影、Edge 低敏 Observation 和 Admin 只读查询边界
| 属性 | 内容 |
|---|---|
| 状态 | 已采用(文档基线),尚未实现/验收 |
| 决策日期 | 2026-09-04 |
| 适用范围 | oceanway-core、oceanway-contracts、Text/Media Gateway、oceanway-api-edge、oceanway-admin 与 oceanway-infrastructure |
| 前置决策 | OW-ADR-011、013、015、019、022、026–029 |
背景
ADR-028 与 ADR-029 已建立受控 API 准入:API Edge 终止 DeveloperCredential,Core 在一个 PostgreSQL 事务内提交 Run Input、Run、不可变 Execution Manifest、credits Reservation、幂等结果以及 run.created / wallet.reserved Outbox 记录。
这条基线证明了“业务事实与待发布事件原子创建”,还没有证明事件会可靠到达消费者,也没有形成管理员可查询的 Operation 时间线。若直接让 Admin 查询 Core 或 Gateway 私有表,或从日志按模型名、Prompt 和发生时间猜测关联,就会重新制造跨域事实副本、越权入口和不可验证的因果关系。
本 ADR 冻结下一阶段的最小实施方案:以同一严格PostgreSQL Outbox协议在各Owner事实库部署Dispatcher/版本化Consumer,完成Core Operations Read Model与私有只读Query,并为Execution/Gateway→Metering、Execution→Billing Finalization、Metering→Billing Catch-up建立必达触发链;Admin再以Workforce BFF消费Operations投影。它不是Gateway执行、完整可观测平台或公网开放方案。
当前已实现事实
截至 2026-09-04,可作为本阶段输入的事实只有:
- Core
f9e55b6已在准入事务中追加不可变run.created@1.0与wallet.reserved@1.0,Outbox 记录包含事件信封、载荷、创建时间和可空的published_at; - Core 已有未发布事件索引和
event_receipts基础能力,但没有正在运行的 Dispatcher、Delivery Lease、投递尝试历史、Operations Projector、完整性快照或 Operations Query API; run.created@1.0没有requestId、开发者访问引用、授权决策、模型目标和 Reservation/Price Snapshot 引用,只能生成信息不完整的投影;- API Edge
f97cf5f会为每次 HTTP 接收创建新的requestId,但首次成功准入的requestId之外,幂等重放尝试和准入前失败尚未形成持久运维记录; - Admin 文档已定义 Run Explorer、Error Center、Incident 和 Reconciliation 的产品目标,当前尚没有基于真实事件的完整实现链路;
- Public Admission 仍默认关闭;Gateway 执行、输出登记、Metering 与 Settlement 尚未接入。
因此,本 ADR 中除上述条目外的内容都是已采用的目标设计,不能在实现与验收完成前标记为“当前能力”。
决策摘要
正式采用以下所有权:
| 组件 | 唯一 Owner | 职责 | 明确不负责 |
|---|---|---|---|
| 通用 Outbox Dispatcher / Delivery / Attempt / Ack Contract | 每个Producer事实Owner;Schema由oceanway-contracts统一 | 本地事实与Event/Delivery原子提交、跨Owner at-least-once投递、Lease Fencing与Owner Receipt验证 | 跨库事务、执行Consumer领域命令、共享全局游标 |
| Operations Projector、Read Model、Operations Intake 与 Private Query API | oceanway-core 的 Operations 模块 | 幂等运维投影、版本与完整性快照、低敏运维证据摄取与授权查询 | 产品 UI、Provider 私有状态、客户命令代理 |
| Workforce Query BFF 与 Run Explorer UI | oceanway-admin | Workforce Session、展示层授权、脱敏组合与交互 | 拥有投影、直连 Core/Gateway 数据库、成为事实源 |
| Event、Observation 与内部 API Schema | oceanway-contracts | 严格版本、生成制品、生产者/消费者兼容性门禁 | 运行时实现和数据库 |
| Process Role、部署、Secret 引用、监控、灾备与验收证据 | oceanway-infrastructure | 部署不可变制品、配置与跨进程证据 | 应用业务逻辑、手工改写投影或领域事实 |
Operations Read Model 位于 Core,不再允许文档中使用“可能由 Admin 或 Core 拥有”的模糊表述。Admin 只拥有面向内部员工的 BFF/UI。
候选方案与选择理由
| 方案 | 结论 | 原因 |
|---|---|---|
| 现在引入 Kafka/NATS/Redis Streams | 不采用 | 当前Consumer虽有四个,但均是固定内部Owner链路;尚无吞吐、跨团队回放或故障域证据足以承担第二套集群、权限、保留与恢复系统 |
| Admin 自建并拥有诊断数据库 | 不采用 | 会让 UI 仓成为跨域第二事实源,并诱发直连 Core/Gateway 数据库 |
| Projector 收到 v1 Event 后查询当前 Domain State 补字段 | 不采用 | 历史重放会随当前状态变化,且把重建绑定到多个在线模块 |
| 从 Logs/Trace 反推 Operation 状态 | 不采用 | Telemetry 可采样、过期和丢失,不能证明领域事务或经济事实 |
| 新建独立 Operations 仓库/微服务 | 不采用 | Core 已拥有 Operations 模块;首期 Process Role 足以形成扩缩和故障边界 |
PostgreSQL Outbox + Core Worker + run.created@2.0 | 采用 | 在现有事实边界内补齐可靠投递与自包含快照,最容易做真实事务和崩溃验证 |
选择 PostgreSQL-first 不等于把投影查询放回 API 请求事务。Admission 只追加 Outbox;独立 Worker 异步投影,Operations 故障不会回滚已经提交的 Run 或 Reservation。
1. 首期部署保持简单
首期不引入 Kafka、NATS、Redis Stream、云消息服务、新 Git 仓库或新的业务微服务。各现有Owner使用同一Contracts/Outbox实现,在自身发布制品与事实库边界提供独立Process Role;逻辑角色为:
core-api # 现有私有领域 API
core-operations-worker # 单一 managed Dispatcher + 版本化本地 Consumer Registry / Projector
metering-input-worker # Execution/Gateway Event Inbox + 分Lane Fence / Work
billing-coordination-worker # Finalization 与 Settlement Catch-up Inbox / Fence / Work
gateway-outbox-worker # 每个 Text/Media Gateway 自己的 Availability Event Dispatcher每个角色可以独立重启,但只拥有自己的Source/Inbox数据库边界;跨Owner调用不共享事务、Lease或游标。首期每个环境、每个角色只部署一个受编排器管理的活动实例;Lease Fencing仍必须允许旧实例退出和替代实例接管,但不以多副本并发作为当前容量方案。Dispatcher通过稳定内部Transport Port调用Consumer;同进程场景可以使用本地Registry Adapter,跨Owner场景使用认证内部Adapter,具体传输方式不进入领域Event或Receipt语义。
只有当独立团队/区域回放、吞吐、延迟、隔离与恢复测试证明这些Producer-local PostgreSQL Outbox无法满足SLO时,才提交新ADR评估外部Broker。当前四个固定内部Consumer本身不授权团队临时接入消息中间件,也不能为了“未来可能需要”预先维护双传输路径。
2. Outbox 与投递状态分离
不可变事件
已经提交的 Outbox 事件信封、eventType、schemaVersion、Aggregate Revision 和 Payload 永不修改。重试、失败、隔离、恢复与重新投递不是业务事件内容的一部分,必须进入独立 Delivery State 与 Attempt 记录。
目标数据职责至少包括:
outbox_events # 已存在的不可变事实载体
delivery_set_versions # 不可变 Delivery Set 版本与制品摘要
delivery_set_members # event type/schema version 对应的 Mandatory Destination → Consumer 映射
outbox_delivery_states # 每个 event × mandatory subscription 的当前状态与终态 Receipt 引用
outbox_delivery_attempt_starts # Claim 同事务追加的不可变尝试开始事实
outbox_delivery_attempt_finishes # 每个 Attempt 至多一个不可变终结/分类错误事实
event_receipts # Consumer 已原子应用的回执
completeness_snapshots # 普通查询完整性:同事务冻结 Source、Proof 与结果
completeness_snapshot_members # 普通快照的 Event / Accepted Observation 精确成员
completeness_proof_manifests # 不可变历史Proof,自身四元组与全部tagged输入
completeness_proof_mandatory_delivery_definitions # Proof内完整排序Definition四元组成员
completeness_proof_mandatory_delivery_members # 每个Delivery坐标、精确Definition绑定与terminal分类
cutover_candidate_boundaries # 切换/回滚前冻结的 Source、定义与预期 Receipt 坐标,不含 Ready 结论
cutover_candidate_members # Candidate 的精确成员、Payload Digest 与预期 Receipt 坐标
cutover_readiness_proofs # Shadow 追平后在最终事务生成,引用 Candidate 并记录 Ready/Blocked
operational_observations # Intake 已接受的不可变低敏 Observation
observation_id_registry # Observation ID 与首次 Workload/摘要/acceptedAt 的永久命名空间绑定
observation_payload_purge_markers # 已清理 Payload 的追加式唯一标记与 Retention Audit 引用具体表名可以在 Core Migration 中按现有命名约定调整,但职责不能合并回可变 Event Payload,也不能只靠日志表达失败与重试。
首期不增加全局递增位置,也不使用任何试图代表“已经完整处理到这里”的标量水位。Dispatcher 可以用 (created_at, event_id) 做无语义的有界 Keyset 扫描,但游标只服务当前批次,不能持久化为完整性证明,也不能解释为事件因果或全局顺序。
普通查询完整性由版本化 Completeness Snapshot 证明:在同一个 PostgreSQL Consistent Snapshot 事务中,按固定的 sourceQueryDefinitionVersion + sourceFilterDigestAlgorithmVersion + sourceFilterDigest 冻结当时可见的 Event 与 Accepted Observation 精确成员集合。sourceFilterDigest 必须对该 Query Definition Version 的严格 Source Filter DTO 使用 Contracts Canonical JSON 计算,算法版本与摘要成组持久化;Source Filter 的字段或规范化规则变化必须升级 Query Definition Version,Digest 算法变化必须升级 Digest Algorithm Version。每个成员保存严格 sourceKind + sourceId + sourceDigest 联合:Event 使用覆盖完整 Envelope+Payload 的 eventEnvelopeDigestAlgorithmVersion + eventEnvelopeSha256,Accepted Observation 使用 observationDigestAlgorithmVersion + acceptedEnvelopeSha256,禁止误用 Payload-only 或 Submission 摘要。Event 与 Accepted Observation 的成员分别按 Contracts 固定顺序序列化完整 sourceKind + sourceId + sourceDigest.kind + algorithmVersion + sha256,再以各自 setDigestAlgorithmVersion=jcs-sha256-v1 计算 Set Digest;算法版本、摘要与 Count 成组持久化和返回。该事务同时分别做双 Receipt anti-join,检查 Aggregate Revision Gap,并单列不支持 Schema、Hash 冲突、Quarantine 与语义校验结果。完整成员保存在 append-only completeness_snapshot_members;Proof 使用不可变 proofManifestRef + proofManifestSchemaVersion + proofManifestDigestAlgorithmVersion + proofManifestDigest 内容寻址,冻结同一个 Source Filter 三元组、两类 tagged Source Set、Mandatory Delivery Definition Set Digest/Count与完整排序四元组成员、每个Delivery坐标绑定的精确Definition四元组及状态、Revision Gap、Quarantine、Unsupported Schema 与 tagged Semantic Check Input 的精确成员。同一Snapshot可合法跨越Definition切换,不能用当前Registry中的单一Definition替代历史成员。只存 Count、裸 Digest 或缺少算法/Schema 的引用都不构成证明。上述新建内容摘要首期算法固定为 jcs-sha256-v1;未知算法拒绝,算法或覆盖字段变化必须升级 Digest Algorithm Version,结构变化同时升级所属 Schema/Definition Version。Cutover/rollback 不复用 Completeness 表冒充 Readiness:Candidate Boundary、Expected Receipt Coordinates 与后置 Readiness Proof 都保存自身 Schema Version、Digest Algorithm Version 与 Digest;Candidate Member 使用同一 tagged Source Digest 联合,Shadow 追平后才生成引用完整 Candidate 四元组的 Proof。Observation 判定范围只到 Core 已接受的不可变 Source,不暗示 Edge 请求全集完整。任何一项未知或未完成都使投影完整性降级;总数相等、最新时间接近、最大 ID、数据库 Transaction ID 或扫描游标推进均不能替代精确集合及其验证。
数据库必须禁止运行时 API、Admin BFF、Worker 普通路径和人工 SQL 角色原地 UPDATE/DELETE Outbox Event Envelope/Payload、Event 上冻结的 Delivery Set Definition 四元组、Delivery Attempt Start/Finish、Applied Receipt、Accepted Observation、Observation ID Registry/Purge Marker、完成后的 Source Snapshot Result/Member 与敏感查询 Audit。Claim 必须在设置 Lease/Current Attempt 的同一事务追加 Start,外部调用只能发生在提交后;Ack/Retry/Quarantine 用 destination + eventId + currentAttemptId + leaseToken 校验,并在状态迁移事务追加该 Attempt 的唯一 Finish。Lease 恢复者只能在旧 Token 失效且无 Finish 时追加 lease_expired_unknown;旧 Worker 不得追加第二个 Finish或覆盖新状态。published_at 是唯一受控的一次性状态例外:Dispatcher 没有该列的直接 UPDATE 权限,只能调用数据库受控函数;函数在同一 Lease-fenced 确认事务内重新验证冻结的 Destination→Consumer 映射、全部 Mandatory Delivery terminal 状态与匹配的 Applied Receipt 引用后执行唯一一次 NULL → timestamp。可变 Delivery State、Lease 与 Active Projection Pointer 使用职责受限的数据库角色、状态机约束和 CAS/Fencing 更新。
保留期到期后的归档或删除只能由专用、受审计的 Retention/Archive Procedure 执行,并且必须先满足恢复/重建基线、Legal Hold、数据分类和 Observation accepted health window 边界。Source Snapshot 的 ID/Digest/Count 只证明完整性,不含可恢复 Payload;全量 Projection State Snapshot 也只是派生状态,二者都不能替代或成为删除唯一 Canonical Source 的理由。Outbox Event 离开热表后,其完整不可变 Canonical Envelope/Payload、Canonical Event Digest Algorithm Version/SHA 与 Delivery Set Definition 必须进入 Rebuild/Verifier 每次都会读取的批准归档 Source View,逻辑 sourceKind + sourceId + sourceDigest(kind + algorithmVersion + sha256) 不因冷热迁移消失;同时保留固定 Schema/Consumer Runtime 和隔离 Restore Drill 证据。缺少、摘要错误或不可读取的归档必须让 Completeness/Cutover 失败。Procedure 绝不允许原地改写历史证据;执行结果本身追加不可变 Audit。
Active/Shadow Applied Receipt、被 Delivery terminal Ack 引用的 Receipt,以及对应 Source 仍可投递/重建时的 Receipt 均不得删除。Projection Version 已退休且回滚窗结束、Pointer/Consumer Target/Delivery/Quarantine/Legal Hold 无引用并完成恢复演练后,才可将该退休版本的 Receipt 与派生 Projection 成组清理;Canonical Event Source 仍留在批准归档 Source View。Accepted Observation Payload 清理是有意缩小当前保留范围的特殊情况:sourceQueryDefinitionVersion 必须冻结按 Core acceptedAt 计算的 Retention 范围,Verifier/Semantic Check 必须读取唯一 Purge Marker 与 Retention Audit,且 Query 明示超出范围不可恢复;同时保留环境命名空间生命周期内不可更新/删除的低敏 observation_id_registry。Intake 每次接受前都检查该 Registry 或其不可变归档,不能因 Payload 到期而允许 ID 被重绑或旧提交复活。
领取与 Lease Fencing
Dispatcher 按配置选择到期且尚未完成的有界批次,并在短事务中使用 FOR UPDATE SKIP LOCKED:
- 领取候选 Delivery State;
- 写入新的 Lease Token、Owner 与到期时间;
- 提交事务并释放数据库锁;
- 在锁外解析 Schema、调用 Consumer 和执行可能较慢的工作;
- 完成、续租或记录失败时必须携带当前 Lease Token;数据库只接受仍然有效的 Token。
网络调用、Consumer 执行和退避等待都不能发生在持有行锁的事务中。旧 Worker 即使在暂停后恢复,也不能用已失效的 Lease Token 覆盖新 Owner 的结果。
Batch Size、Lease Duration、并发、退避、最大停留与隔离条件来自部署配置、容量测试和 SLO,不在文档或代码中写拍脑袋常数。配置必须有安全边界、变更审计和可观测结果。
Mandatory Delivery Set 与 published_at
版本化 Consumer Registry 为每个 eventType@schemaVersion 声明当期 Mandatory Subscription,并把已激活定义持久化为不可变的 delivery_set_versions + delivery_set_members(或可证明等价的固定制品摘要模型)。每个 Member 冻结 destination → consumerName → expectedReceiptType/SchemaVersion;core_operations Member还必须冻结可接受的精确projectionName + projectionVersion,其他Destination明确projectionTarget=not_applicable。deliverySetVersion + deliverySetDefinitionSchemaVersion + deliverySetDefinitionDigestAlgorithmVersion + deliverySetDefinitionDigest 必须在追加领域事实与 Outbox Event 的同一个 PostgreSQL 事务中解析并冻结,同时创建带上述Target的精确 Delivery State。Deferred Constraint 或等价的事务末 anti-join 必须证明没有缺失或多余成员。不能等 Worker 首次扫描时再读取“当前 Registry”补写。只有该事件在已冻结集合内的全部 Mandatory Delivery 都以匹配Consumer、Receipt Contract和Projection Target的Applied Receipt确认,数据库受控函数才可以设置 published_at。
DeliverySetDefinition@N 是严格、封闭的内容寻址 DTO:
DeliverySetDefinition@N = {
deliverySetVersion
deliverySetDefinitionSchemaVersion
deliverySetDefinitionDigestAlgorithmVersion = jcs-sha256-v1
deliverySetDefinitionDigest
applicableEventCount
applicableEvents[] = sorted { eventType; eventSchemaVersion }
memberCount
members[] = sorted {
eventType; eventSchemaVersion; destination; consumerName;
expectedReceiptType; expectedReceiptSchemaVersion;
projectionTarget = { state=not_applicable }
| { state=required; projectionName; projectionVersion };
mandatory
}
}applicableEvents[] 按 (eventType, eventSchemaVersion) 排序,members[] 按 (eventType, eventSchemaVersion, destination, consumerName) 排序;数组拒绝重复并与 Count 精确相等。Member 的 Event 键必须存在于 Applicability 集合,同一 (eventType, eventSchemaVersion, destination) 只能映射一个 Consumer,mandatory=true 子集精确定义 Producer 必须创建的 Delivery Row。core_operations必须且只能绑定OperationsProjectionAppliedReceipt@1 + projectionTarget.required;其他Destination绑定各自注册Receipt且Target为not_applicable。Digest Candidate 精确覆盖 deliverySetDefinitionSchemaVersion + deliverySetDefinitionDigestAlgorithmVersion + deliverySetVersion + applicableEventCount + applicableEvents[] + memberCount + members[],包括Receipt/Projection Target,只排除 deliverySetDefinitionDigest 自身;Registry-owned Status/Activated Time/Source Release 不在 Candidate 内。Contracts 必须发布 JSON/Canonical Bytes/Digest Golden;未知字段/Schema/算法、未排序、Count不等、孤立Member、Destination/Receipt/Projection错绑、裸Digest或同Version异Candidate/Digest全部拒绝。字段或排序语义变化升级Definition Schema Version,摘要算法变化升级Digest Algorithm Version。Registry只能按完整四元组insert-or-compare;Event、Delivery/Ack校验和批准归档Source View必须逐项保存或以不可变外键解析相同四元组和Member Target,禁止用Receipt自报Projection作为Expected。
首个完整Release必须精确注册十三个Applicable Pair:Run/Wallet v2、五类Billing经济边界、四类Owner间触发Event,以及billing.finalization-reconciliation.case-requested@1/1和billing.finalization-reconciliation-resolution.applied@1/1。Run/Wallet、五类Billing Pair与Finalization Case Requested各有唯一core_operations → operations_projector Mandatory Member,并冻结OperationsProjectionAppliedReceipt@1与当期精确Active Projection Name/Version;Execution Eligibility与Gateway Availability各有唯一metering_input_processor → metering_input_processor_consumer Member;Execution Closure有唯一billing_finalization_coordinator → billing_finalization_coordinator_consumer Member;Settlement Input有唯一billing_settlement_catch_up → billing_settlement_catch_up_consumer Member;Finalization Resolution Applied同时拥有billing_finalization_coordinator → billing_finalization_coordinator_consumer和core_operations → operations_projector两个Member。因此最小Definition为十三个Pair、十四个Member;run.created@2不得增加Billing Destination。Projection Cutover必须在同一受控锁中证明新版本Ready、原子切换Active Pointer并激活引用新Target的Definition;新Event使用新Definition,旧Pending Event继续满足自己冻结的旧Target,Shadow/其他Projection Receipt不能满足任何Member。最终Release可以加入其他已注册精确Pair,但不能使用前缀、通配、别名或latest,Count/Digest必须覆盖完整排序集合与Target;Producer在自身领域事务启用Event前先证明当前激活Definition适用且Member精确,否则事务失败。
Billing多Event事务为每个Event预分配独立ID、冻结精确Causation、计算各自Envelope Digest,并分别冻结Delivery Set四元组和创建Mandatory Rows。Finalization、每个Transition、request_open Case Requested、resolve Applied、Late Command Result+Applied必须按领域结果完整出现;任何Owner Fact/Result、Ledger/State、Audit、Event、Digest、Definition或Delivery Row失败都回滚整个Billing事务。一个Event的Delivery不能代替同事务另一个Event;Consumer乱序依靠各自Receipt、Case Identity Reservation、Exposure Revision和pending_resolution收敛。Contracts/PostgreSQL门禁覆盖同事务缺任一Event/Row、Event别名、错Causation、同Owner重复Event、Definition切换竞态和响应丢失重放。
Execution Eligibility Fact事务、Gateway attempt-bound Availability Current事务、Execution关闭事务和Metering Settlement Input Current事务分别原子写对应Owner事实/Pointer、Canonical Event摘要、冻结Definition四元组与上述唯一Mandatory Row。Metering Input Consumer把Eligibility与Gateway Usage按Attempt合流、Provider Cost按gatewayDeploymentId + sourceEvidenceDimensionKey独立分Lane;Billing分别以Finalization Fence/Work与Dimension Catch-up Fence/Work消费Closure和Settlement Input。Consumer只有在本地Inbox事务推进Watermark/Fence、insert-or-compare Work并写严格Owner Receipt后才Ack;等待另一输入必须是可恢复Work,不能成功后丢弃。同步响应、Poll、Operational Observation或一个Destination的Receipt都不能替代另一Mandatory Delivery。
在本阶段,published_at 只表示:事件已被其自身冻结Definition四元组所指的全部Mandatory Delivery确认应用。权威状态始终是每个eventId + destination + consumerName独立Delivery/Receipt;该兼容时间戳不表示当前Registry、所有未来Consumer、外部Broker或客户Webhook已处理。后续新增投影通过保留事件重建,不反向篡改历史published_at,也不能让一个Destination的Ack覆盖另一个。
Owner 间必达触发 Event
四个非Operations协调Event采用Event Contracts的严格封闭DTO,不与五类Billing经济边界Event或Operational Observation混用:
| Event | Aggregate / Revision | 唯一 Mandatory Destination | 只允许触发 |
|---|---|---|---|
execution.attempt-eligibility.finalized@1 | executionAttemptId / 1 | metering_input_processor | Eligibility/Settlement Input Lane Watermark与Work |
gateway.evidence-availability.current-changed@1 | Contracts确定性Attempt/Deployment/Kind/Dimension ID / Availability State Version | metering_input_processor | Usage Eligibility Lane或独立Provider Cost Lane Watermark与Work |
execution.run-finalization.closed@1 | runId / 1 | billing_finalization_coordinator | Finalization Closure Watermark、Trigger Snapshot与Work |
metering.settlement-input.current-changed@1 | Contracts确定性Reservation/Step/Attempt/Dimension ID / Settlement Input State Version | billing_settlement_catch_up | Finalization输入或终局后Catch-up Watermark/Fence/Work |
另有两类Finalization Reconciliation编排Event:billing.finalization-reconciliation.case-requested@1只投递core_operations并按预留Run级Identity创建Case;billing.finalization-reconciliation-resolution.applied@1同时投递billing_finalization_coordinator + core_operations,前者触发严格Owner状态重评,后者只在验证Billing Applied Fact后终结对应Case。两者都不是五类经济Boundary,不得推断Ledger或改写Billing Fence。
Execution Eligibility Event完整携带Tenant/Account/Reservation、Run/Step/Attempt、Attempt Manifest与Eligibility Fact四元组、Terminal及严格Dispatch/Output联合;Gateway Event完整携带Attempt/Deployment、Manifest/Binding/Route、Kind/Dimension、Current Availability五元组、Available/None Evidence联合和直接前驱五元组,Pre-binding Snapshot禁止产生它;Closure Event完整携带Tenant/Account/Reservation/Run、Run Admission Manifest、Finalization Fact/State Version、Attempt Set Manifest与Count;Settlement Input Event完整携带Tenant/Account/Reservation/Run/Step/Attempt/Dimension、经济身份、Current Snapshot与直接前驱五元组。每个Producer在Owner事实/Pointer事务中双向验证Payload、计算完整Envelope摘要并创建Mandatory Row;弱changed=true通知、同Owner第二Root/Event、同Revision异摘要、跨身份重绑或缺Delivery全部回滚。
Metering Input Fence严格分离eligibility_and_settlement_input与每个(gatewayDeploymentId,sourceEvidenceDimensionKey) Provider Cost Lane;Receipt分别冻结对应Fence Revision、Trigger Digest、不可变Metering Input Work完整四元组和Generation,不能共享一个Generic Revision/Work。Worker读取Execution Owner的Attempt Manifest只使用metering_execution_eligibility | metering_gateway_availability两个Event-bound Purpose;读取Gateway Owner的Availability/Evidence/Route正文另用metering_source_event_bootstrap,它只绑定已接受Gateway Current-changed Event完整身份与摘要,不预取Basis/Candidate/Validation Operation。Billing Finalization/Catch-up Consumer同样先将Event变成耐久Watermark/Fence/Work再Ack;Coordinator/Catch-up Receipt不能当作Current Validation、Consumption或Ledger证据。Admission首代Finalization Work在Closure Event/Coordinator Receipt尚未应用时只能以closure_event_not_applied阻塞并禁止Owner Read/Input/Decision/Ledger;成功Committed Fact/Event必须绑定Fence Revision、Closure Event身份/Envelope摘要和Coordinator Receipt四元组。S1 Receipt → S2 Settlement Event先到并Ack → Finalization按S1提交 → 无S3仍必须由早到Watermark与Finalization创建的Work追平S2。
3. 投递语义是 at-least-once
Dispatcher 允许“Consumer 已提交,但 Dispatcher 尚未确认”后进程崩溃,因此事件可能重复交付。平台明确承诺 at-least-once,不宣称端到端 exactly-once。
Active Consumer Ack必须返回Destination、Consumer Name、Event ID、Canonical Event摘要与封闭appliedReceipt联合:operations_projection | metering_input_processor | billing_finalization_coordinator | billing_settlement_catch_up。四个分支全部携带对应Owner Receipt完整四元组;跨库Dispatcher严格解码响应、重算Receipt摘要并比较Event/业务身份,必要时只用精确Audience/Scope的Owner Receipt定向Read复核,不伪造数据库外键。完整ActiveConsumerAck@1、OperationsProjectionAppliedReceipt@1、MeteringInputProcessorConsumerReceipt@1、两类Billing Receipt及其Read以Outbox Publisher为唯一Schema;Operations Receipt的Expected Name/Version必须来自Event冻结Definition Member的Projection Target,Receipt自报值不能作为Expected,Read Scope同时绑定Definition四元组和Member。Metering Receipt还必须用Eligibility/Provider Cost严格Lane联合冻结各自Fence Revision、Trigger摘要、Metering Input Work完整四元组及Generation;涉及Billing Finalization Work的两个Receipt分支同样冻结该Work完整四元组而非裸ID;Settlement Catch-up分支冻结Fence Revision、Catch-up Work完整四元组、Generation与确定性billingCatchUpValidationOperationId,并与同事务Fence Active Work逐项相等。Dispatcher验证冻结Member Target、当前Lease Token和所选Receipt后,在确认事务追加成功Attempt、把Delivery置为terminal并冻结Receipt引用、调用published_at受控函数;其他Mandatory未完成返回not_complete,最后一个完成才CAS写首次时间。不同Destination、Shadow Rebuild或错误Receipt分支不能完成当前Delivery。
Operations Projection Receipt是内容寻址的 applied receipt,而不是“已经看过”的坐标记录。严格OperationsProjectionAppliedReceipt@1保存自身Ref/Schema/Digest Algorithm/Digest、destination=core_operations、Consumer/Projection/Version/Event坐标、Event Type/Schema、Canonical Event Envelope摘要与appliedAt;Candidate覆盖除Repository-owned Ref/Time和摘要自身外的全部字段。readOperationsProjectionAppliedReceipt(Receipt完整四元组, expected destination/consumer/projection/event ID/Type/Schema/Envelope摘要)只向原Producer Dispatcher精确Audience/Scope返回found|not_found|conflicting并由双方重算摘要。Metering/Billing Consumer使用各自内容寻址Owner Receipt;所有分支都由冻结Destination→Consumer映射阻止互换。Operations Projector必须在同一个PostgreSQL事务中完成:
- 校验 Event Envelope 与明确支持的 Schema Version;
- 构造并校验
OperationsProjectionAppliedReceipt@1完整Candidate与唯一坐标; - 应用或跳过幂等的 Projection Mutation;
- 更新 Aggregate Revision 与当前 Projection Version 的应用状态;
- insert-or-compare完整Receipt四元组并返回它供Ack/定向Read使用。
重复的同一 eventId 与相同 Canonical Event Hash 返回已应用;同一 eventId 的 Envelope 或 Payload 任一字段改变导致 Hash 不同,属于完整性故障,必须隔离并告警,不能覆盖旧投影。
顺序只在单个 Aggregate 的正整数 aggregateRevision 上判断,不承诺全局事件顺序。Projector 必须:
- 对重复 Revision 幂等处理;
- 对乱序事件暂停该 Aggregate 的后续应用,保留待处理证据且不写 applied receipt;
- 对 Revision Gap 标记不完整并触发恢复,而不是猜测缺失事实;
- 通过
causationId、operationId和correlationId连接跨 Aggregate 时间线,不假设它们同时到达。
不支持的 Schema、损坏 Payload、身份冲突和无法恢复的 Revision 矛盾进入 Quarantine。可重试基础设施错误保留下一次到期时间;二者必须使用规范化错误分类,并在 Operations 自监控中可见。
4. run.created@2.0 是自包含低敏准入快照
Operations Projector 不通过 Domain Query 回查 Run、Manifest、Developer Access、Model 或 Billing 当前状态来“补全”历史事件。那会让重放结果随当前数据变化,也会把投影可用性绑到多个在线模块。
oceanway-contracts 下一版本必须新增 run.created@2.0。它在既有 Canonical Event Envelope 内携带创建时自包含、低敏的 Admission Snapshot,至少包含:
envelope.aggregateId = runId
envelope.correlationId / envelope.operationId # v2 必填、非空
envelope.tenantKind / envelope.tenantId / envelope.workspaceId / envelope.projectId?
run.billingAccountId / run.status / run.createdAt / run.updatedAt
run.executionManifestRef / run.executionManifestSchemaVersion
admission.requestId
admission.source.surface / apiVersion / operation
admission.executionPrincipalId
admission.authentication =
{ kind=developer_credential; developerAppId; environmentId; serviceAccountId; developerCredentialId }
| { kind=playground_execution_grant; developerAppId; environmentId; serviceAccountId;
playgroundExecutionGrantId; playgroundExecutionGrantSchemaVersion;
playgroundExecutionGrantClaimsDigestAlgorithmVersion; playgroundExecutionGrantClaimsDigest }
| { kind=customer_session; customerAuthenticationAssertionRef;
customerAuthenticationAssertionSchemaVersion;
customerAuthenticationAssertionClaimsDigestAlgorithmVersion; customerAuthenticationAssertionClaimsDigest;
productSurface }
| { kind=delegated_agent; delegationGrantRef; delegationGrantSchemaVersion;
delegationGrantClaimsDigestAlgorithmVersion; delegationGrantClaimsDigest;
agentRevisionId; originatingSurface }
admission.product
admission.authorizationDecisionId / authorizationDecisionSchemaVersion
admission.authorizationDecisionInputDigestAlgorithmVersion / authorizationDecisionInputDigest
admission.authorizationEvidenceEvaluationSetRef / authorizationEvidenceEvaluationSetSchemaVersion
admission.authorizationEvidenceEvaluationSetDigestAlgorithmVersion / authorizationEvidenceEvaluationSetDigest
admission.authorizationEvidenceArchiveCutRevision
admission.authorizationScopeSnapshotRef / authorizationScopeSnapshotSchemaVersion
admission.authorizationScopeSnapshotDigestAlgorithmVersion / authorizationScopeSnapshotDigest
admission.authorizationActionCount
admission.authorizationActionIds[] = sorted
admission.authorizationEvidenceEvaluationCount
admission.authorizationEvidenceEvaluations[] = sorted {
evidenceKind = authorization_decision | playground_execution_grant |
customer_authentication_assertion | delegation_grant
authorizationEvidenceEvaluationRef / authorizationEvidenceEvaluationSchemaVersion
authorizationEvidenceEvaluationDigestAlgorithmVersion / authorizationEvidenceEvaluationDigest
archiveRevision
}
admission.model.publicModelId
admission.model.logicalModelId
admission.model.modelOfferingRevisionId
admission.model.modelDeploymentId # initial admitted target
admission.model.modelRoutingPolicyRevisionId
admission.model.gatewayPool
admission.outputContractRef / outputContractSchemaVersion
admission.outputContractDigestAlgorithmVersion / outputContractDigest
admission.billing.billingReservationId
admission.billing.pricingSnapshotId
admission.billing.billingPolicyRevisionId配对的 wallet.reserved@2.0 必须把冻结预算作为同一个严格 BillingValue 联合携带:
reservation.value =
{ kind=credits; amount }
| { kind=entitlement; entitlementKey; quantity; unit }
| { kind=money; money={ amount; currency } }Credits Amount、Entitlement Quantity 与 Money Amount 都是同精度规范 Decimal 字符串,不得转成浮点数;Kind、Entitlement Key/Unit 与 Money Currency 都是经济身份,分支字段必须互斥且不能跨 Kind、Bucket、Unit 或 Currency 换算。
admission.authentication 与 tenantKind + tenantId 都保持严格判别联合。认证分支精确四选一,后两个分支禁止Service Account。Grant/Assertion与Authorization Decision版本/摘要必须和不可变Archive相等。Run Event还必须冻结准入时原子持久化的AuthorizationEvidenceEvaluationSet@1完整四元组、统一Archive Cut、Authorization Scope Snapshot完整四元组、规范排序去重Action IDs、authorizationActionCount与精确成员;Action Count必须等于Action数组长度,参与Event、Validation Record、Evaluation Set和Manifest摘要,但不能替代成员集合。Set必须为purpose=run_admission + authorizationEvaluationOperationId=operationId + result=valid,其Authentication Kind、主体/租户/Scope/Audience、Action Count/集合、Cut、Evaluation Count和成员与Event逐项相等;Event Scope四元组还必须等于Manifest executionAuthorization.authorizationScopeSnapshot*。历史重放以Event自包含的Set四元组、Operation/AuthKind、主体/租户、Scope四元组、Action Count/集合和Cut调用readAuthorizationEvidenceEvaluationSet,验证完整Set后再按同一Cut读取成员;不得要求从Manifest补Scope/Action、把逐成员读取拼成Set或按当前Revision重算。Archive在正文清理后仍保留Record/Revocation/Evaluation/Set。后到撤销不改写已提交Run。admission.product == source.surface,Tenant联合同样严格。
Event↔Manifest等值是Admission Producer同事务不变量。Producer在Append前验证run.created@2.0的主体、租户、Authentication/Authorization Evidence、Evaluation Set(Set四元组、Cut、Scope四元组、Action Count/集合、Evaluation Count与成员)、Product、Model/Gateway、Output Contract、Pricing/Billing/Reservation与Manifest逐项相等,并验证Wallet BillingValue同构等值。任一缺项、未知Schema/算法、摘要或字段错配都回滚Run、Manifest、Reservation、Event与Outbox。
Projector把Run/Wallet v2当自包含Source,不解引用Manifest。Authorization验证以Event自带Set四元组、Operation/AuthKind、主体/租户、Scope四元组、Action Count/集合和Cut调用Set Read,验证Purpose/Audience/Scope/Action Count/集合/Cut/Evaluation Count/成员/总结果后再按同一Cut读成员;缺项、Action Count与数组长度不等、不同Cut拼接、绕过Set读取或当前Revision重算值不得进入Snapshot。Projector仍校验Run↔Wallet共同字段;Event自身错误与跨Event冲突隔离,但不重新比较Manifest正文。
通用 Event Envelope 虽可允许其他事件省略关联字段,run.created@2.0 必须通过 Schema Refinement 强制 correlationId + operationId,wallet.reserved@2.0 必须强制 correlationId + operationId + causationId 为合法非空稳定 ID,并在 reservation 内强制 pricingSnapshotId + billingPolicyRevisionId + value。Wallet v2 的 causationId 指向同事务 Run Event;reservation.runId == run aggregateId;Wallet aggregateId == reservation.billingReservationId == run admission.billing.billingReservationId;Wallet Actor 不能改用 Billing 操作者,也不能从当前 Membership 回填。首个 Producer 只允许 Organization 且只启用 Developer Credential/Playground 两个 Capability;Customer Session/Delegated Agent 与 Personal Space 分别等待对应 Assertion/Delegation、tenant-aware Manifest、授权、Billing 与端到端门禁,不能伪造为开发者分支。已发布 wallet.reserved@1.0 继续按原 Organization-backed Schema 消费,不能把其可选 causationId/operationId 改成必填;它只形成 Legacy/Partial 证据,不能因缺失新字段被判为非法。
以下内容禁止进入该事件:
- Prompt、Run Input、System Prompt、参考素材和模型输出;
- Raw Secret、Credential Digest、Authorization/Cookie Header 和 Session;
Idempotency-Key、requestFingerprint、完整请求 Body 或 Header;- 完整 Execution Manifest、Provider Route、Provider Credential、原始 Gateway/Provider Payload;
- 签名 URL、媒体内容和不必要的个人信息。
executionManifestRef 是稳定、不透明、受授权解析的内部追踪引用,专门指向 Run 级 RunAdmissionManifest(现有 Contracts 中 Run-level ExecutionManifest 的语义角色);配合创建时 executionManifestSchemaVersion,Explorer 只展示引用与版本,不读取、复制或由 Projector 解引用正文。Output Contract 正文同样不进入事件;事件只携带可自包含重建的 Ref/Schema Version/Digest Algorithm Version/Digest 四元组,Operations 不回查当前 Manifest 或产品配置补齐/比较。Wallet BillingValue 也只使用 wallet.reserved@2.0 自包含字段。事件中的 modelDeploymentId 是 initial admitted target。首次 Attempt 必须匹配;后续 Attempt 只有在冻结的 modelRoutingPolicyRevisionId 允许时才能改变 Deployment,并必须创建新的 AttemptExecutionManifest 与因果记录,不能修改原 Manifest 或事件。run.created@1.0 与 wallet.reserved@1.0 都保持不可变,只能形成带 partial_legacy_event 的 Legacy/Partial 投影,不能查询当前领域状态伪造缺失字段。完成生产者、消费者与重建验证后,准入路径才原子切换为产生 run.created@2.0 + wallet.reserved@2.0。
5. Billing 经济边界、Case 身份预留与乱序收敛
Operations 不从 Wallet 当前状态、Ledger、Case 文案或 Observation 反推结算。Contracts 与 Delivery Set 必须精确注册下列五个低敏 Canonical Event 字面量;下划线别名、@1.0、通配版本或“latest”都按未知 Event隔离:
| Event Type | 严格 Payload | Owner 证明 |
|---|---|---|
billing.finalization.committed@1 | BillingFinalizationCommitted@1 | Finalization Committed Fact、Decision、Input Manifest、Validation Bundle完整四元组 |
billing.settlement-transition.committed@1 | BillingSettlementTransitionCommitted@1 | BillingSettlementTransition@1完整四元组 |
billing.late-settlement-exposure.case-requested@1 | LateSettlementExposureCaseRequested@1 | Transition、Open Exposure、Case Identity Reservation完整四元组 |
billing.case-resolution.applied@1 | BillingCaseResolutionApplied@1 | Applied Fact完整四元组;Payload与Billing Owner同名严格 DTO完全一致 |
billing.late-settlement-command-result.committed@1 | LateSettlementCommandResultCommitted@1 | Command Result与同事务Applied Fact完整四元组 |
五类 Event 都强制 correlationId + operationId + causationId、原 Run的 Actor/Tenant/Workspace/Project与低敏经济身份。aggregateId 等于当次不可变 Owner Fact/Result Ref,aggregateRevision=1;Dimension State/Exposure/Reservation Revision只放在 Payload,不能冒充 Event Aggregate Revision。Finalization由原 wallet.reserved@2.0 Event引起;首个Dimension Transition由Finalization Event引起,后续Transition由同Dimension直接前一个推进State Revision的Transition或Late Result Event引起,并严格要求前驱toStateRevision == 当前 fromStateRevision;Case Requested由同事务Transition引起;Late Result由授权的最新Open Exposure Case Requested引起;Applied由它所引用的Transition或Late Result Event引起。所有后继 Event ID在事务开始前分配,禁止以时间相近、投递顺序或更早Transition猜因果。
严格 Payload 的最小闭包如下,所有 Ref / Schema Version / Digest Algorithm Version / Digest 都是不可裁剪四元组:
BillingFinalizationCommitted@1 =
own committed fact quartet
+ finalization decision quartet + decision input digest
+ FinalizationInputManifest quartet + FinalizationValidationBundle quartet
+ Reservation/Account/Run + Billing Finalization Fence ID/Revision
+ Closure Source Event ID/Type/Schema/Envelope Digest + Closure Consumer Receipt quartet
+ Billing Policy/Finalization Rule + SettlementEconomicIdentity
+ exact sorted Dimension Consumption commitments + resulting Dimension State revisions
+ exact sorted Ledger Effects + Release Effect union
+ resulting Reservation status/revision
BillingSettlementTransitionCommitted@1 =
BillingSettlementTransition quartet
+ logical Billing Transition Operation + deterministic Validation Operation
+ Reservation/Account/Run/Step/Attempt/Dimension + SettlementEconomicIdentity
+ Current SettlementInputSnapshot quartet/state version
+ ordered non-empty SettlementInput lineage, each item carrying full quartet/state version
+ SettlementInputConsumption quartet + from/to State revision
+ Ledger Effect union
+ Case Action(none | request_open | resolve) carrying Case Generation, Case Reservation and Exposure quartets
LateSettlementExposureCaseRequested@1 =
Case Generation + Case Request Operation + preallocated Case Ref + Case Identity Reservation quartet
+ originating Transition quartet + Transition Operation/from/to State revision
+ current Open Exposure quartet/revision
+ Reservation/Account/Run/Step/Attempt/Dimension + Pricing/Billing Policy + SettlementEconomicIdentity
BillingCaseResolutionApplied@1 =
exact Billing-owned Applied Fact DTO, including own quartet/idempotency key
+ Case Generation + Case Ref/Identity Reservation quartet + resolved Exposure quartet/revision
+ Reservation/Account/Run/Step/Attempt/Dimension + SettlementEconomicIdentity
+ Transition-or-Command-Result source quartet
+ Ledger Effect strict union + resulting Settlement State/Pointer + recorded time
LateSettlementCommandResultCommitted@1 =
Command Result quartet + Operation/status
+ Case Generation + Case Ref/Identity Reservation quartet
+ Reservation/Account/Run/Step/Attempt/Dimension + Policy + SettlementEconomicIdentity
+ authorized Open Exposure quartet/revision + resolved Exposure quartet/revision
+ Ledger Effect quartet + from/to State revision
+ same-transaction BillingCaseResolutionApplied quartetFinalization Event的Status、Reservation终态、Dimension/Consumption集合、Ledger与Release必须双向严格一致:release_all只允许零目标、零Ledger、Reservation released和精确余量释放;settle_and_release_remainder只允许Reservation settled、Ledger精确达到Manifest目标并按剩余量形成Release。成功Fact/Event还必须完整携带Billing Finalization Fence ID/Revision、Closure Source Event ID/Type/Schema/Envelope摘要与Closure Consumer Receipt四元组,并与Fence Watermark/Trigger、Execution Owner Read、Input Manifest和Execution Receipt逐项等值;Closure Event/Consumer Receipt未先耐久应用时只允许closure_event_not_applied阻塞Work,禁止进入Owner Read、Input Manifest或任何成功Event。Dimension集合与Input Manifest全维度一一相等,所有Effect与同一Billing事务真实写入集合精确等值。Projector分别以Event自带四元组和期望身份调用 readBillingFinalizationCommitted | readBillingSettlementTransition | readLateSettlementExposure | readBillingCaseResolutionApplied | readLateSettlementCommandResult,严格复算Owner正文并与Event逐项比较;摘要自洽不能替代这些语义Refinement,也不能按裸Ref读取或从相邻Event补字段。Transition Event同时携带稳定逻辑billingTransitionOperationId与由Contracts固定Namespace按(billingTransitionOperationId, billingReservationId, runStepId, executionAttemptId, chargeDimensionKey, fromStateRevision, basisDigestAlgorithmVersion, basisDigest)确定性派生的billingTransitionValidationOperationId;后者必须与Owner Fact/Consumption/Receipt逐项相等,不得冒充Envelope Operation或跨From Revision复用。其非空有序settlementInputLineage[]把Owner fromLastEvaluatedInput到Current Input之间每个Snapshot完整四元组/State Version逐项冻结;Projector以readSettlementInput精确读取历史中间项、验证无缺口直接前驱链和末项Current等值,不要求中间项继续为Current,也不允许只凭最新Snapshot跳链。Transition、Case Requested、Applied与Late Result都必须自包含并逐项保持Billing Account/Reservation/Run/Step/Attempt/Dimension;Case Requested必须自带Originating Transition的Operation和from/to State Revision,确保它先于Transition Event到达时仍能独立构造严格Owner Read,禁止Attempt→Step隐式Bootstrap。Transition resolve的Resolution Operation按(billingTransitionOperationId, caseGeneration, predecessor Exposure完整四元组/Revision)确定性派生;Late Command Applied固定resolutionOperationId=lateSettlementOperationId,Projector必须连同Source专属直接前驱验证。Finalization Input/Validation Bundle或Transition Snapshot涉及的Gateway Usage、AttemptRouteBinding、GatewayRouteSnapshot与Provider Usage Evidence都只接受各自完整四元组;任何Projection/Query不得把它们压成裸Ref或Ref/Schema,也不得从相邻Event补齐。
Finalization的dimensionCommitments[]与ledgerEffects[]都按(runStepId, executionAttemptId, chargeDimensionKey[, Effect Ref])规范排序,必须与Owner Fact、Input Manifest、Consumption、Dimension State、Ledger和Event逐项等值;缺失/错误Step、旧的Attempt-only排序或跨Step重绑都在写Projection/Receipt前拒绝,不能通过Attempt→Step映射补值。
Operations唯一拥有 ReconciliationCaseIdentityReservation@1 与业务Case。Billing首次需要打开Late Settlement Case时先调用无业务副作用的 reserveReconciliationCaseIdentity:Operations以 (sourceOwner=billing, sourceKind=late_settlement, billingReservationId, billingAccountId, runId, runStepId, executionAttemptId, chargeDimensionKey, caseGeneration) 唯一,在同一事务分配稳定Case Ref、持久化严格Reservation四元组并返回;caseRequestOperationId由Contracts固定Namespace对这组完整Source+Generation字段确定性派生,调用方不能覆盖。第一代必须 generation=1 + predecessor=root;后继必须恰好前代+1并携带前代Case Ref与Identity Reservation完整四元组。同Generation/Operation重放返回原记录,同Generation异候选冲突。预留本身不创建可见Case、告警或审批。受限 readReconciliationCaseIdentityReservationBySource 以精确Source Key、Generation、期望确定性Operation与Predecessor读取并返回完整Reservation四元组,只向Billing Case Identity Workload Audience/Scope开放,不提供列表、模糊查询或裸Ref读取。若reserve已提交而Billing在保存响应或Transition前崩溃,新Worker必须先以同一Source/Generation/Predecessor调用该Read或重放reserve,严格验证并采用原Reservation,禁止生成第二个Case Ref或让孤儿预留封锁Generation。Operations还提供受限 readReconciliationCaseByReservedIdentity:请求携带Reservation完整四元组及期望Case Ref、Billing Reservation/Account、Run/Step/Attempt/Dimension、Generation与Case Request Operation;响应严格为not_created | found{Case Ref/Revision, Current Source Exposure完整四元组/Revision, Case State} | conflicting。服务端与调用方都重算并验证Reservation/Case/Exposure/Repository绑定;not_created只适用于合法预留尚未建Case,不存在或错摘要不得降格。Create已提交但Ack丢失时Billing用该Read采用原Case,不另建同代Ref。opening|open的新Exposure沿用同代Case;旧代进入resolving后再次出现正差额必须新预留下一代,旧Approval/JIT/Grant不得迁移。Billing只有取得Reservation后才能形成Transition Basis,并在Case Requested Event中同时携带Generation、Case Ref、Reservation四元组与Exposure四元组;Operations先验证本地Reservation/Predecessor,再按预分配 (Generation,Ref) 与同代单调Exposure Revision创建/更新。
Finalization Reconciliation使用另一条Run级Identity协议。FinalizationReconciliationCaseIdentityReservation@1严格覆盖自身四元组、预分配Case Ref、sourceOwner=billing/sourceKind=billing_finalization、规范化tenantKind/tenantId/workspaceId/projectId?、Account/Reservation/Run/Fence、Generation、确定性Case Request Operation与Predecessor;摘要排除Repository-owned Ref/Time和摘要自身。caseRequestOperationId由Contracts固定Namespace按上述稳定Source身份确定性派生。reserveFinalizationReconciliationCaseIdentity与readFinalizationReconciliationCaseIdentityBySource的Audience/Scope逐项绑定Tenant、Account/Reservation/Run/Fence、Generation、Operation、Predecessor,禁止Admin、列表、latest、裸Ref和跨Source;Identity与这两个接口都不得绑定可被并发后继替换的Triggering Work或billingFinalizationOperationId。reserve(G1) → G2 Work取代G1 → G1 CAS失败 → G2必须复用相同Identity并只让G2 winning Work进入最终Event。
billing.finalization-reconciliation.case-requested@1自包含Tenant、Account/Reservation/Run/Fence、Case/Generation/Expected Resolution Revision、Case Request Operation、最终billingFinalizationOperationId、Identity quartet、Decision quartet、winning Work quartet与triggeringBillingFinalizationWorkGeneration、reasonCode。Operations只可调用readBillingFinalizationDecisionForOperations(purpose=operations_finalization_case_projection, caseRequestedEvent={eventId/eventType/eventSchemaVersion/eventEnvelopeDigestAlgorithmVersion/eventEnvelopeSha256}, expected 扁平Tenant/Account/Reservation/Run/Fence/Case/Generation/Resolution Revision/两Operation/Identity quartet/Decision quartet/Work quartet+Generation);响应严格为found{decision=BillingFinalizationDecision@1; triggeringWork=BillingFinalizationWork@1} | not_found | conflicting{conflict quartet}。Resolution Event则调用readFinalizationReconciliationResolutionAppliedForOperations,同样绑定resolutionEvent完整四元组、扁平Expected Tenant/Account/Reservation/Run/Fence/Case/Generation/Resolution Revision/Operation/Actor、Identity与Applied Fact quartet,返回found{fact}|not_found|conflicting。两个Read使用Operations Projector独立Audience/Scope,Owner和Projector双方重算摘要;not_found保留Delivery non-terminal,conflicting隔离,禁止当前Fence/latest/裸Ref读取。
Operations Case、Authorized DTO和Explorer Cursor必须使用严格sourceScope联合:late_settlement_dimension分支保留Run/Step/Attempt/Dimension、经济身份、Exposure和BillingCaseResolutionApplied;billing_finalization_run分支只保留Run/Fence、Identity、Case Request Event、Decision、winning Work/Generation、reasonCode和Finalization Resolution Applied,不得伪造Step/Attempt/Dimension或Exposure。Case稳定排序为(sourceKindOrder,billingAccountId,billingReservationId,runId,sourceScopeSortKey,caseGeneration,reconciliationCaseRef),Dimension的Scope Key为(runStepId,executionAttemptId,chargeDimensionKey),Run级为(billingFinalizationFenceId);Cursor签名必须绑定联合分支与完整Scope。Resolution先于Request时,Owner验证完成后持久pending_resolution + OperationsProjectionAppliedReceipt;Request后到直接materialize Resolved。
C2 Request先于C1 Resolution时必须写有可唤醒语义的耐久载体,而非仅做文字归属。Operations在一个Projector事务锁同Source前代Case与Pending集合,insert-or-compare严格pending_case_successors_vN行和C2 Event的OperationsProjectionAppliedReceipt@1后Ack;行完整冻结后继Tenant/Account/Reservation/Run/Fence、Case/Generation、Identity quartet、直接Predecessor Identity联合、Case Requested Event完整Envelope、两Operation、Decision、winning Work quartet/Generation、reasonCode,禁止创建第二Open Case。同一前代/后继代次异任何冻结字段冲突。C1 Resolution到达且C1已存在时,同一事务用Billing Applied Fact终结C1、物化C2并把Pending标为consumed,冻结所用Resolution Event/Fact和C2初始Revision;若C1 Request尚缺,则先保留既有pending_resolution,待C1 Request到达时在一个事务直接物化Resolved C1、消费C2并创建C2。多代乱序消费最大连续、全部Owner验证的Generation链,至多末代可Open;C2 Request → C1 Resolution → C1 Request、并发反序与Shadow Rebuild必须同结果。Owner暂不可读返回retryable且不写Pending/Outcome/Receipt;不得提交deferred。
两个sourceScope分支都使用严格terminalAuthority=not_terminal | billing_owner_applied{ownerFact=late_settlement|billing_finalization quartet}。非终态必须且只能没有Applied Fact并为not_terminal;resolved必须且只能带同分支Applied Fact,并让Authority逐字节引用相同四元组。Operations/Admin对这两类Billing Case的本地resolved | rejected | no-action恒定拒绝;本地终结与Billing Resolution Event并发时,本地事务始终零终态副作用。Query权限不足时裁剪整个Case,不能单独隐藏Authority。
Finalization Resolution最小命令是ADR-031/Metering-Settlement上线门禁,本ADR只冻结Schema与只读投影,不启用命令入口。依赖顺序固定为:先冻结FinalizationReconciliationResolutionCommandRequest@1业务Payload;基于它完成Impact Preview、Approval/JIT;AdminCommandCandidate@1(commandType=finalization_reconciliation_resolution)的既有commandRequest.payload严格选择该Request并保存完整四元组;Operations锁Case后形成FinalizationReconciliationResolutionDecision@1;Core最后签发SignedWorkforceCommandExecutionGrant@1.commandSpecialization.kind=finalization_reconciliation_resolution。Request/Decision摘要均排除Repository-owned Ref/Time与摘要自身,Request不含后续对象,Decision不含Grant/Operation;resolutionOperationId仅按完整Decision quartet确定性派生,因而无环。Grant aud=billing_finalization_reconciliation_resolution_command、boundWorkload=Admin Command Gateway Workload Principal且等于实际调用JWT sub,Grant sub=actorWorkforcePrincipalId。
Core Grant Exchange使用独立operation-first恢复:commandGrantExchangeOperationId由Contracts固定Namespace仅按actorAssertionIssuer + actorAssertionJti确定性派生,两字段分别逐字节等于已验证Actor Assertion JWT的iss + jti;Candidate不进入Operation ID,Actor、Bound Workload、Request Digest/Nonce、Candidate quartet与Grant quartet/JTI都进入内容寻址CommandGrantExchangeResult@1并insert-or-compare。Core先认证Caller Workload JWT且要求Subject等于Bound Workload,再在Assertion时间/JTI检查前按Operation读取Result;同正文found返回首次Grant,即使Assertion已过期或JTI已消费,异正文conflicting停写,只有not_found才执行当前校验并原子消费Assertion JTI、保存Grant/Result与签发Audit。响应丢失跨Assertion到期、同JTI偷换Candidate/Request/Nonce/Actor/Workload、并发唯一键竞争与Audit失败回滚必须有负测。
命令唯一action=reevaluate_current_owner_state,非空investigationEvidenceReferences[]只允许Contracts Registry封闭Tuple并排序去重;它只是授权调查引用,Billing只验证Request/Candidate/Decision/Grant/Fact间集合等值和Schema注册,不能解引用它证明Owner已修复。Billing入口在Grant时间/JTI/Fence检查前先调用readFinalizationReconciliationResolutionAppliedByOperation(resolutionOperationId + expected Tenant/Account/Reservation/Run/Fence/Case/Generation/Revision/Actor/Identity/Request/Candidate/Decision/Grant quartet/JTI);found完整比较后返回首次Fact,即使Grant已过期或JTI已消费,conflicting停写,只有not_found执行新事务。Applied Fact是稳定Result;Event表示“受控重评授权已应用”。真正安全性来自后继Finalization严格Owner Reads和Current Receipts;冲突未消失则创建新Case代次。Request/Candidate/Decision/Grant禁止金额、Ledger、Reservation终态、替代Snapshot、忽略冲突与自由Map。任何Producer可能进入reconciliation_required前,必须与ADR-031完整命令链同批启用;不能先上线无出口Fence。
一个或多个 BillingCaseResolutionApplied@1 可能先于Case Requested到达。Projector先验证Case Reservation/Generation/Predecessor、受限 readBillingCaseResolutionApplied与Source专属Exposure前驱:Settlement Transition允许同代Current Open→Resolved及连续零/负更正的Current Resolved→Resolved直接后继,Late Command只允许授权Current Open→Resolved;跳过前驱、回指旧Open、跨Generation/Reservation全部拒绝。只有全部Owner Fact与中间Exposure链均可读且验证完成后,Projector才在同一事务把每个 (Case Generation, Case Ref, resolved Exposure Revision, Applied Fact完整四元组) 唯一保存为 pending_resolution并写Event Receipt。后到同代Case Requested必须选择最高连续、已验证Revision并在单一事务直接创建绑定对应Fact的Resolved Case,不能短暂提交Open。已有Case只在同Generation按Exposure Revision返回 applied | superseded_noop | already_applied | conflict;已Resolved Case收到更高合法Resolved直接后继时追加Timeline并原子推进Source Binding、状态保持Resolved。中间Owner Fact暂不可读或链不完整时必须返回retryable,不写Pending/Outcome/Event Receipt、不提交任何投影副作用,让原Event Delivery保持non-terminal并按既有Lease重试;禁止把这一瞬态耐久化为deferred。评论、指派、审批等本地Case Revision不能永久拒绝经济事实,更高Generation使用新Case/Reservation,晚到旧代Create/Ack不得覆盖新代或把Resolved恢复为Open。Late Result Event自身必须携带Generation、Result、Case Reservation、Open→Resolved Exposure与Applied Fact四元组,Consumer足以构造两个Owner Read请求,不能依赖另一Event先到。
sourceOwner=billing + sourceKind=late_settlement的Case采用分域协作、单一终态事实Owner:Operations拥有Case/Identity Reservation与协作元数据,但首期禁止本地普通命令、评论或“无动作关闭”把它写成resolved|rejected;只有完整验证且原子绑定BillingCaseResolutionApplied@1四元组的Billing Owner路径能驱动终态,Case的terminalAuthority=billing_owner_applied必须与当前Applied Fact等值。平台承担、供应损失或核销必须等未来新增Billing-owned Disposition契约,不能复用本地Case终态绕过账务边界。乱序与并发测试必须覆盖本地终结和新Exposure/Applied竞争、Applied先于Create以及连续Resolved Revision,最终只能收敛为一个Owner驱动终态。
Billing每次成功边界都在同一PostgreSQL业务事务提交Owner Fact/Result、Ledger/Release/Reservation或Dimension State、Exposure/Applied Fact、Audit、Canonical Event及其Envelope Digest、冻结的Delivery Set四元组和全部Mandatory Delivery Rows。Finalization至少发Finalization Event;每个已应用Transition发Transition Event,request_open同事务加Case Requested,resolve同事务加Applied;Late Command成功同事务发Result Committed与Applied。任一事实、Audit、Event、Outbox或Delivery Row失败全部回滚;投影失败只重试消费,绝不回滚已提交账务。Contracts/Repository/PostgreSQL测试覆盖五类Payload Golden、事件名别名拒绝、Owner四元组/摘要篡改、错Aggregate/Causation、跨Case/Generation/Exposure/Dimension/经济身份、Generation root/连续Predecessor、resolve-before-create、Ack丢失、旧代Create晚到、并发Revision/Generation超越、事务缺任一Event/Delivery和Shadow Rebuild确定性;并覆盖Billing停在S1而Metering已连续形成S2→S3时以完整Lineage一次catch-up到S3、Lineage漏项/分叉/错摘要,以及F→旧Basis/Receipt→并发前驱推进→CAS失败→按新From State重建Basis与新Validation Operation→成功Event,证明旧Receipt不可采用且逻辑Operation仍稳定。
Transition Lineage Owner Read固定使用readPurpose=operations_transition_audit与独立Operations Projector Audience;请求Expected Identity与Scope同时绑定Tenant/Workspace/Project、Billing Account/Reservation、Run/Step/Attempt/Dimension、Event ID/Type/Schema/Envelope摘要、Transition四元组和目标Snapshot五元组,服务端与Projector逐项比较;禁止Admin使用、跨Event复用或枚举其他Snapshot。
6. Operational Observation 与领域事实分离
API Edge 中的每次 HTTP 接收、准入前失败、幂等重放与提交确定性不能伪装成 Run Domain Event。Contracts 分离生产者发送的 OperationalObservationSubmission@1.0 与 Core 接受后保存的 AcceptedOperationalObservationEnvelope@1.0,由 API Edge 通过 Core Operations Intake 提交低敏 RequestAttempt / ErrorOccurrence;受信来源身份和 acceptedAt 只能由 Core 注入,Producer 只能提供可选的来源声明时间。
Submission 与 Accepted Envelope 不共用一张可由 Producer 任意填写的字段表,首期同名 Schema 摘要固定为:
OperationalObservationSubmission@1.0
observationId / schemaVersion / sourceClaimedObservedAt?
requestId / errorId? / traceId? / correlationId? / operationId? / runId?
surface / apiVersion? / operation?
claimedTenantKind? / claimedTenantId? / claimedWorkspaceId? / claimedServiceAccountId? / claimedDeveloperCredentialId?
payload = RequestAttempt@1 | ErrorOccurrence@1 | GatewayDiagnostic@1
RequestAttempt@1
requestKind / outcome / submissionState / normalizedCode?
httpStatusClass? / idempotencyDisposition? / attributes(严格白名单)
ErrorOccurrence@1
layer / phase / normalizedCode / errorNormalizationPolicyVersion / severity
certainty / retryDisposition / compensationDisposition
messageTemplateKey / sanitizedParameters
causeErrorId? / technicalDetailRef?
runStepId? / executionAttemptId?
gatewayExecution? {
gatewayPool / gatewayDeploymentId
attemptRouteBindingRef / attemptRouteBindingSchemaVersion
attemptRouteBindingDigestAlgorithmVersion / attemptRouteBindingDigest
gatewayRouteSnapshotRef / gatewayRouteSnapshotSchemaVersion
gatewayRouteSnapshotDigestAlgorithmVersion / gatewayRouteSnapshotDigest
executionAnchor = { kind=text_invocation; gatewayInvocationId } | { kind=media_task; gatewayTaskId }
}
GatewayDiagnostic@1
gatewayPool / gatewayDeploymentId
gatewayAttemptDispatchSlotId
gatewayPreBindingRequestId?
executionAnchor? = { kind=text_invocation; gatewayInvocationId } | { kind=media_task; gatewayTaskId }
providerAttemptId? / capability / configRevision
normalizedStatus / stateVersion / submissionCertainty
executionAttemptId?
attemptRouteBindingRef? / attemptRouteBindingSchemaVersion?
attemptRouteBindingDigestAlgorithmVersion? / attemptRouteBindingDigest?
gatewayRouteSnapshotRef? / gatewayRouteSnapshotSchemaVersion?
gatewayRouteSnapshotDigestAlgorithmVersion? / gatewayRouteSnapshotDigest?
usageEvidenceAvailability = GatewayEvidenceAvailabilitySnapshotSummary@1 {
availabilityRef / availabilitySchemaVersion
availabilityDigestAlgorithmVersion / availabilityDigest / stateVersion
providerEvidenceKind=provider_usage / evidenceDimensionKey=attempt_aggregate@1
availabilityIdentity =
{ scope=attempt_bound; executionAttemptId; gatewayDeploymentId;
attemptRouteBindingRef; attemptRouteBindingSchemaVersion;
attemptRouteBindingDigestAlgorithmVersion; attemptRouteBindingDigest;
gatewayRouteSnapshotRef; gatewayRouteSnapshotSchemaVersion;
gatewayRouteSnapshotDigestAlgorithmVersion; gatewayRouteSnapshotDigest }
| { scope=pre_binding_no_execution; gatewayAttemptDispatchSlotId;
gatewayPreBindingRequestId; gatewayDeploymentId }
supersedesAvailability =
{ state=root }
| { state=supersedes; supersedesAvailabilityRef; supersedesAvailabilitySchemaVersion;
supersedesAvailabilityDigestAlgorithmVersion; supersedesAvailabilityDigest;
supersedesAvailabilityStateVersion }
stateReasonCode
state = available { providerUsageEvidenceRef; providerUsageEvidenceSchemaVersion;
providerUsageEvidenceDigestAlgorithmVersion; providerUsageEvidenceDigest }
| not_reported | pending | unavailable | conflicting
}
costEvidenceAvailability = GatewayEvidenceAvailabilitySnapshotSummary@1 {
availabilityRef / availabilitySchemaVersion
availabilityDigestAlgorithmVersion / availabilityDigest / stateVersion
providerEvidenceKind=provider_cost / evidenceDimensionKey=attempt_aggregate@1
availabilityIdentity =
{ scope=attempt_bound; executionAttemptId; gatewayDeploymentId;
attemptRouteBindingRef; attemptRouteBindingSchemaVersion;
attemptRouteBindingDigestAlgorithmVersion; attemptRouteBindingDigest;
gatewayRouteSnapshotRef; gatewayRouteSnapshotSchemaVersion;
gatewayRouteSnapshotDigestAlgorithmVersion; gatewayRouteSnapshotDigest }
| { scope=pre_binding_no_execution; gatewayAttemptDispatchSlotId;
gatewayPreBindingRequestId; gatewayDeploymentId }
supersedesAvailability =
{ state=root }
| { state=supersedes; supersedesAvailabilityRef; supersedesAvailabilitySchemaVersion;
supersedesAvailabilityDigestAlgorithmVersion; supersedesAvailabilityDigest;
supersedesAvailabilityStateVersion }
stateReasonCode
state = available { providerCostEvidenceRef; providerCostEvidenceSchemaVersion;
providerCostEvidenceDigestAlgorithmVersion; providerCostEvidenceDigest }
| not_reported | pending | unavailable | conflicting
}
resultSummary = GatewayDiagnosticResultSummary@1
GatewayDiagnosticResultSummary@1 =
{ state=not_available; reason=not_terminal|not_reported|not_applicable }
| { state=available; outputDisposition=no_output;
outputItemCount=0; outputModalitySet=[] }
| { state=available; outputDisposition=reported;
outputItemCount=positive_integer_up_to_contract_limit;
outputModalitySet[]=sorted_unique(text|embedding|rerank|image|video|audio) }
AcceptedOperationalObservationEnvelope@1.0 = canonical Submission + Core-only fields
acceptedAt / source.workloadPrincipalId / source.service / source.environment
source.region? / source.deploymentId? / source.instanceId?
observationDigestAlgorithmVersion = jcs-sha256-v1
submissionPayloadSha256payload.type 是严格判别字段,三个分支拒绝其他分支字段和未知字段;Error 分支必须携带 errorId 以及实际 Adapter 使用的 errorNormalizationPolicyVersion。Intake 按受信 Workload/Deployment 支持矩阵验证 Policy,并验证 normalizedCode 属于该版本的注册值。规范错误码不再并存 normalizedErrorCode 或裸 code。Attributes/Parameters 均为 Contracts 固定字段、类型、长度、编码与分类的子 Schema,不能成为任意 JSON 逃逸口。最终字段只以 Contracts 与事件和 Observation 契约为准。Submission 出现 acceptedAt、source.*、service、observationDigestAlgorithmVersion、submissionPayloadSha256、errorFingerprint、errorFingerprintVersion、Gateway 私有 backendModelId 或任一分支未知字段必须拒绝;Core 不能采取“忽略后继续”的歧义行为。可信 Service 只能取自 Core 根据 Workload Registry 注入的 source.service,不能使用 Producer Claim。
GatewayDiagnostic 的 Usage/Cost Availability 均必填,且所有状态都携带不可变 Snapshot Ref/Schema Version/Digest Algorithm Version/Digest/State Version/Kind/固定 Dimension/严格 Identity/前驱联合/Reason;attempt_bound Identity 中的 Binding/Route 以及 available 的 Evidence Head 全部使用 Ref/Schema Version/Digest Algorithm Version/Digest 四元组,其他状态禁止全部 Evidence 四元字段。resultSummary同样必填并只允许上述封闭低敏联合:Pre-binding固定not_available,reported的Count必须为正且不超过Contracts上限、模态排序去重并与Capability相容,no_output固定零/空;文本、URL、文件、MIME、尺寸、Hash、Asset、原始状态或自由Map都拒绝。缺 Snapshot/Result Summary或四元组字段、错摘要/版本/Kind/Dimension/Identity/前驱、错类 Evidence Ref、未知状态、Summary分支/Count/模态错误或混合字段均拒绝。两类 Snapshot 必须共享同一 Identity Scope,且外层 gatewayAttemptDispatchSlotId 对两个 Scope 都必填:attempt_bound 要求外层 executionAttemptId + Binding完整四元组 + Route完整四元组 + executionAnchor 同时存在、不得只给子集,Slot、Route与Anchor必须与受信 readAttemptRouteBinding 返回的完整 Binding 逐项相等;Operations持久层与Query DTO必须原样round-trip这份严格二选一Anchor,不能只把它保留在Source Envelope或Error专用联合。pre_binding_no_execution 仅用于 Binding 前且可证明无 Provider Side Effect 的拒绝,外层与两类 Snapshot 必须共享相同 gatewayAttemptDispatchSlotId + gatewayPreBindingRequestId,省略 Attempt/Binding/Route/Anchor,且两类 Snapshot 只能是 stateVersion=1 + root + not_reported|unavailable、不得携带 Evidence 四元组,Result Summary固定not_available。Pre-binding 分支不可进入 Metering/Eligibility/Finalization,不能证明无 Side Effect 时必须使用 Attempt-bound。Anchor 严格二选一且 Text/Media Pool 分别只接受 Invocation/Task。Gateway Error 使用 Error 分支内的严格 gatewayExecution:发生上述边界后,外层 runId 与 Payload 的 runStepId + executionAttemptId + gatewayExecution 全部必填,Binding/Route 完整四元组必填,执行锚点必须在 gatewayInvocationId | gatewayTaskId 中严格二选一;Core 只使用同一 Error Observation 自包含的完整四元身份逐项验证 Run/Step/Attempt、Deployment、Binding、Route 与 Anchor,禁止查询当前版本或从相邻记录补字段。Contracts 必须覆盖 Slot 缺失、两 Scope 混合、外层与任一 Pre-binding Snapshot Slot/Request ID 不等、Attempt-bound Slot、Binding或Anchor不等、同 Attempt 换 Slot、Pre-binding 偷带 Attempt/Evidence/Anchor、多 Attempt 错绑、Binding/Route/Evidence 四元组缺项或错配、摘要或算法未知、三件套缺项、两类 Anchor 同时/均缺、wrong-pool、Availability Snapshot/Evidence 错配、Result Summary缺失/分支混合/Count非法/模态非法/内容注入、Slot/Anchor/四元组/Result Summary round-trip 丢失与未知字段负向测试。
Error Fingerprint 的规范 Owner 是 Contracts,唯一 Writer 是 Core Operations Projector。Projector 从已校验字段按版本化 Normalization Policy、UTF-8 NFC、RFC 8785 Canonical JSON 与 SHA-256 确定性计算,并保存 Policy Version、Fingerprint Version 和 efp1_ 值;固定字段集、空值规则和测试向量以事件契约为准。任何 Producer 提交值都不能信任或比较;算法或字段改变必须升级版本,重建不得让旧新版本混组。
未认证请求可能没有可信 Tenant、Actor、Operation 或 Run;这些字段必须保持缺失,不能从 keyId、IP 或输入内容推断。Submission 中即使存在 claimed Tenant/Developer Access、operationId 或 runId,也只是 Producer Claim,不能直接用于租户授权、operation_linked 或领域时间线。Projector 的关联结果固定为严格联合:
association =
{ trust=producer_claimed; associationEvidence={ state=none } }
| { trust=domain_verified;
associationEvidence =
{ kind=domain_event;
eventId; eventType; eventSchemaVersion;
eventEnvelopeDigestAlgorithmVersion; eventEnvelopeSha256 }
| { kind=admission_context;
admissionContextRef; admissionContextSchemaVersion;
admissionContextDigestAlgorithmVersion; admissionContextDigest } }只有 Projector 通过对应 Canonical Domain Event,或 Core 签发且可验证的不可变 Admission Context 交叉验证后,才能产生 domain_verified。Domain Event 分支固定 Event ID/Type/Schema Version 与覆盖完整 Envelope+Payload 的摘要身份;Admission Context 分支固定 Core append-only Registry 中 Ref 永不复用、内容寻址的 Context 四元组,摘要覆盖关联所依据的 Actor/Tenant/Workspace/Developer Access/Operation/Run Claims。Projector 必须在同一投影事务校验证据、Observation Claim、目标领域事实和授权分区逐项相等,不能只存裸 Ref、混合分支、用当前 Run/Session 补值或把摘要失败降格成可信关联。producer_claimed 必须且只能携带 state=none,domain_verified 必须且只能携带一个完整分支;缺项、未知 Schema/算法、摘要错配、同 Ref 异正文、错标 Tenant/Run 或跨租户重绑都隔离关联。未验证记录只允许显式跨租户 Observation 调查 Scope 查询。Observation 禁止包含 Raw API Key、Credential Digest、Prompt、Request Body、Headers、完整 IP、Provider Payload、堆栈和数据库错误。确需安全调查的网络属性只能按数据治理策略生成受控、限期的证据引用,不进入通用读模型。
Observation对Binding的Bootstrap验证使用readAttemptRouteBinding两个互斥Operations Audience:Intake以operations_observation_intake绑定Canonical Submission摘要,Projector以operations_observation_projection绑定Accepted Envelope摘要;Scope还必须覆盖Observation、Tenant/Run(若有)、Attempt/Deployment、Binding四元组与可选Operation。二者只返回低敏Binding正文,禁止摘要类型互换、跨Observation复用、读取Availability/Evidence/Receipt或Route私有正文。
Operational Observation 是 best-effort operational evidence,不是 Domain Fact:
- Edge 发送失败不能阻塞、改变或重复客户业务请求;
- 首期 Edge 不新增本地业务数据库或耐久 Spool;
- Operations UI 必须展示 Producer、摄取时间、accepted health window 与已知发送/Intake 失败,不能把“没有 Observation”解释为“没有发生请求”;
- 经济事实、Run 状态和授权结果仍以对应领域事实为准;Gateway 类 Observation 对 Usage/Cost 只允许报告严格 Evidence Availability,并在
available分支引用对应 Evidence Ref/Schema Version/Digest Algorithm Version/Digest完整四元组,不能生成规范MeterEvent/ProviderCostFact、驱动结算或推动业务状态迁移。
Gateway 的跨服务读取是定向、版本化 Contract。Execution的readAttemptExecutionManifest使用严格三Purpose:Gateway执行Bootstrap要求期望Run/Step/Model/Gateway Deployment/Pool;metering_execution_eligibility绑定已接受Execution Eligibility Event完整摘要及其Run/Step/Attempt;metering_gateway_availability绑定已接受Gateway Availability Event完整摘要、Attempt和Manifest四元组,允许Provider Cost在Eligibility Event尚未到达时从Manifest正文bootstrap Run/Step/Deployment。不存在metering_fact_construction宽泛Purpose,两个Metering Token不能互换或枚举同Run其他Attempt。readAttemptRouteBinding 的Bootstrap请求只要求Binding完整四元组、期望Execution Attempt/Deployment与精确Audience;响应返回严格不可变的Attempt/Manifest四元组、Dispatch Slot、Route四元组与Execution Anchor,调用方再以Contracts和自身期望逐项验证,禁止反过来要求调用方预先知道只存在于Binding正文的Slot/Manifest/Anchor。Metering第一次读取Gateway Owner的readAvailability | readEvidence | readRouteCostBinding必须统一使用metering_source_event_bootstrap:请求与JWT绑定已由Inbox接受的Gateway Current-changed Event ID/Type/Schema/Envelope摘要、Aggregate ID/Revision及Event中的Attempt/Deployment/Binding/Route/Kind/Dimension/Availability五元组和可选Evidence四元组;Gateway从不可变Outbox Event重算并比较。该Source Purpose不携带尚未读取正文、因而尚无法构造的Eligibility Basis、Provider Cost Candidate或Validation Operation,返回正文也不能充当Current Receipt。调用方先验证Binding/Anchor,再按Event明确引用的Current attempt-bound Availability、Active Head、Evidence或Route Cost Binding逐项读取,禁止直连Gateway库、换用同Attempt其他Event或读取“当前最新”。validateAvailabilityCurrent 的Receipt使用严格Purpose联合:Metering的 eligibility_consumption 分支绑定确定性eligibilityValidationOperationId、先计算且不含Operation/Receipt的Eligibility Basis Digest、Usage Snapshot和其Current Active Usage Evidence四元组(Terminal-none为state=none)。Gateway分支的Operation由Contracts固定Namespace按purpose=eligibility_consumption + Eligibility Basis带算法摘要 + eligibilityValidationScope派生;Scope恰好包含Gateway能由请求与冻结记录共同验证的executionAttemptId + gatewayDeploymentId + AttemptRouteBinding完整四元组 + GatewayRouteSnapshot完整四元组 + providerEvidenceKind=provider_usage + evidenceDimensionKey + Usage Availability完整五元组,Run/Step/Charge Dimension/Billing Policy只由Basis摘要承诺,不塞入Gateway Token。Not-dispatched分支另按purpose=no_gateway_execution + Basis带算法摘要 + AttemptNotDispatchedFact完整四元组 + Slot/Request/Deployment派生且无Gateway Receipt。Metering的 provider_cost_fact 分支绑定确定性providerCostValidationOperationId、先计算且不含Operation/Receipt的ProviderCostFact Candidate Digest、共享providerCostValidationScope、Cost Snapshot和它的Current Active Evidence四元组;Billing的 billing_finalization 分支绑定Billing Finalization Operation、先内容寻址且不含Receipt的FinalizationInputManifest四元组、Usage Snapshot及Manifest冻结的Evidence(Terminal-none为none)。三类Receipt使用不同Schema分支、Idempotency Namespace、Audience与保留标签,不能互换;expectedEvidence/validatedEvidence必须与Snapshot状态同构:Available恰好携带完整四元组,其他状态恰好为state=none。Eligibility链固定为Basis Candidate → Basis Digest → eligibilityValidationOperationId → Receipt/no-receipt marker → Input Digest → Key/Decision;Decision持久化该Operation,Basis排除它,Input Digest包含它与Receipt,Gateway Receipt的Operation必须严格等于它。相同Basis/Scope在Receipt后崩溃或响应丢失时必须派生同一Operation并幂等取回首次Receipt;任一Basis或Scope成员变化派生新Operation。Billing将去重后的Gateway Receipt放进FinalizationValidationBundle.gatewayAvailabilityReceipts[],不存在Manifest/Receipt自引用。缺失、错误派生Operation、Purpose/Audience/Scope错配、越权、篡改、边界前陈旧或部分验证均fail-closed,Receipt边界后的新Head走post-boundary correction。
Provider Cost 采用双回执的 Candidate → Validation Operation → Receipts → Fact 无环链。Metering 先用 readEvidence 取得 Cost Evidence 严格正文/摘要,再以完整业务字段、Cost Availability Snapshot 四元组/State Version、Provider Cost Evidence 四元组和 GatewayRouteCostBinding@1 四元组计算不含任何Receipt、Fact ID/Time及Validation Operation的 ProviderCostFactCandidate@1 摘要;随后构造两个验证端共同可验证的providerCostValidationScope = executionAttemptId + gatewayDeploymentId + AttemptRouteBinding完整四元组 + GatewayRouteSnapshot完整四元组 + providerEvidenceKind=provider_cost + sourceEvidenceDimensionKey,由Contracts固定Namespace按purpose=provider_cost_fact + ProviderCostFact Candidate带算法摘要 + providerCostValidationScope确定性派生providerCostValidationOperationId。输出Cost Dimension、Mapping与Normalization由Candidate/各自Receipt承诺,不伪装成两个端点都能验证的公共Scope。Metering用同一Scope、Operation和Candidate Digest分别调用 validateAvailabilityCurrent(purpose=provider_cost_fact) 与 validateRouteCostBinding:前者锁定 Current Cost Snapshot/Active Evidence 边界,后者验证 Route Cost Binding、Route Snapshot 与冻结的 providerCostMappingRevisionId + providerCostNormalizationPolicyVersion;两份请求、短期JWT和Receipt都必须逐项携带它们。最终封闭 ProviderCostFact@1 在单一 Metering Repository 事务中原子保存Candidate Digest、providerCostValidationOperationId、共享Scope、Availability/Evidence四元组、Cost Availability Validation Receipt、Route Cost Binding四元组与Route Validation Receipt;两份Receipt的Operation/Scope/Candidate都必须回显并等于确定派生值,Candidate与Fact逐项相等。Receipt后崩溃或响应丢失必须重建同一Operation并取回首次Receipt;不得只保存 Route Receipt、在写 Fact 后补 Receipt,或以读取成功推断 Evidence 仍是 Current。E1 read → E2/A2 推进 → E1 validate 必须失败,E1 validate → E2/A2 只形成后续更正。Metering 与 Billing 之间另由 Metering SettlementInputSnapshot@1 将 Current Eligibility、MeterEvent、Execution Eligibility Fact、Usage Basis、Quantity、Pricing/Policy 与经济身份组合为严格一致快照,并通过 validateSettlementInputCurrent 的 billing_finalization | billing_catch_up_fence | post_finalization_transition | late_settlement_command Purpose Receipt 建立线性化边界;Catch-up分支绑定billingCatchUpValidationOperationId + BillingCatchUp Basis摘要,其Receipt只能推进Fence,不能替代Transition Receipt或驱动Ledger。FinalizationValidationBundle@1 必须同时包含恰好一份 Execution 关闭回执、按 Charge Dimension 全覆盖的 meteringSettlementInputReceipts[],以及按唯一 Attempt-bound Availability Snapshot 去重的 gatewayAvailabilityReceipts[];Not-dispatched 维度禁止夹带 Gateway Receipt。三类 Bundle Entry 都必须完整回显并严格校验各 Owner Receipt 的 Purpose、Operation、FinalizationInputManifest 四元组和对应 validated* 身份,不得压缩为裸 Receipt Ref/Schema 或仅依赖数量相等。三组均完整且与 Input Manifest 精确等值才能结算。完整字段以钱包、计量与商业系统和调度与网关架构的 strict schema 为唯一规范。Gateway Evidence Source Registry 的每次 insert-or-compare 必须与 Evidence append/Active Head CAS/Availability Pointer CAS 同一事务,避免登记、Evidence、Head 或 Availability 出现半完成状态。
Provider Cost Lane对Gateway Cost Availability的全部状态都有封闭处理结果,不能只定义Available成功路径。成功或稳定Owner状态的Work Generation追加内容寻址ProviderCostAvailabilityProcessingFact@1并与Work Attempt Finish、Lane Trigger CAS同一Metering事务提交:available → available_applied要求ProviderCostFact完整四元组和Availability/Route两份Receipt;pending → waiting_for_evidence;not_reported|unavailable → no_cost_evidence且禁止ProviderCostFact、不得解释为零成本;只有Gateway Owner的Availability正文为conflicting时才允许outcome={state=reconciliation_required; availabilityState=conflicting; stateReasonCode}。该终态以Processing Fact四元组作为Metering-owned锚点,且只有Gateway后继Current-changed Event可用新Generation恢复。
available路径中确定性Schema/Identity/Mapping/Normalization/Receipt/Repository冲突必须在同一Metering事务写内容寻址ProviderCostProcessingConflictEvidence@1 + MeteringInputWorkAttemptBlocked@1,把Lane推进为blocked_on_processing_conflict,保留相同activeWork.state=pending、Work/Operation/Generation并释放Lease;禁止写Processing Fact、ProviderCostFact、Work Finish、终态CAS或Case。Eligibility的Execution/Gateway Owner冲突同理分别绑定ExecutionEligibilityConflictFact@1 | GatewayEvidenceConflictFact@1,令Lane=blocked_on_owner_conflict。Owner修复后同Work重新Lease;Owner暂不可读、超时、锁冲突或其他可重试错误连领域Conflict/Blocked也不写。Settlement Input/Catch-up与Execution Finalization的Owner Read冲突只返回SettlementInputConflictFact@1 | RunExecutionFinalizationConflictFact@1完整四元组,禁止Read端生成Case;Catch-up写BillingSettlementCatchUpWorkAttemptBlocked@1并保留同Work,只有正式Billing Finalization Worker可把Run Conflict纳入Decision并按Run级Identity协议创建Operations Case。当前十三Pair Delivery Set不含这些Processing/Conflict Event,因此首期只承诺Owner定向诊断与明确标注为不完整的best-effort告警,不创建第三种Reconciliation Case,也不宣称Operations拥有完整队列;可靠Admin队列必须先增加Canonical Event、core_operations Mandatory Delivery、Owner Read与严格投影契约。Contracts/Repository/Receipt测试必须拒绝非Gateway-conflicting终态、Conflict/Blocked四元组缺项或错摘要、Blocked夹带Processing/Finish/Case、暂态错误带领域Fact、同Ref异摘要、跨Deployment/Source Dimension/Manifest/Binding/Route重绑和旧Generation晚到,并覆盖Blocked后同Work重试、全部Availability状态、响应丢失及A1 Work与A2 Event并发。
Gateway Binding Owner冲突必须使用GatewayBindingConflictFact@1,Availability/Evidence/Route冲突使用GatewayEvidenceConflictFact@1。Eligibility Lane可直接选择Blocked Cause gateway_binding_owner_conflict | gateway_usage_owner_conflict;Provider Cost Lane只允许外层provider_cost_processing_conflict,再由ProviderCostProcessingConflictEvidence@1严格Cause联合引用两类Fact,或在本地确定性分支冻结Stage与冲突Candidate带算法摘要。两类Fact的Purpose、Expected Attempt/Deployment与完整四元组不可混用。负测覆盖Lane/Cause互换、Binding/Evidence类型互换、泛conflictRef、半四元组和跨Purpose重放。
Eligibility Basis、Availability Receipt、Decision、MeterEvent、Settlement Input的已证明本地确定性冲突由EligibilityProcessingConflictEvidence@1记录,Stage严格为eligibility_basis | availability_validation_receipt | eligibility_decision | meter_event | settlement_input;Evidence绑定Run/Step/Attempt、Manifest/Execution Fact、Work/Operation/Generation、Lane Trigger与冲突Candidate摘要,并与MeteringInputWorkAttemptBlocked@1.blockingCause.kind=eligibility_processing_conflict同事务提交,Fence保持blocked_on_owner_conflict + activeWork.state=pending。暂态错误不得写Evidence,Execution/Gateway Owner Conflict Fact不得伪装成本地Processing Evidence。Golden与Repository测试必须覆盖五个Stage、Candidate摘要/Work/Trigger错绑、未知Stage、Evidence后同Work重试及Blocked夹带Finished/CAS/Case。
MeteringInputWorkAttemptFinished@1.outcome增加两Lane通用严格分支{kind=eligibility_and_settlement_input|provider_cost; state=superseded; supersededByWorkGeneration}。A1已经Started而A2 Current Event创建新Generation时,A2 Inbox事务必须按A1 Work/Started/Fencing Token insert-or-compare唯一Superseded Finish;若未代写,A1旧Worker晚到只可在确认Current Generation=A2后追加同一正文,旧Token不得提交业务Fact或CAS。两路径竞争、响应丢失和未完成Started扫描必须收敛同一Finish,测试固定覆盖A1 running → A2 event → old worker。
“Best effort”只描述 Edge 到 Intake 之间可能丢失。Core Operations Intake 首次返回 accepted_new 前,必须已经在同一事务中写入不可变 operational_observations Source 与 ID Registry。两个摘要固定使用 observationDigestAlgorithmVersion=jcs-sha256-v1:UTF-8 严格解码并拒绝重复 Key,Member Name/字符串值转 NFC 且拒绝规范化重名,通过严格 Schema 后按 RFC 8785 生成 Canonical Bytes,再计算小写十六进制 SHA-256;可选字段缺失不注入默认值,Schema 未声明 Nullable 时显式 null 拒绝。只有当前 Workload JWT Subject 等于首次保存的 Source、且算法版本与 observationId + submissionPayloadSha256 相同,才是幂等重放:Payload 尚存时返回 accepted_existing、原 Envelope 与原 acceptedAt;已有 Purge Marker 时返回 accepted_existing_purged,只返回 observationId + originalAcceptedAt + observationDigestAlgorithmVersion,不能复活或重新投影 Source。跨 Workload 的同 ID、算法版本错配或同 Source 不同摘要必须作为完整性冲突拒绝、追加低敏 Audit/告警,且不返回首次 Source 内容。首次 Accepted Envelope 另存不可变 acceptedEnvelopeSha256;它覆盖包含算法版本与 Submission 摘要的完整 Accepted Envelope,但不包含 Registry 中的自身字段。Projector 从该 accepted source 投影,而不是由 Intake 直接改写当前版本的 Read Model。
Observation Projector 对 (consumerName, projectionName, projectionVersion, observationId) 使用保存 observationDigestAlgorithmVersion + acceptedEnvelopeSha256 的独立 Receipt,并把 Receipt 与 Projection Mutation 放在同一事务;其他 Consumer/Projection 的 Receipt 不能满足当前投影,相同 Accepted Envelope 的重投不重复投影。Intake 按受信 Workload Registry 注入 Producer、Environment、Deployment/Instance,acceptedAt 是 Core 权威时间;来源声明的 sourceClaimedObservedAt 只用于 Timeline 证据展示,不驱动排序、Summary、Retention、授权、Freshness 或健康判定。Observation 固定按 (acceptedAt, observationId) 稳定排序。Intake 的 Payload、字段、批次、每 Workload 配额与背压边界全部由 Contracts、部署配置和容量验证给出。
Edge Observation 不能计算或承诺端到端 Coverage 百分比,因为首期没有 Edge 耐久 Spool,也没有一个可以证明“应该收到多少”的权威分母。UI 只显示按 Producer/Deployment 划分的 accepted health window:窗口起止、最后 Accepted 时间、已接受数量、已知发送失败/Intake 拒绝和 healthy | degraded | unknown。窗口健康只说明这段时间的摄取证据,不说明窗口内所有请求均已覆盖;没有 Observation 仍然不能解释为没有请求。
若后续 SLO 要求每次边缘尝试都可审计,需要先设计独立的耐久摄取/Spool 与容量边界,不能把本期 best-effort 语义改写成已保证送达。
7. Operations Read Model
Operations Read Model 是 Core Operations 模块拥有的可删除、可重建投影,不是新的 Run、钱包、资产或审计事实源。首期建议的逻辑数据集包括:
| 数据集 | 用途 |
|---|---|
| Completeness Snapshot / Cutover Candidate / Readiness Proof | 普通完整性同事务冻结 Source+Proof;Cutover/rollback 先冻结 Candidate,Shadow 追平后再生成 Proof。全部保存精确成员或内容寻址 Manifest、Canonical Digest/Count 与版本,不依赖标量水位 |
| Operation/Legacy Run Summary | operation 分支严格要求 Operation/Correlation;缺少 Operation 的 v1 只能成为 legacy_run,不伪造 ID。只保存租户范围、业务摘要、记录级限制及由 Core acceptedAt 聚合的首次/最近时间;projection-level Freshness/Verification/Currency/Completeness 由 Query 在一致性快照中现场附加,不持久化 stale-green 状态 |
| Admission Snapshot Projection | 仅从 run.created@2.0 + wallet.reserved@2.0 重建主体、租户、Developer Access、授权及准入时 Evaluation Set完整四元组/统一Archive Cut/Count/规范排序成员四元组与Revision、模型、Manifest Ref/Version、Output Contract四元组、Billing/Reservation/Pricing/Billing Policy、严格 BillingValue 联合与状态;Decimal原文保存;两个v1只形成Legacy/Partial且不补造字段 |
| Billing Economic Boundary Projection | 严格投影五类经济Event的Envelope摘要、Owner Fact/Result四元组、Reservation/Run/Dimension、Case/Exposure、State Revision、Causation与Settlement Economic Identity;不从Wallet/Ledger当前状态补值 |
| Case Identity Reservation / Reconciliation Case / Pending Resolution / Pending Successor | Operations唯一拥有的稳定Case身份、单调Exposure Source、resolve-before-create与后继先到耐久状态;Case、Pending、Outcome与Event Receipt原子提交 |
| Record Field Evidence | 为 Summary 与 Admission Snapshot 的每个对外字段保存 evidenceKind、Source Ref 与 Source Schema Version |
| Exact Identifier Index | 连接 Request、Operation、Correlation、Run、Reservation、Event,以及 Accepted Source 存在时的 Observation 和 Error ID |
| Operation Timeline | 保存事件/Observation 引用、来源类别、发生/摄取时间与因果边 |
| Aggregate Progress | 保存每个 Aggregate 已应用 Revision、Gap 与 Quarantine 状态 |
| Observation Accepted Health | 保存每个 Producer/Deployment 的 accepted health window、已接受范围与已知发送/Intake 失败;不声称端到端 Coverage 百分比 |
所有 Operations Query 与 Admin 页面统一使用严格五值 evidenceKind,不得另造同义枚举:
domain_fact:由已校验 Domain Event 派生的领域权威事实;operational_observation:Core Intake 已持久化的低敏 Observation,只证明生产者观察结果;operations_runtime:Delivery State/Attempt、Applied Receipt、Projection Checkpoint(仅版本/快照状态,不含标量位置)、Source Snapshot、Revision Gap、Quarantine 与切换证据;telemetry_reference:指向授权 Telemetry Query 的可采样诊断引用,不复制日志正文;derived_summary:由上述证据确定性聚合出的计数、分组、最新可信边界和影响摘要,必须链接其输入证据且不能反向成为事实源。
Error Occurrence 时间字段统一为 occurredAt? / sourceClaimedObservedAt? / acceptedAt? / derivedAt? 并按 evidenceKind 条件校验:domain_fact 与 operations_runtime 使用对应 Owner 生成的 occurredAt;operational_observation 要求 Core acceptedAt、允许可选 claimed time 且禁止伪造 occurredAt;telemetry_reference 与 derived_summary 的 derivedAt 只表示引用或摘要生成时间。四者不能互相回填。
首期精确检索枚举统一为 requestId | operationId | correlationId | runId | reservationId | eventId | observationId | errorId。其中 observationId、errorId 以及准入前失败/幂等重放的 requestId 只在对应 Accepted Observation 或 Error Source 确实存在时命中;有命中时,Search Item 返回 operation_linked | observation_only | legacy_run_only 严格判别联合。携带合法 Operation 的 v1 可以进入第一分支但带 partial_legacy_event;缺少 Operation 的 v1 只能按真实 Run/Event ID 进入 legacy_run_only,不伪造 Operation/Correlation/Request ID,也不能进入 /explorer。Wallet-first Reservation 在与 Run Event 原子校验并链接前只进入受限 Pending/Quarantine 视图,不成为 Operation。没有 Operation/Tenant 的准入前记录只能进入 observation_only。UI 必须明确说明可获得范围。Prompt、模型名、时间相近或相同错误文案都不能用于猜测关联。
Search Request 还固定 identifierNamespace=domain_fact | accepted_observation | auto,Contracts 限制 Type/Namespace 合法组合;Response 回显 negativeProofCapability=complete_source_guard | positive_match_only。首期只有 Canonical Event/Archive Source 上的 operationId | correlationId | runId | reservationId | eventId 的 domain_fact 查询可在完整 Guard 下获得前者;requestId、errorId、全部 auto,以及尚未把永久 Registry 纳入 Completeness Proof 的 observationId/accepted_observation 均为 positive_match_only。
Search Response 必须是严格 searchOutcome=matched | not_found | inconclusive 联合:matched 要求非空 Item 列表;not_found 要求空列表与 negativeProofCapability=complete_source_guard,并且只在 verified + verified_current + complete_for_supported_scope、当前 Source Query Definition 支持该 Type/Namespace 且 Grant 足以在目标范围作否定证明时合法;其余空结果必须是 inconclusive,原因固定为 projection_unverified | projection_incomplete | unsupported_identifier_scope | negative_proof_unavailable | authorization_non_disclosure。潜在 Grant 外资源始终使用最后一种原因,且对象存在与否不能改变 HTTP 状态、Envelope、原因、数量或审计结果。Contracts 必须拒绝 not_found + positive_match_only、非法 Outcome/Verification/Type-Namespace 组合,并覆盖不可枚举负向测试。未来提升某个 Namespace 的否定能力必须升级 Source Query Definition/Contracts,并把其不可变 Registry 纳入 Snapshot Member 与 Guard。
operation_linked 命中的授权 Admission Query DTO 必须使用严格联合:available 分支完整返回Evaluation Set四元组/Archive Cut/Count/成员、Output Contract四元组与 reservationValue = credits{amount} | entitlement{entitlementKey,quantity,unit} | money{money{amount,currency}};任一Set、成员、Output或BillingValue都不能裁成部分。partial_legacy_event | pending_wallet_event | field_redacted 分支明确返回不可用原因且不携带上述字段。未知算法、摘要格式非法、不同Cut成员拼接、BillingValue分支混合/缺项、Decimal浮点化、经济身份字段丢失或JSON round-trip丢失任一字段均拒绝,不能从当前Manifest、Archive Revision、钱包余额或产品配置补值。
同一命中的经济边界Query使用 available | redacted 严格联合。Available必须按规范顺序返回每个Event的ID/Type/Schema/Envelope Digest、Aggregate/Revision、Operation/Causation、Reservation/Run/Dimension、Settlement Economic Identity,以及Event所选的完整Owner Fact/Result、Case Reservation、Exposure与Applied Fact四元组;Reconciliation Case另外返回Current Exposure完整四元组/Revision与Applied Fact联合。字段权限不足时整组redacted,不能裁成无法调用Owner Read Contract的裸Ref或半四元组。Owner/Case/Exposure摘要、Aggregate、Causation或经济身份任一错配都拒绝/隔离,不能用当前Wallet、Ledger或Case正文修补。
Query 返回任何 Accepted Observation 关联时必须镜像上述严格联合。可见 domain_verified 证据完整返回 Event ID/Type/Schema/Envelope Digest 或 Admission Context Ref/Schema/Digest 四元组;证据字段不在 Workforce Grant 内时只能把整个证据返回为显式 state=redacted,不能裁成裸 Ref、Ref/Schema 或无算法标签的摘要。producer_claimed 禁止携带可用/裁剪证据,domain_verified 禁止 state=none。Contracts/Repository/Query round-trip 必须拒绝两个证据 Kind 同时或均未出现、Kind/字段错配、四元组缺项、未知算法、摘要篡改、同 Ref 异正文、Event Type/Schema/Digest 错配、Context 跨 Tenant/Run 重绑和 Producer Claim 被提升为授权索引。
每个查询响应和页面都显示:
projectionName / projectionVersion / generatedAt
evidenceTimes = occurredAt? / sourceClaimedObservedAt? / acceptedAt? / derivedAt? / appliedAt?
freshness =
{ status=verified_current; evaluatedAt / latestAcceptedAt? / lastAppliedAt? / lastVerifiedAt }
| { status=stale; evaluatedAt / latestAcceptedAt? / lastAppliedAt? / lastVerifiedAt }
| { status=unknown; evaluatedAt / latestAcceptedAt? / lastAppliedAt? / lastVerifiedAt=null }
limitations[] = sorted_unique(partial_legacy_event | not_yet_supported | source_delay)
sectionSupportCount
sectionSupport[]=sorted OperationsSectionSupport@1
observationEvidence=OperationsObservationEvidence@1
missingSources=MissingSourceDisclosure@1
unsupportedSchemas=UnsupportedSchemaDisclosure@1
authorizationScope=OperationsAuthorizationScope@1
verifiedCommon = {
completenessAsOf = snapshotId / evaluatedAt / sourceQueryDefinitionVersion
/ lastVerifiedProjectionCompleteness
integrity@2 = {
snapshotProof = {
proofManifestRef / proofManifestSchemaVersion
proofManifestDigestAlgorithmVersion = jcs-sha256-v1
proofManifestDigest
sourceFilterDigestAlgorithmVersion = jcs-sha256-v1
sourceFilterDigest
eventSource = setDigestAlgorithmVersion / setDigest / count / receiptAntiJoinCount
acceptedObservationSource = setDigestAlgorithmVersion / setDigest / count / receiptAntiJoinCount
mandatoryDelivery = definitionSetDigestAlgorithmVersion / definitionSetDigest / definitionCount
/ authorizedDefinitions[]? sorted {
deliverySetVersion / definitionSchemaVersion
definitionDigestAlgorithmVersion / definitionDigest }
/ coordinateSetDigestAlgorithmVersion / coordinateSetDigest / coordinateCount / nonTerminalCount
revisionGaps = setDigestAlgorithmVersion / setDigest / count / authorizedRefs[]?
quarantine = setDigestAlgorithmVersion / setDigest / count / authorizedRefs[]?
unsupportedSchemas = setDigestAlgorithmVersion / setDigest / count / authorizedSchemas[]?
semanticCheck = { version; inputDigestAlgorithmVersion; inputDigest;
status=passed|failed|unknown; authorizedFailureCodes[]? }
}
currentGuard = {
comparedSnapshotId / activeProjectionVersion / sourceQueryDefinitionVersion
sourceFilterDigestAlgorithmVersion = jcs-sha256-v1
sourceFilterDigest
comparisonToSnapshot = equal | changed
guardDigestAlgorithmVersion = jcs-sha256-v1
guardInputDigest
eventSource = setDigestAlgorithmVersion / setDigest / count / receiptAntiJoinCount
acceptedObservationSource = setDigestAlgorithmVersion / setDigest / count / receiptAntiJoinCount
mandatoryDelivery = definitionSetDigestAlgorithmVersion / definitionSetDigest / definitionCount
/ authorizedDefinitions[]? sorted {
deliverySetVersion / definitionSchemaVersion
definitionDigestAlgorithmVersion / definitionDigest }
/ coordinateSetDigestAlgorithmVersion / coordinateSetDigest / coordinateCount / nonTerminalCount
revisionGaps = setDigestAlgorithmVersion / setDigest / count / authorizedRefs[]?
quarantine = setDigestAlgorithmVersion / setDigest / count / authorizedRefs[]?
unsupportedSchemas = setDigestAlgorithmVersion / setDigest / count / authorizedSchemas[]?
semanticCheck = { version; inputDigestAlgorithmVersion; inputDigest;
status=passed|failed|unknown; authorizedFailureCodes[]? }
evaluatedAt
}
}
}
verification =
verificationState=verified
+ snapshotCurrency=verified_current
+ projectionCompleteness=complete_for_supported_scope|partial_source_gap|unknown
+ verifiedCommon
| verificationState=verified
+ snapshotCurrency=stale
+ projectionCompleteness=partial_source_gap|unknown
+ verifiedCommon
| verificationState=unverified {
projectionCompleteness = unknown
completenessAsOf = null
snapshotCurrency = unknown
integrity = null
verificationReason = no_completed_snapshot | definition_mismatch | guard_unavailable
}snapshotProof 只描述 completenessAsOf.snapshotId 的历史 Proof Manifest,currentGuard 只描述本次只读 Consistent Snapshot 的现场输入;二者不能复用同一组 Count/Status。Snapshot Proof 必须返回 Proof Manifest 的 Ref/Schema Version/Digest Algorithm Version/Digest;两边都必须返回 sourceFilterDigestAlgorithmVersion + sourceFilterDigest,并与 sourceQueryDefinitionVersion 共同定义可比较的 Source Filter。guardInputDigest 按 Contracts 的 jcs-sha256-v1 覆盖 Current Guard 的 Compared Snapshot、Active/Query/Semantic Version、Source Filter Digest Algorithm Version/Digest、Comparison、两类 Source Set Digest Algorithm Version/Digest/Count/Receipt Anti-join、Mandatory Delivery Definition Set Digest Algorithm Version/Digest/Definition Count、完整排序Definition四元组成员、逐坐标Definition绑定与状态Set Digest/Count/Non-terminal Count、Gap/Quarantine/Unsupported Count 与 Semantic Input Digest Algorithm Version/Digest/Status;不覆盖自身、evaluatedAt 或按 Grant 裁剪的Definition/明细引用。verified_current 必须是 comparisonToSnapshot=equal,且 Current Guard 与 Snapshot Proof 的 Query/Semantic Version,以及 Source Filter、两类 Source Set、Mandatory Delivery Definition Set/成员/坐标状态与 Semantic Check 的Schema/Definition/Digest Algorithm Version/Digest和其余Proof输入全量相等。任一算法未知、缺失、版本不等、Definition成员/Count或坐标绑定不等或摘要错配都不能声称相等。Source Filter Algorithm Version 或 Digest 在任一侧缺失、未知、错配,或相同 Query Definition Version 下摘要漂移,都属于定义不可比,必须返回 verificationState=unverified + verificationReason=definition_mismatch,不能降格为 changed/stale;只有定义可比且其他 Proof 输入变化才可返回 stale。stale 必须是 changed 并禁止返回 complete_for_supported_scope。旧 Snapshot 的结论只保留在 lastVerifiedProjectionCompleteness,不能冒充当前完整性。
Contracts 必须把顶层完整性与 integrity@2.currentGuard 做双向 Refinement:complete_for_supported_scope 当且仅当 Current Guard 中 Event/Accepted Observation 两个 receiptAntiJoinCount、Mandatory Delivery nonTerminalCount、Revision Gap、Quarantine、Unsupported Schema Count 全为已知 0,且 Semantic Check 为 passed。任一确定非零或 failed 必须是 partial_source_gap;没有确定负向但任一必要值为 unknown/unavailable 必须是 unknown。stale 不能 Complete,unverified 只能 Unknown,Search not_found 只能依赖通过该 Refinement 的 Complete。freshness.status 必须逐字等于 verification.snapshotCurrency;Verified 的 lastVerifiedAt 必须非空且等于 currentGuard.evaluatedAt,Unverified固定status=unknown + lastVerifiedAt=null。Contracts/Repository 必须逐项拒绝 Complete 与 Current Guard 非零/failed/unknown 并存、Current Guard 全零 passed 却声称 Partial、Comparison 标记或 Guard Digest 错配、Freshness/Currency错配、Verified缺少或错绑验证时间、Unverified携带历史验证时间;还必须拒绝 Proof Manifest 四元组或 Snapshot/Guard 任一 Source Filter、Source Set、Mandatory Delivery、Semantic Input 的 Digest Algorithm Version/Digest 缺失、未知或错配,相同 Query Definition Version 下 Filter Digest 漂移却返回 verified_current/stale,以及 guardInputDigest 未覆盖全部 tagged digest;定义漂移只能得到 unverified + definition_mismatch。同时接受 Snapshot 与 Current Guard 红绿状态不同的合法 Stale DTO。
projectionCompleteness 只判断当前已支持投影范围的 Source 是否完整;limitations[] 与 sectionSupport 独立表达记录版本和产品阶段限制,两者可以同时存在。因而 v1 记录可以同时是 projectionCompleteness=partial_source_gap 且包含 partial_legacy_event,而已追平的 v2 准入可以是 complete_for_supported_scope 且 Gateway/Asset/Metering 仍为 not_yet_supported。响应不得用单值优先级吞掉另一项事实。
verificationState=verified 只表示存在已完成、定义可比的 Snapshot 且 Query Guard 已成功执行,不保证结论为 Complete。这里的定义可比要求 Query Definition Version、Source Filter Digest Algorithm Version/Digest 与 Semantic Definition 全部精确匹配。该分支要求 lastVerifiedAt/completenessAsOf/integrity@2.snapshotProof/currentGuard 非空,并把lastVerifiedAt固定为本次currentGuard.evaluatedAt;空库、尚无完成 Snapshot、定义不可比或 Guard 无法执行时必须使用 unverified 分支及freshness.status=unknown + lastVerifiedAt=null,不得填零 Count、空 Digest 或虚构验证时间。Snapshot Proof 与 Current Guard 都必须分别表达 Source Filter tagged digest、两类 Source Set Digest Algorithm Version/Digest/Count 与 Receipt Anti-join、Mandatory Delivery Definition Set Digest Algorithm Version/Digest/Definition Count、完整排序四元组成员及逐坐标Definition绑定/状态集合,并携带 Gap、Quarantine、Unsupported Schema 和 Semantic Check Input Digest Algorithm Version/Digest/Result;Snapshot 另外必须带 Proof Manifest 四元组,不能压成一个总 Missing Count、单一当前Definition或裸摘要。具体Definition成员/ID/Schema/失败参数按 Workforce Grant 的 Tenant、Resource 和 Field Classification 裁剪,但 tagged digest、汇总 Count/Status 与比较字段必须保留且不能泄露越权标识。完整、裁剪、跨Definition Snapshot与无 Snapshot DTO 都要通过 Schema round-trip 和权限负向测试,且 round-trip 后任一算法版本、摘要或Definition坐标绑定不得丢失或被默认值替代。
持久化 Snapshot 只证明 completenessAsOf 所引用的完整 Proof Manifest 四元组,由 Projector Workload 内的 Core Operations Verifier 模块产生,Query Role 不能写 Snapshot。Projector DB Role 只可读取批准的 Source/Runtime View,写目标 Projection/Receipt/Build/Gap、追加 Snapshot/Candidate/Proof,并通过校验 Lock、Fencing Token、Candidate 与 Readiness Proof 的受控 Primitive 切换 Pointer;不能直接更新 Pointer。Manifest 冻结 sourceQueryDefinitionVersion + sourceFilterDigestAlgorithmVersion + sourceFilterDigest、两类 tagged Source Set/Member、tagged Mandatory Delivery Definition Set与完整四元组成员、逐坐标Definition/Receipt/状态精确集合、Gap、Quarantine、Unsupported Schema以及tagged Semantic Check Input/Result。Query必须在同一个只读 Consistent Snapshot中用相同Query/Semantic Definition和全部Schema/Digest Algorithm Version对全部Proof输入与当前状态做精确比较,并确认Active Projection Version未变;不得从当前Registry回填历史Definition。Source Filter三元组及全套输入精确相等才返回verified_current;定义可比而其他Proof输入变化时返回stale,其中确定Gap对应partial_source_gap,无确定Gap对应unknown。Source Filter Algorithm Version/Digest缺失、未知或不相等必须返回unverified + definition_mismatch,不能返回stale。没有已完成Snapshot、定义/版本不可比或Guard无法执行/比较时返回verificationState=unverified + snapshotCurrency=unknown + projectionCompleteness=unknown。历史结论只保留在completenessAsOf.lastVerifiedProjectionCompleteness;时间差、Count、最大ID或TTL不能替代该Guard。
sourceClaimedObservedAt 只用于 Timeline 证据展示,不参与排序、Summary、Freshness、Retention、授权或健康窗口计算;Observation 的稳定顺序、first_accepted_observation_at/latest_accepted_observation_at、新鲜度、保留资格与健康窗口只能使用 Core 权威的 acceptedAt。
not_found 只表示当前授权且被完整性证明覆盖的已支持 Source 范围内没有结果,不表示源领域从未发生该事实;无法形成这项否定证明时必须显示 inconclusive,不能把空数组渲染成“未找到”。
历史CompletenessProofManifest@1必须冻结并让自身摘要覆盖两类Source Set Digest/Count/Receipt Anti-join、Mandatory Delivery Definition Set Digest/Definition Count/完整排序四元组成员、带terminal|non_terminal状态分类且逐项绑定精确Definition四元组的Delivery坐标集合及其Set Digest/Count/Non-terminal Count、Gap/Quarantine/Unsupported的Set Digest/Count、Semantic Input/Result和顶层结果。Snapshot列只是Manifest同值索引;旧Snapshot生成后Current Delivery、Current Definition Registry或检查状态变化不得回填、重算或改写历史Proof。Current Guard比较Definition Set、成员、坐标Definition绑定/状态集合及所有tagged Set Digest而不只比较Count。Repository/Query测试必须覆盖同一Snapshot跨Definition切换、篡改或删除任一历史Definition成员/坐标绑定/Count/Result/Set Digest并验证Proof失败,也必须验证Current Delivery终结后旧Proof不变而Guard只返回stale。
版本化重建与切换
投影逻辑升级使用新 projectionVersion Shadow Rebuild:
- 从保留的不可变 Event 与 Observation 重建新版本;
- 保持独立 applied receipt、Aggregate Progress 和 Source Snapshot 验证记录;
- 比较确定性摘要、数量、Gap、隔离项和授权查询结果;
- 先暂停 Shadow Claim 并排空 Shadow in-flight Mutation;再在一个短 Consistent Snapshot 事务中冻结不可变
CutoverCandidateBoundary(Event/Accepted Observation 精确 Source Member与两类 Set Digest Algorithm Version/Digest、sourceQueryDefinitionVersion + sourceFilterDigestAlgorithmVersion + sourceFilterDigest、Semantic Definition Version与 Expected Receipt Coordinates Schema/Digest Algorithm Version/Digest),保存 Candidate 的 ID/Schema Version/Digest Algorithm Version/Digest,并原子把 Shadow Build 绑定到该四元组、递增 Claim Fencing Token、切换为candidate_bound;旧 Token 和边界外 Mutation 必须被数据库拒绝。此时不冻结 Readiness Proof,随后只按 Candidate Catch-up; - 获取 Projector/Cutover Lock,停止新的 Event 与 Accepted Observation Claim,并排空旧 Active Version 的 in-flight Event Lease/Ack 与 Observation Projection 事务;
- 在最终事务中逐项复核同一 Candidate Boundary 的 ID/Schema Version/Digest Algorithm Version/Digest,并基于该边界计算双 Receipt anti-join、Revision Gap、Quarantine、Schema 与 tagged Semantic Check Input/结果;全部通过时才追加带自身 ID/Schema Version/Digest Algorithm Version/Digest 的不可变
CutoverReadinessProof,引用完整 Candidate 四元组,并在同一事务原子更新 Active Pointer 和后续消费目标版本;不得重新读取“此时全部可见 Source”扩展边界,也不得用时间范围、计数或标量位置替代成员集; - 恢复 Claim;候选边界之后提交的 Event/Accepted Observation 保持 Pending,并只进入新的 Active Version。它们不阻塞 Pointer Switch,但会让切换后的 Query 暂时显示
partial_source_gap,直到新 Active 应用/确认完成。领域 Writer 与 Observation Intake 不需要停机;实现若不能按 Frozen Member 精确过滤,则必须改为在有界窗口短暂停 Writer/Intake,不能保留竞态; - 保留旧版本至回滚窗口结束;若需回滚,先把旧版本作为 Shadow 追平,再暂停/排空该 Shadow,以短事务原子冻结 Candidate 并绑定新 Claim Fencing Token,按边界 Catch-up,最后执行同样的停 Active Claim/排空、同一边界 Final Verification 和 Pointer+Consumer Target 原子切换。不能直接翻转 Pointer;旧版本无法追平时关闭或保持降级 Query,不得服务陈旧投影。回滚窗口结束后再按治理策略删除派生投影。
重建不能调用 Domain Query 水合历史,也不能重发客户副作用。已发布 Outbox Event 始终属于 Canonical Source;它离开热表前必须已原样进入批准、不可变且 Rebuild/Verifier 可读的 Archive Source View,并在逻辑 Source Membership 中继续存在。这里只允许按治理策略删除已完成归档与可恢复性验证的热表副本,不能用 Projection、Completeness Snapshot、Read Model 或“已有重建基线”替代 Canonical Event。具体保留期来自数据分类、恢复目标与管理员配置,不在 ADR 中写固定天数。
8. Private Query 与 Workforce 授权
Admin BFF 调用 Core Operations Query 时需要同时满足:
- 面向
oceanway-core、具有 Operations Query Scope 的短期 Workload JWT; - 仅由
oceanway-coreAuthorization/Policy 模块签发的 Signed Workforce Grant,绑定真实员工 Principal、呈递 Workload、职责、Tenant/Resource/Field/Action Scope、认证与策略版本、MFA Assurance、jti、有效期和可选 Incident/Reconciliation Case;Admin、IdP、BFF 和其他服务都不能自行签发; - Core 对每个查询重新校验 Workload、Grant 的
iss/aud/sub、iat/nbf/exp、撤销状态、Scope 与查询资源范围,并强制boundWorkload(或azp/cnf)等于当前 Workload JWT Subject。
Workforce Principal、角色、Organization Scope 或 Case ID 不能由 Admin 请求 Body 自我声明。Host-only Cookie/Session 只在 Admin BFF 终止并验证,绝不转发到 Core;BFF 使用自身 Workload JWT 加 Workforce IdP/Identity Authorization 唯一签发或交换的 Actor Assertion 请求最小 Grant。该 Assertion 的 aud 必须精确指向 Grant Exchange,至少携带 sub、auth_time、acr/amr、Session/Authn Version、jti、iat/nbf/exp、boundWorkload 与 Exchange Request Digest/Nonce;它不能复用 Admin Audience 的 ID Token,也不能由 BFF 自报或签发。Core 必须在“消费 Assertion JTI、记录 Grant 与追加签发 Audit”的同一事务内完成一次性消费,任一环节失败都不返回 Grant。浏览器不能取得 Assertion 或可重放 Grant。跨租户查询必须具备显式职责与 JIT/Case Scope;无权查询时返回不泄露资源是否存在的统一结果。
首期 Private API 只提供:
POST /internal/v1/workforce/query-grants:issue:由 Admin BFF 交换定向 Signed Workforce Grant;POST /internal/v1/operations/search:按允许的稳定 ID 精确查询;使用 POST,避免检索 ID 默认进入 URL 与 Access Log;POST /internal/v1/operations/explorer:读取一个已授权 Operation 的摘要、时间线、投影元数据与缺失来源;POST /internal/v1/operations/projection-status:读取版本、新鲜度与集合完整性证据;POST /internal/v1/operations/observations:仅供明确授权的生产服务写入严格低敏 Observation。
前三个Operations Query端点必须直接发布并消费Contracts的封闭OperationsSearchRequest/Response@1、OperationsExplorerRequest/Response@1、ProjectionStatusRequest/Response@1,共同引用同一个OperationsQueryMetadata@1;Admin BFF不得重命名、裁剪或重新拼装Wire Shape。严格外层如下,嵌套SearchMatch@1、Admission、Boundary、Case、Timeline、Observation与Runtime Detail逐字段复用Operations Read Model同名Schema:
OperationsSearchRequest@1 =
identifierType/identifierValue/identifierNamespace
+ active|exact projection
+ matchPage(cursor?,requestedPageSize?)
+ sorted unique nestedPages(operationId, billing_boundaries|reconciliation_cases, cursor, requestedPageSize?)
OperationsSearchResponse@1 =
{ metadata=available(OperationsQueryMetadata@1); result=OperationsSearchResult@1 }
| { metadata=unavailable(projection_unavailable);
result=inconclusive + positive_match_only + projection_unavailable + empty data }
OperationsExplorerRequest@1 =
operationId + active|exact projection
+ admission include|omit
+ billingBoundaries/reconciliationCases/timeline/observations requested-page unions
OperationsExplorerResponse@1 =
available + available metadata + strict summary/admission/four paged section unions
| inconclusive(not_found_or_not_authorized) + available metadata
| inconclusive(projection_unavailable) + unavailable metadata
ProjectionStatusRequest@1 =
projectionName + active|exact projection
+ not_requested|requested(detailKind=mandatory_delivery|revision_gap|quarantine|unsupported_schema,cursor?,requestedPageSize?)
ProjectionStatusResponse@1 =
available + available metadata + registry/build
+ runtimeSummary(available current-guard counts | unavailable exact verification reason)
+ authorizedDetails(not_requested | available typed page | redacted)
| inconclusive(not_found_or_not_authorized) + available metadata
| inconclusive(projection_unavailable) + unavailable metadataSearch Match、Explorer四类分页和Status四类Detail各使用Contracts固定稳定排序;Cursor由Core签名并绑定Endpoint/Section、原请求、Projection/Query Definition、Workforce Principal/Bound Workload、Grant/Tenant/Run/Step Scope、过滤、Page Size和最后排序键,BFF不得解析/续签或跨Scope复用。Count必须等于数组长度,Next Cursor仅在同Scope有下一页时存在。Admission授权组完整保留Scope四元组、Action Count/排序成员与Evaluation Set;Boundary/Case完整保留Billing Account/Reservation/Run/Step/Attempt/Dimension及Owner四元组;Mandatory Delivery Detail完整保留Delivery Set Definition四元组;Timeline/Observation严格保留证据联合和Redaction。runtimeSummary.available只可逐项复制同响应Current Guard六个Count;Unverified只能返回Unavailable且Reason等值。Golden、未知字段、联合混合、Count/摘要错配、越权、Cursor错Endpoint/Section/Scope/Projection/Grant以及无Guard却返回Count都必须拒绝。
服务的 Access Log 不能记录请求 Body。Grant 的签发、拒绝、撤销与使用都进入追加式 Audit,签发 Audit 失败时不得发放 Grant。跨租户、技术证据、Case-scoped 内容、Sensitive Debug 与其他策略标记的敏感查询采用两阶段 Audit fail-closed:先提交 query.authorized,执行范围受限的查询,再在任何响应字节离开 Core 前提交 query.released | query.not_found | query.inconclusive | query.failed,记录字段分类、结果数量、Projection Version、Search Outcome 和 Grant ID;第二次追加失败同样不得返回数据。搜索、详情和 Observation Intake 分别使用最小 Scope,不能因拥有 Query 权限获得 Write 权限。
Operations Query 使用专属只读数据库 Role,只能读取批准的 Projection/Runtime View,并仅能调用 Audit Append Procedure。Observation Intake 使用另一数据库 Role,只能 EXECUTE 受控 accept_operational_observation Primitive,对 Accepted Source/Registry 没有直接 SELECT/INSERT/UPDATE/DELETE;该固定 search_path、撤销 PUBLIC EXECUTE 的 Security Definer Primitive 在唯一约束下原子执行 insert-or-compare,只有 Source Workload 与 Submission 摘要都匹配时返回首次 acceptedAt,跨 Workload 冲突只返回通用完整性错误。Admin Query BFF 与未来 Command Gateway 使用不同 Workload Principal、Audience、凭据和网络权限。敏感响应固定 Cache-Control: private, no-store,不得进入 CDN、Service Worker、浏览器持久化或前端 APM Body/ID/Grant 采集。
首期 Admin 是只读 Explorer:不提供重试 Run、强制成功、改余额、补 Ledger、跳过状态、重放 Gateway、编辑 Outbox、删除错误或修复投影的按钮。本阶段注册但不启用FinalizationReconciliationResolutionCommandRequest@1 → AdminCommandCandidate@1 → FinalizationReconciliationResolutionDecision@1 → SignedWorkforceCommandExecutionGrant@1 → Billing Applied Fact/Event;任何Finalization Producer可能进入reconciliation_required前,必须与ADR-031完整命令链同批启用,不能先产生无出口Case/Fence。完整十三Pair/十四Member Definition是ADR-031 Release目标;当前阶段若相关Producer尚未启用,仅按“未启用Producer不适用”运行精确子集,不能用缺Member的完整Definition冒充就绪。
9. 隐私、自监控与故障表达
Operations Read Model 只保存定位所需的低敏引用和规范化摘要。租户 ID、Request/Run ID、上游任务 ID 和其他高基数值不能成为 Metrics Label;它们只用于权限感知的精确查询。普通日志和 Trace 不记录 Query Body、Prompt、Secret、Digest、签名 URL 或完整 Projection Row。
首期自监控至少覆盖:
- 未投递数量与最旧事件年龄;
- Lease 领取、过期、Fencing 拒绝与 Worker 恢复;
- 可重试失败、Schema 拒绝、Payload Hash 冲突和 Quarantine;
- 各 Consumer/Projection Version 的 Receipt、Source Snapshot anti-join、Revision Gap、Quarantine 与重建进度;
- Observation Intake 成功/失败和按低基数 Producer/Deployment 表达的 accepted health window;
- Query 延迟、授权拒绝与脱敏 Audit 写入失败。
告警阈值和容量限制来自 SLO、容量测试和部署配置。Operations 自身异常必须能够在不依赖同一损坏投影的基础健康端点、数据库指标或 Infrastructure 监控中发现,避免“监控系统只能用自己证明自己健康”。
10. 首期 Explorer 能看到什么
首期 UI 只展示受控准入链已有或本 ADR 明确新增的事实:
- Request/Operation/Correlation 基本信息与授权租户范围;
- Run 创建、当前已知状态和
run.createdEvent 引用; - credits Reservation 与
wallet.reservedEvent 引用; - 已提交Billing Finalization/Transition/Late Settlement边界、Owner Fact/Result四元组,以及Case Identity/Exposure/resolve-before-create收敛状态;
- Outbox 存储、Delivery Attempt、applied receipt 和 Projection 完整性快照;
- 可用时的 Edge RequestAttempt / ErrorOccurrence Observation 与 accepted health window;
- 明确的完整性、Revision Gap、Quarantine 和未支持 Schema 提示。
Gateway、Provider、Output、Asset、Usage、Webhook 与 Incident等尚无对应严格事件的部分可以显示为“尚未接入/未观测”,但不能生成假时间线或从日志猜测状态。Settlement只展示已投影五类经济边界Event及其Owner四元组/Case状态,不能把缺失边界从当前Ledger补造。页面默认不显示客户输入、模型输出、Credential、内部Route和Provider私有配置。
明确排除
本 ADR 不授权或不实现:
api.oceanway.tech公网开放、一般客户流量或生产切流;- Text/Media Gateway 调度、Provider 提交、轮询、回调和结果取回;
- AssetVersion、Run Output、产品 Binding 与客户可见生成结果;
- Metering、Provider Cost、Settlement、Release、Refund 的领域计算或写入;本ADR只授权消费已提交的五类低敏经济边界Event和维护Operations-owned Case投影;
- 客户 Webhook、外部 Event Subscription 或客户可见日志;
- 外部 Broker、跨区域事件总线、CDC 或通用数据平台;
- 本阶段启用Admin写命令、人工修复、重试、强制状态迁移或直接改表;Finalization Resolution严格Schema只作为ADR-031同批启用门禁,不构成本阶段可调用能力;
- 原始 Prompt、输入输出、Secret、Credential Digest 和 Provider Payload 的通用检索。
这些能力需要各自的契约、事实 Owner 和验收证据,不能因 Ops Explorer 页面预留了位置就视为存在。
实施顺序
按以下顺序推进,禁止 Consumer 先依赖未发布的本地类型:
- Contracts:配对新增
run.created@2.0 + wallet.reserved@2.0、五类Billing经济边界Event、四类Owner间必达触发Event(Execution Eligibility、Gateway Availability Current、Run Finalization Closure、Settlement Input Current)、两类Finalization Reconciliation编排Event、各Destination严格Consumer Receipt、两类Case Identity Reservation/Pending Resolution及PendingFinalizationCaseSuccessor@1、Finalization Resolution Request/Decision/Grant specialization、OperationalObservationSubmission@1.0、AcceptedOperationalObservationEnvelope@1.0、RequestAttempt/ErrorOccurrence 与 Private Operations API Schema;发布固定Release并验证生成制品一致性。 - Owner Migration:Core、Execution、Gateway、Metering、Billing各自增加本库独立 Delivery State/Attempt、版本化Consumer Receipt或精确Owner Read及所需Fence/Work;Core另增加Source Snapshot、经济边界投影、Case Identity Reservation/Reconciliation Case/Pending Resolution/Pending Successor、Operations Projection和Active Pointer。在Public Admission仍关闭时,Core用Append Migration Lock完成Bootstrap Delivery Set和受控Append Primitive,其他Owner也必须在启用各自新Event前确认旧Writer退出与激活Definition适用,杜绝“有Event、无Delivery”或只有裸定义摘要的窗口。
- Dispatcher/Consumer:实现各Producer DB Worker的领取、Lease Fencing与at-least-once;Operations Projector实现Applied Receipt、五类经济边界严格投影、Case预留/resolve-before-create/Pending Successor原子消费、Gap/Quarantine、Shadow Rebuild与自监控;Metering/Billing Consumer按严格Lane或Fence/Work Receipt耐久接收触发Event。
- Core Producer:准入事务原子切换为产生
run.created@2.0 + wallet.reserved@2.0,验证事务回滚与幂等重放不增加事件;两个 v1 只由兼容 Consumer 读取历史。 - Core Private API:实现 Operations Observation Intake 与只读 Query,并完成 Workload JWT + Core Signed Workforce Grant 双重授权。
- API Edge:在不阻塞客户请求的边界发送低敏 Observation,验证 Intake 不可用时业务语义不变。
- Admin:实现精确搜索、Operation 时间线,以及版本/新鲜度/完整性/accepted health windows 真值提示,不提供写操作。
- Infrastructure:以独立 Process Role 部署 Core Worker,建立配置、告警、灾备、跨进程测试和不可变证据。
验收门禁
本 ADR 只有在以下证据全部完成后,状态才能更新为“实现基线已验收”:
- Contracts 的严格 Schema、JSON Schema、Node/Web 消费、Package/Release 摘要和向后消费测试通过;
run.created@1.0与wallet.reserved@1.0的原 Schema、Optionality 与语义均保持不可变; - 真实PostgreSQL验证业务事实与Outbox原子提交,Delivery State不修改Event Payload;首个完整Release精确注册十三个Event/Schema Pair与十四个Mandatory Member:Run/Wallet v2、五类Billing边界和Finalization Case Requested各自唯一投递
core_operations → operations_projector,Execution Eligibility与Gateway Availability分别唯一投递metering_input_processor → metering_input_processor_consumer,Run Finalization Closure唯一投递billing_finalization_coordinator → billing_finalization_coordinator_consumer,Settlement Input Current唯一投递billing_settlement_catch_up → billing_settlement_catch_up_consumer,Finalization Resolution Applied同时投递billing_finalization_coordinator → billing_finalization_coordinator_consumer与core_operations → operations_projector;run.created@2不得增加Billing Destination。拒绝别名/通配/latest。每个Producer在自己的领域事务冻结完整Delivery Set Definition四元组并精确创建适用Mandatory集合,缺失/多余Member、同事务缺任一Owner Fact/Event/Row、未知Schema/算法、同Version异Candidate/Digest、裸Digest或Bootstrap Event缺四元组均使该业务事务失败;Contracts/Registry/Archive round-trip使用同一Golden且不从当前Registry补值; - 用替代实例与旧实例短暂重叠的恢复测试证明不会重复持有有效 Lease,旧 Lease Token 无法覆盖新 Owner;生产部署仍保持单一 managed Worker;
- 在领取前、Consumer 提交前、Consumer 提交后但 Dispatcher 确认前、确认后等故障点重启,最终投影正确且业务副作用不重复;
- 重复 Event、同 ID 不同 Hash、乱序、Aggregate Revision Gap、不支持 Schema、可重试错误与 Quarantine 均有确定测试;
- Receipt、Projection Mutation 与 Aggregate Progress 在同一事务提交;任何中途失败不留下“已回执但未投影”状态;
- 从保留 Event/Accepted Observation Shadow Rebuild 相同 Projection Version 得到确定性结果;正向切换后写入新 Source,再回滚旧版本时也必须先追平、排空、Final Snapshot 验证并原子切换,不能直接翻转 Pointer;
run.created@2.0强制非空operationId + correlationId、按Authentication分支冻结Authorization Evidence Evaluation Set完整四元组/统一Archive Cut/Count/精确成员四元组与Revision,以及完整Output Contract四元组;wallet.reserved@2.0强制非空operationId + correlationId + causationId、Pricing/Billing Policy与严格BillingValue。Admission Producer/Contracts/Repository证明Event↔Manifest(含Set/Cut/成员)及reservation.value == reservedValue逐项等值,任一错配同事务回滚;Projector不解引用Manifest,先按Event冻结Set四元组/Cut调用readAuthorizationEvidenceEvaluationSet,再按Set成员与同一Cut读取Evaluation。负测覆盖Set/成员漏项/夹带/重复/乱序、四元组/Revision/Cut裁剪、不同Cut拼接、绕过Set读取、Purpose/Operation/Validity/Evidence错配、当前Revision冒充历史值和后到撤销后Shadow Rebuild漂移。两个v1只形成Legacy/Partial,不被新Requiredness判非法;- 五类Billing经济Event的严格Payload、Owner Fact/Result定向Read、Aggregate=Owner Ref/Revision=1、Operation/Causation、Case Generation/Identity Reservation/Predecessor、Exposure Revision与Settlement Economic Identity均可从保留Event独立重放;Finalization Status/Dimension/Consumption/Ledger/Release/Reservation双向Refinement通过。真实并发测试覆盖第一代root、连续Generation、同代Exposure更新、新代不继承旧Approval/JIT/Grant、Case Requested缺失/篡改Transition Operation或from/to Revision、Case Requested先于Transition Event仍可独立读取Owner、F→T(open)→Late Result→T(correction)乱序与缺失/错误State边界前驱、resolve-before-create直接Resolved、多条Resolution先于Create后取最高连续版本、
C2 Request → C1 Resolution → C1 Request通过Pending Successor与Receipt原子收敛、同代异后继Candidate冲突且在线/Shadow Rebuild结果一致、Open O1→Resolved R2→Resolved R3及回指旧Open分叉、Transition随机/错误前驱派生Resolution Operation、Late Command Resolution Operation不等于Command Operation、Metering Receipt提交后Billing预提交前崩溃并由新Worker重建相同Resolution Operation/Basis、Case Owner Read的not_created/found/conflicting与Create提交后Ack丢失恢复、旧代Create/Ack晚到、Result/Applied乱序、响应丢失、Owner同Ref异Digest、Case/Exposure/经济身份错绑、reserve提交后Billing保存响应或预提交Transition前崩溃并由新Worker按Source/Generation/Predecessor采用同一Reservation,以及Case/Boundary/Pending/Outcome/Receipt事务中途失败无半完成状态; - Observation Intake 不可用不会阻塞 API Edge;未认证、幂等重放和提交不确定 Observation 的缺失租户正确;同 Source 重放按 Source 尚存/Purged 分别返回
accepted_existing | accepted_existing_purged,跨 Workload 同 Observation ID 作为完整性冲突拒绝且不泄露首次 Source;Retention 后 Registry 仍阻止重绑或复活。Producer Claim 不能参与 Tenant 授权或 Operation 关联;Domain-verified Association 必须完整携带并验证domain_event的 Event ID/Type/Schema/Envelope Digest 或admission_context的 Ref/Schema/Digest 四元组,错标 Tenant/Run、证据缺项/篡改/混合/重绑都进入隔离且不能创建授权索引。完整性同时检查 Event Receipt 与已接受 Observation Receipt anti-join,只显示 Accepted Observation 范围、健康窗口与已知投递失败,并声明请求级完整性未知/不可测。时间负测必须证明反序/未来/Retention 边界 claimed time 不改变按acceptedAt计算的排序、Summary、Freshness、保留和健康窗口,非法evidenceKind时间组合被拒绝/隔离; - Admin Query 同时校验 Workload JWT 与 Core 唯一签发的 Signed Workforce Grant;Workforce Actor Assertion 只能由受信 IdP/Identity Authorization 签发或交换,Audience/Workload/Exchange Request 绑定且 JTI 与 Grant/Audit 同事务一次性消费;敏感查询 Audit fail-closed,跨租户和不存在资源不泄露存在性,Search ID 不进入 URL/Access Log;
- Search 八类精确 ID 均按来源真实能力处理;命中项使用三类 Match 判别联合,空结果使用
not_found | inconclusive可判定性联合。只有verified_current + complete_for_supported_scope且支持范围与授权范围可证明时才能not_found;其他空结果及潜在越权均为不可枚举的inconclusive。没有带完整严格证据的 Domain-verified Association 时,Accepted Observation 只能返回observation_only,不能伪造 Operation/Tenant、不能被 Tenant-scoped Grant 命中,也不能进入/explorer;Query 对可见证据完整 round-trip,字段权限不足使用整个state=redacted分支而不是半个证据; - UI 展示 Projection Version、Freshness、Event/Accepted Observation 精确 Snapshot Member/Digest、Receipt Anti-join、Revision Gap、Quarantine、
projectionCompleteness + limitations[] + sectionSupport与 accepted health windows,并统一使用五值evidenceKind; - Metrics 无高基数业务 ID,日志/Trace/普通业务投影不含 Prompt、Input、Secret/Credential Digest、Request Fingerprint、Headers、签名 URL 或原始 Provider Payload;
integrity@2的 Snapshot/Guard Source Digest 使用独立字段分类与 Grant,不进入普通日志; - Infrastructure 的真实跨进程证据覆盖 Core API、Core Worker、API Edge、Admin BFF 与隔离 PostgreSQL,并证明 Public Admission 仍默认关闭;
- 各仓独立 CI、版本、Commit、部署制品摘要、配置来源和回滚步骤可追溯。
回滚与后续演进
- Dispatcher/Projector 异常时停止
core-operations-worker或 Operations Query,Admission 继续提交不可变 Outbox;恢复后从逐事件 Delivery/Receipt 与新 Source Snapshot 重新核验,不能依赖标量位置或删除积压来恢复绿色状态。 - Projector 版本异常时先停止或降级 Query,把旧版本按 Event/Accepted Observation Receipt anti-join 追平并验证,再以完整 Cutover 协议原子切回 Pointer 与 Consumer Target;不能直接切回自上次切换后未消费新 Source 的旧版本。无法追平时保持 Query 关闭并修复 Projector,不回写领域表或修改历史 Event。
- Producer 一旦原子产生
run.created@2.0 + wallet.reserved@2.0,回滚 Core 版本时仍必须保留能够解析两类 v2 的 Consumer;不能先回滚任一 Contracts 支持,也不能产生 Run v2 搭配 Wallet v1 的混合窗口。 - Edge Observation Intake 可以独立关闭或降级,客户请求语义保持不变;页面同步把 Accepted Observation 完整性标为 Unknown,不推算缺口数量。
- 只有外部 Broker 的触发证据成立并通过新 ADR 后,Transport Adapter 才能替换;Canonical Envelope、Event ID、幂等 Receipt 和 Projection Contract 保持稳定。
影响
收益是从第一条已准入请求开始获得可重建、可授权、能说明数据缺口的真实运维时间线,同时保持 Admin、Core 与 Gateway 的事实边界。故障恢复依赖 applied receipt 与 Lease Fencing,而不是依赖“消息只会来一次”的假设。
代价是需要新增版本化事件、投递状态、投影版本和双重查询授权;Operations UI 在早期会主动显示大量“尚未接入/未观测”。这是有意保留的诚实边界。它比提前引入消息平台或让 Admin 跨库拼接更容易验证,也为未来根据实际容量与故障域证据演进到外部 Broker 留出了稳定 Port。