事件与 Observation 契约
准入、结算、对账经济边界事件、Legacy v1、Observation Submission/Accepted Envelope 与数据最小化规则
运维读模型只能消费稳定、低敏、可验证的契约,不能解析应用日志,也不能在投影时回查 Domain Query 来猜测创建时上下文。本章固定首期领域事件与入口观测契约。
证据种类不能混用
evidenceKind | 含义 | 可靠性 | 可作为领域权威事实 |
|---|---|---|---|
domain_fact | 已由领域事务提交的状态变化 | Transactional Outbox + at-least-once | 可以;权威范围仅限事件明确表达的事实 |
operational_observation | Edge 或未来 Gateway 观察到的一次请求、响应或错误 | 首期 best-effort | 不可以;只能表达“该来源曾观测到” |
operations_runtime | Delivery、Attempt、Receipt、Quarantine、Projection Build 等 Core 运维状态 | Core PostgreSQL 受控写入 | 不可以;只对运维管道自身状态负责 |
telemetry_reference | 指向日志或 Trace 的受控引用 | 可采样、可丢失、按策略保留 | 不可以 |
derived_summary | 从已标注证据计算的摘要 | 可重建 | 不可以 |
Observation 不能伪装成 Domain Event;Domain Event 也不能塞入原始 HTTP Body、Prompt、Secret 或 Provider Payload 以满足调试需求。
ErrorOccurrence 时间字段
所有 Error Occurrence Schema 统一暴露以下可选字段,并由 evidenceKind 做条件校验:
occurredAt? / sourceClaimedObservedAt? / acceptedAt? / derivedAt?domain_fact与operations_runtime分别要求其事实 Owner/Core Runtime 生成的occurredAt,其他三项必须缺失;operational_observation要求 Core Intake 生成的acceptedAt,允许 Producer 提供可选sourceClaimedObservedAt,occurredAt/derivedAt必须缺失;telemetry_reference要求表示受控引用生成时间的derivedAt,可保留来源声明时间,occurredAt/acceptedAt必须缺失;derived_summary要求derivedAt,其他三项必须缺失;输入事实时间通过 Field Evidence 保留,不能生成替代时间。
不满足组合约束的记录必须在 Contracts 或 Projector 边界拒绝/隔离,不能在反序列化、数据库默认值或 UI 中互相回填。对于 operational_observation,acceptedAt 是排序、accepted health window、Retention/Archive、Freshness 与 Summary 首次/最近时间的唯一权威时间;sourceClaimedObservedAt 只允许进入 Timeline 展示。
Canonical Event Envelope
已发布的 run.created@1.0 与 wallet.reserved@1.0 保持原 Canonical Envelope,不改写历史 Schema:
eventId / eventType / schemaVersion / occurredAt / producer
actorPrincipalId
organizationId / workspaceId / projectId?
aggregateId / aggregateRevision
correlationId / operationId? / causationId? / traceId?
payload面向个人与企业统一生态的新事件版本使用 Tenant-aware Canonical Envelope:
eventId
eventType
schemaVersion
occurredAt
producer
actorPrincipalId
tenantKind = organization | personal_space
tenantId
workspaceId
projectId?
aggregateId
aggregateRevision
correlationId
operationId?
causationId?
traceId?
payloadtenantKind + tenantId 是严格判别联合:organization + OrganizationId 或 personal_space + PersonalSpaceId,不可同时或缺失。旧 Envelope 的 organizationId 只能确定性映射为 tenantKind=organization + tenantId=organizationId 并保留 Source Lineage;不能把 Personal Space 伪造成 Organization,也不能给既有 @1.0 静默增加字段。run.created@2.0 与 wallet.reserved@2.0 都使用 Tenant-aware Envelope;首个 Producer/Cutover 门禁仍只允许 tenantKind=organization。Personal Space 必须等两个 v2 Event、tenant-aware Manifest、Contracts/Core/API Edge、授权与 Billing 的真实门禁同时通过后才启用。
规则:
eventId在 Producer 首次提交前生成,重投、重建和跨进程传递时保持不变。aggregateRevision是对应 Aggregate 的正整数版本;Consumer 只据此判断单 Aggregate 缺口或乱序。correlationId用于关联业务链,operationId用于稳定业务操作,traceId只代表可采样的遥测链。- Event Envelope 与 Payload 一经提交不可修改;更正通过新领域事实或新事件完成。
- PostgreSQL Trigger 与 Column Grant 必须拒绝对 Envelope、Payload、
eventEnvelopeDigestAlgorithmVersion、Canonical Event SHA-256、deliverySetVersion + deliverySetDefinitionSchemaVersion + deliverySetDefinitionDigestAlgorithmVersion + deliverySetDefinitionDigest和创建时间的 UPDATE;应用 Role 没有绕过权限。Delivery Set 四元组是不纳入 Canonical Event Digest 的独立不可变投递元数据,其DeliverySetDefinition@N严格 DTO、规范排序与摘要规则以 Outbox Publisher 契约为唯一规范;每个Member还冻结Expected Receipt Type/Schema,core_operations额外冻结精确Projection Name/Version。Producer、Dispatcher、Ack 和 Archive不得用当前Registry、响应或Receipt自报值补Target。 - 严格 Schema 的字段增加、移除或语义变化必须发布新版本,不能静默扩展旧版本。
- Canonical Event 摘要固定为
eventEnvelopeDigestAlgorithmVersion=jcs-sha256-v1:使用与下文 Observation Digest 相同的 UTF-8/重复 Key/NFC/严格 Schema/RFC 8785/SHA-256 规范,覆盖对应 Event Version 的完整严格 Canonical Envelope(包括 Payload),只排除eventEnvelopeDigestAlgorithmVersion + eventEnvelopeSha256自身和数据库投递元数据。算法版本与eventEnvelopeSha256一起写入 Event、Ack 与 Receipt;同一eventId的 Envelope 任一 Actor/Tenant/Aggregate/Revision/Correlation/Operation/Causation/Producer/Time/Payload 字段被替换,或算法版本/摘要不同,都属于完整性错误并隔离。Contracts Release 必须提供完整 Event Golden JSON/Canonical Bytes/摘要,Producer、Consumer、Archive 与 Rebuild 使用同一制品;如实现另存 Payload-only 摘要,它只能用于局部诊断,不能满足 Delivery、Receipt、Completeness 或 Cutover。
run.created@2.0
现有 run.created@1.0 只包含基础 Run Summary,没有 requestId、认证链、模型、Execution Manifest 与价格快照等首期 Explorer 所需字段。由它产生的记录必须在 limitations[] 中包含 partial_legacy_event;Projector 不得通过查询当前 Domain State 回填历史创建快照,因为当前状态可能已经变化。
run.created@2.0 是新的严格事件版本。Canonical Envelope 与 Payload 合在一起构成低敏、自包含的 Admission Snapshot;Envelope 已有的字段不在 Payload 中重复保存。
Envelope
aggregateId = runId
actorPrincipalId
tenantKind / tenantId / workspaceId / projectId?
correlationId / operationId / traceId?
occurredAt
Payload
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字段以正式 Contracts Schema 为准,但必须满足以下不变量:
- Snapshot 描述准入事务创建时已经验证并冻结的引用,不携带可变对象全文。
run.created@2.0对 Canonical Envelope 做严格 Refinement:correlationId与operationId都是必填、非空的稳定 ID;虽然通用 Envelope 允许其他事件省略operationId,本事件缺少任一字段都必须拒绝。executionManifestRef是稳定、不透明、受授权解析的内部引用,executionManifestSchemaVersion是创建时的严格版本;二者专门指向 Run 级RunAdmissionManifest(已发布 Contracts 中 Run-levelExecutionManifest的语义角色),首期 Explorer 只展示引用和版本,不通过它读取或复制正文。aggregateId是runId,租户、Actor 与关联 ID 统一使用 Envelope;Payload 不维护第二份可漂移副本。requestId是首次成功准入的公开 Request ID。幂等重放产生的新 Request ID 不改写该事件。status在本事件中只能表达创建时准入终点;当前纵切为reserved,不能表示模型已执行或已结算。- Authentication 是严格四分支联合,只保存不透明资源 ID/Assertion/Delegation、严格 Schema Version 与 Claims Digest,不保存 Raw Session ID、Cookie、Secret、Authorization Header、Credential Digest 或可重放 Token。Developer Credential 与 Playground 分支必须含 Service Account;Customer Session 分支要求
actorPrincipalId == executionPrincipalId;Delegated Agent 分支要求 Execution Principal 是与本 Run 一对一的agent_runPrincipal、同 Tenant 且与 Agent Revision 不可变绑定,Actor/授权范围与 Delegation Grant 相等。分支外字段、缺项、两个分支混合和伪造 Service Account 都拒绝。 - Playground Grant、Customer Authentication Assertion、Delegation Grant 与 Authorization Decision 的 Ref/ID、Schema Version、Digest Algorithm Version、Digest 必须成组出现并与不可变
AuthorizationEvidenceArchive相等。事件必须冻结准入时原子持久化的AuthorizationEvidenceEvaluationSet@1完整四元组、统一authorizationEvidenceArchiveCutRevision、Authorization Scope Snapshot完整四元组、规范排序去重的authorizationActionIds[]、authorizationActionCount和精确排序成员;authorizationActionCount必须等于Action数组长度,参与Event、Scope与Evaluation Set摘要,但不能替代精确成员集合。Set的Purpose/Operation/Authentication Kind、Actor/Execution Principal、Tenant/Scope/Audience、Action Count/集合、Archive Cut、成员集合与总结果必须和事件、Validation Record及RunAdmissionManifest逐项相等且result=valid。Scope Snapshot四元组还必须等于RunAdmissionManifest的executionAuthorization.authorizationScopeSnapshot*。Developer Credential恰好一项authorization_decision;Playground、Customer Session与Delegated Agent分别恰好再多一项对应Evaluation,不得漏项、夹带、重复、Kind错配或混用不同Cut。历史Projector先以事件冻结的Set四元组、Operation/AuthKind/主体/租户、Scope四元组、Action Count/集合与Cut调用readAuthorizationEvidenceEvaluationSet,严格验证Set/Count/成员全等后,再按Set内成员四元组与同一Cut调用readAuthorizationEvidenceEvaluation;不得把逐成员读取拼成原子Set,不按run.createdAt读取当前Revision,也不回查当前Session、Membership、Grant或Policy。事件只携带低敏Set/Scope/Action/成员身份,不携带Claims/Timeline正文;未知算法/Schema、Set/Scope/成员摘要、Action Count/成员、Revision或Cut错配、Set/成员缺失或非valid全部进入Quarantine。后到撤销只能影响新准入或追加Security Incident/Reconciliation事实,不得重算已提交Run的Evaluation Set。 admission.product是冻结经济身份并必须等于admission.source.surface:Developer Credential 为developer_api,Playground Grant 为developer_playground,Customer Session 还要求productSurface相等,Delegated Agent 还要求originatingSurface相等。只有 Delegated Agent 允许agentRevisionId;MeterEvent 与 Operations Snapshot 必须逐项继承 Product/Agent Revision,不能由 Consumer 推断或覆盖。- Model 只保存已解析并冻结的 Offering、initial Execution Target 与 Routing Policy Revision;不包含 Provider Credential、物理路由配置或内部模型提示词。
- Output Contract 只保存内容寻址的
outputContractRef + outputContractSchemaVersion + outputContractDigestAlgorithmVersion + outputContractDigest四元组,不复制 Contract 正文。四项必须同时存在。Admission Producer 必须在提交事务内验证它与RunAdmissionManifest的冻结四元组逐项相等,并拒绝未知 Schema/算法、摘要错配或缺项;Projector 只按严格 Event Schema 校验并投影这组自包含字段,不解引用 Manifest 重新比较。 - 不包含输入正文、Prompt、输出、参考素材、签名 URL、客户邮箱、IP 原文或原始错误。
run.created@2.0 与 RunAdmissionManifest 的同名字段必须逐项相等,包括完整Authentication Evidence、Product、Authorization Evidence、Authorization Evidence Evaluation Set(Set四元组、统一Archive Cut、Scope Snapshot四元组、Action集合、Count、精确成员、排序与成员四元组/Revision)、Delegated Agent Revision与Output Contract四元组。这项等值是Admission Producer同事务不变量:Producer必须在Event Append前以同一份已冻结Candidate比较,任一Actor/Tenant/Access/Authentication Evidence/Product/Authorization Evidence/Evaluation Set/Scope/Action/Offering/Initial Deployment/Routing Policy/Gateway Pool/Output Contract/Pricing/Billing Policy/Reservation字段缺失、未知或错配,都回滚Run、Manifest、Reservation、Event与Outbox;Producer/Contracts/Repository测试必须覆盖这些拒绝和原子回滚。run.created@2.0本身是Projector的完整投影输入;Projector验证Event严格Schema并以事件自包含的Set/Scope/Action/Cut构造Set Read,再严格读取成员;它不解引用Manifest,也不因无法解引用而声称完成或失败了Event↔Manifest运行时比较。executionManifestRef + executionManifestSchemaVersion只用于受授权追踪。事件中的modelDeploymentId只表示首次准入目标;首次Attempt必须使用它,后续重试只有在冻结Routing Policy允许时才能选择新Deployment并创建独立Attempt Manifest。历史Admission/Eligibility只使用已提交事件及其不可变证据,更新当前授权、Billing Policy或Output Contract不能改变重放结果。
首期 Producer 在同一发布门禁停止产生两个 v1,原子改为产生 run.created@2.0 + wallet.reserved@2.0;不得出现 Run v2 搭配 Wallet v1 的窗口。第一批 Producer Capability 只启用 Organization 下的 developer_credential | playground_execution_grant;customer_session | delegated_agent 必须等对应产品的 Assertion/Delegation、Actor/Execution Principal、Billing 与端到端门禁完成后单独启用。Consumer 仍能按各自原 Schema 读取历史 v1,并在 limitations[] 标记 partial_legacy_event 和缺失原因。因为平台尚未上线,不为不存在的生产历史写复杂数据迁移或伪造缺失字段。Contracts 负向测试必须拒绝 Run v2 中缺失、空白或格式非法的 operationId / correlationId,Wallet v2 的 operationId / correlationId / causationId,以及任一 Authentication 分支缺项/混合、Claims/Decision 摘要或版本错配、Actor/Execution Principal/Assertion/Delegation 错配、跨 Run Agent Principal、Product/Surface 错配、Authorization Action Count缺失或不等于排序去重成员长度、Evaluation Count/完整四元组/Archive Revision 缺项、排序/去重/分支精确集合错误、Evaluation Purpose/Operation/Time/状态/Evidence 错配、Output Contract 四元组缺项/算法未知/摘要格式非法和后两个分支伪造 Service Account;还必须覆盖后到撤销不改变已冻结 Evaluation 的历史重放。Output Contract 正文摘要与 Ref/Schema 的内容一致性及 Event↔Manifest(含 Evaluation Set)等值由 Producer/Contracts Golden 与 Repository 原子回滚测试保证,Projector 测试不伪造 Manifest 解引用能力。
Legacy wallet.reserved@1.0
wallet.reserved@1.0 继续表达 Billing Domain 已完成的预占。它必须永远按已发布 Envelope 和 Payload 原样消费;operationId、causationId 等可选字段不能在同版本中改成必填。当前严格 Payload 是 reservation,即既有 BillingReservationRef:
reservation.billingReservationId
reservation.billingAccountId
reservation.runId
reservation.value
reservation.statuspricingSnapshotId 不在 wallet.reserved@1.0 Payload 中。不能在不升级 Wallet Event Version 的情况下假称 v1 已携带该字段,也不能要求历史 v1 必须拥有新的因果字段。
Legacy Consumer 遵循以下规则:
- 严格校验 v1 原本要求的字段、类型、Aggregate 与 Payload 自洽性;缺少原本可选的
operationId或causationId仍是合法 v1,不能因此隔离。 - v1 只能形成带
partial_legacy_event的 Legacy/Partial 投影,不能参与新的完整 Versioned Admission Snapshot。 - 若 v1 可选关联字段实际存在,Projector 可以在不猜测的前提下建立低置信度受限链接,但必须保留字段级 Lineage;缺失字段不能从时间、租户、金额、当前领域表或相邻日志补造。
- 重放不能再次预占;同一 Reservation 的后续结算、释放或调整由新领域事件表达。
- Wallet/Billing 领域仍是余额和账本的唯一事实源。
wallet.reserved@2.0
新的完整准入投影使用 Tenant-aware wallet.reserved@2.0。其 Envelope 通过事件类型 Refinement 强制非空 operationId + correlationId + causationId,Payload 为:
reservation.billingReservationId
reservation.billingAccountId
reservation.runId
reservation.pricingSnapshotId
reservation.billingPolicyRevisionId
reservation.value =
{ kind=credits; amount }
| { kind=entitlement; entitlementKey; quantity; unit }
| { kind=money; money={ amount; currency } }
reservation.status = active这里是 Billing Reservation 状态,不是 Run 状态;wallet.reserved@2.0 创建时固定为版本化 BillingReservation 枚举中的 active,同一事务里的 Run 仍为 reserved。reservation.value 复用 Contracts 的严格 BillingValue 判别联合:Credits 的 amount、Entitlement 的 quantity 与 Money 的 money.amount 都是同精度的规范 Decimal 字符串,禁止浮点转换;分支外字段、空白或非正数值一律拒绝。entitlementKey + unit 与 currency 是经济身份的一部分,不能跨 Kind、Bucket、Unit 或 Currency 合并、比较或换算。
它遵循以下不变量:
- 与 Run、Run Input、RunAdmissionManifest、幂等结果和
run.created@2.0在同一 Admission 事务提交;causationId指向本次 Run Event 的eventId。Admission Producer 在 Event Append 前强制reservation.value == RunAdmissionManifest.budgetAuthorization.reservedValue,包括 Kind、规范 Decimal 以及 Entitlement Key/Unit 或 Money Currency 的逐项等值;失败时整笔事务回滚,不能提交后留给 Projector 修复或隔离。 reservation.runId == run Event aggregateId;Wallet EventaggregateId == reservation.billingReservationId == run Event admission.billing.billingReservationId。reservation.billingAccountId == run.billingAccountId,reservation.pricingSnapshotId == run admission.billing.pricingSnapshotId,reservation.billingPolicyRevisionId == run admission.billing.billingPolicyRevisionId;Admission Producer 还必须在 Append 事务内验证三者与RunAdmissionManifest的冻结账务字段逐项相等。- 两个 Envelope 的
actorPrincipalId + tenantKind + tenantId + workspaceId + projectId? + correlationId + operationId必须逐项相等;Wallet Actor 不能省略、改用 Billing 操作者或由当前 Membership 回填,可选projectId一侧有值而另一侧缺失同样不一致。 - 首个 Producer 策略只接受
tenantKind=organization。Schema 已保留严格 Personal Space 分支,但在 tenant-aware Manifest、授权、Billing 与端到端门禁完成前不得启用。 - Projector 独立严格验证
wallet.reserved@2.0的自包含BillingValue分支,并只在已提交的 Run/Wallet 两个 Event 之间校验双方都携带的 ID、Actor、Tenant、Workspace/Project、Correlation/Operation、Billing Account、Reservation、Pricing、Billing Policy 与因果关系;这些跨 Event 字段冲突才进入 Quarantine 和 Snapshot Semantic Check。Projector 不解引用 RunAdmissionManifest,也不声称验证reservation.value与 ManifestreservedValue的运行时等值;该不变量由前述 Producer/Contracts/Repository 测试保证。 - 重放不能再次预占;后续结算、释放或调整使用新的版本化领域事件。
execution.attempt-eligibility.finalized@1
Execution每次为Attempt追加唯一ExecutionEligibilityFact@1时,必须在同一Execution事务追加Tenant-aware execution.attempt-eligibility.finalized@1,使Metering不依赖同步响应或进程内回调。schemaVersion=1的严格低敏Payload为:
AttemptEligibilityFinalized@1 = {
tenantKind / tenantId / workspaceId / projectId?
billingReservationId / billingAccountId
runId / runStepId / executionAttemptId
attemptExecutionManifestRef / attemptExecutionManifestSchemaVersion
attemptExecutionManifestDigestAlgorithmVersion / attemptExecutionManifestDigest
executionEligibilityFactRef / executionEligibilityFactSchemaVersion
executionEligibilityFactDigestAlgorithmVersion / executionEligibilityFactDigest
terminalState = succeeded | failed | cancelled | timed_out
dispatchDisposition =
{ state=gateway_bound;
attemptRouteBindingRef / attemptRouteBindingSchemaVersion;
attemptRouteBindingDigestAlgorithmVersion / attemptRouteBindingDigest;
gatewayRouteSnapshotRef / gatewayRouteSnapshotSchemaVersion;
gatewayRouteSnapshotDigestAlgorithmVersion / gatewayRouteSnapshotDigest }
| { state=not_dispatched;
attemptNotDispatchedFactRef / attemptNotDispatchedFactSchemaVersion;
attemptNotDispatchedFactDigestAlgorithmVersion / attemptNotDispatchedFactDigest;
gatewayAttemptDispatchSlotId / gatewayPreBindingRequestId / gatewayDeploymentId }
outputEligibility =
{ state=registered;
outputFactRef / outputFactSchemaVersion;
outputFactDigestAlgorithmVersion / outputFactDigest }
| { state=not_required;
outputContractRef / outputContractSchemaVersion;
outputContractDigestAlgorithmVersion / outputContractDigest }
| { state=unavailable; reasonCode }
| { state=failed;
executionFailureFactRef / executionFailureFactSchemaVersion;
executionFailureFactDigestAlgorithmVersion / executionFailureFactDigest }
}producer是认证后的Execution Workload;Envelope Actor/Tenant与Run Admission、Attempt Manifest和Fact逐项相等,Payload Tenant字段与Envelope相等。aggregateId=executionAttemptId、aggregateRevision=1,同Attempt终身只允许一个Fact和一个同摘要Event。Payload的Terminal、Dispatch与Output三部分必须与Owner Fact严格同构;Gateway-bound与Not-dispatched互斥,Not-dispatched/Failed分支必须完整绑定对应低敏Fact四元组,禁止原始错误、Prompt、Provider正文、URL或资产内容。Fact、嵌套Fact、Event、Canonical摘要、冻结Definition四元组或metering_input_processor Mandatory Delivery任一失败,Execution事务全部回滚。
Metering Consumer只在本地Inbox事务严格验证Event与Owner四元组,推进MeteringAttemptInputFence的Eligibility Watermark、insert-or-compare可恢复Work并写版本化Consumer Receipt后Ack。Gateway-bound分支可以等待Gateway Usage Availability;Not-dispatched分支不等待不存在的Gateway输入。Event先于Gateway输入、Consumer/Ack响应丢失或Worker崩溃都只能留下可恢复非终态Work,不能把Event Receipt当作Eligibility Decision、MeterEvent或Settlement Input。readExecutionEligibilityFact或后续Gateway Evidence Owner Read的确定性冲突必须分别返回内容寻址的ExecutionEligibilityConflictFact@1 | GatewayEvidenceConflictFact@1完整四元组;Metering同事务写MeteringInputWorkAttemptBlocked@1,把Fence置为coordinationState=blocked_on_owner_conflict,释放Lease但保留相同Work/Operation/Generation及activeWork.state=pending。该路径禁止写Work Finish、Eligibility Decision、Settlement Input、Lane终态CAS或reconciliationCaseRef;Owner修复后按同Work重新Lease,或由合法新Current Event创建后继Generation。首期没有这些Conflict Fact的Canonical Event与core_operations Mandatory Delivery,因此只能形成Owner诊断和明确标注不完整的best-effort告警,不能宣称Operations拥有可靠Admin队列。Contracts/跨进程测试必须覆盖四类Output、两类Dispatch、错Step/Manifest/Fact、半联合/未知字段、同Attempt第二Event、两类Conflict Fact/Blocked四元组错配、阻塞后同Work重试,以及Fact提交后在同步Metering调用或返回响应前崩溃仍最终生成规范Metering输入。
Attempt Binding Owner Read冲突使用独立内容寻址GatewayBindingConflictFact@1,不得拿Gateway Evidence Conflict Fact或泛conflictRef代替。Eligibility Lane选择MeteringInputWorkAttemptBlocked@1.blockingCause.kind=gateway_binding_owner_conflict;Provider Cost Lane必须选择外层provider_cost_processing_conflict,再由ProviderCostProcessingConflictEvidence@1.cause.kind=gateway_binding_owner_conflict绑定同一Fact。两条Lane不能互换外层Cause,任一Fact四元组、Purpose或Expected Attempt/Deployment错绑都拒绝。
Eligibility Basis、Availability Validation Receipt、Eligibility Decision、MeterEvent或Settlement Input构造/Repository的已证明本地确定性冲突使用EligibilityProcessingConflictEvidence@1,其validationStage严格为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原子提交,同样保持Active Work而不写Finished/CAS/Case。Provider Cost本地确定性校验同理由ProviderCostProcessingConflictEvidence@1.cause=metering_deterministic_validation冻结Stage与冲突Candidate带算法摘要,再由Blocked外层provider_cost_processing_conflict绑定。暂态依赖、网络、锁或容量错误不得伪造任何Evidence。
gateway.evidence-availability.current-changed@1
Text/Media Gateway每次为attempt-bound Usage或Cost链CAS推进Current Availability时,必须在Evidence(若有)、Availability Snapshot和Current/Active Head同一Gateway事务追加gateway.evidence-availability.current-changed@1。Pre-binding/no-execution Snapshot严格不得产生该Event。schemaVersion=1的封闭Payload为:
GatewayEvidenceAvailabilityCurrentChanged@1 = {
executionAttemptId / gatewayDeploymentId
attemptExecutionManifestRef / attemptExecutionManifestSchemaVersion
attemptExecutionManifestDigestAlgorithmVersion / attemptExecutionManifestDigest
attemptRouteBindingRef / attemptRouteBindingSchemaVersion
attemptRouteBindingDigestAlgorithmVersion / attemptRouteBindingDigest
gatewayRouteSnapshotRef / gatewayRouteSnapshotSchemaVersion
gatewayRouteSnapshotDigestAlgorithmVersion / gatewayRouteSnapshotDigest
providerEvidenceKind = provider_usage | provider_cost
evidenceDimensionKey
currentAvailabilityRef / currentAvailabilitySchemaVersion
currentAvailabilityDigestAlgorithmVersion / currentAvailabilityDigest
currentAvailabilityStateVersion
currentEvidence =
{ state=none }
| { state=available; evidenceRef / evidenceSchemaVersion;
evidenceDigestAlgorithmVersion / evidenceDigest }
supersedesAvailability =
{ state=root }
| { state=supersedes; ref; schemaVersion; digestAlgorithmVersion; digest; stateVersion }
}Event使用Tenant-aware Envelope;producer是持有该Deployment身份的认证Gateway Workload,Actor/Tenant/Workspace/Project/Correlation必须与事件绑定的不可变Attempt Manifest相等。aggregateId由Contracts按executionAttemptId + gatewayDeploymentId + providerEvidenceKind + evidenceDimensionKey确定性派生,aggregateRevision=currentAvailabilityStateVersion。Payload必须与同事务Current Snapshot、Active Head、Binding/Route及Attempt Manifest四元组逐项相等;Snapshot为available时且仅此时currentEvidence=available并完整等于对应Usage/Cost Evidence四元组,其余状态固定none。Root/后继与Current Availability直接前驱五元组同构,第二Root、跳号、分叉和同Revision异摘要拒绝。
Gateway事务必须同时冻结适用Delivery Set四元组并创建唯一metering_input_processor Mandatory Delivery;任一Event/Digest/Member失败都回滚Evidence/Availability/Pointer。Metering Consumer只在Inbox事务推进相同Attempt/Kind/Dimension的Availability Watermark、insert-or-compare不可变Work和写版本化Consumer Receipt后Ack;旧合法Revision可already_observed,同Revision异摘要或跨Binding/Route/Kind/Dimension重绑隔离。Gateway Source正文只允许用绑定这条已接受Event完整ID/Type/Schema/Envelope摘要、Aggregate/Revision与Payload身份的metering_source_event_bootstrap读取;该Purpose不携带尚未从正文构造的Eligibility Basis、Provider Cost Candidate或Validation Operation,Bootstrap响应也不是Current Receipt。Usage Work与Execution Eligibility Watermark合流,Cost Work不等待客户Eligibility;新Revision必须重新置Pending。Cost Work对Gateway Availability的五种状态形成严格处理:available → available_applied并绑定ProviderCostFact与两份验证Receipt,pending → waiting_for_evidence,not_reported|unavailable → no_cost_evidence且不表示零成本;只有Gateway Owner已经持久化state=conflicting时才允许追加ProviderCostAvailabilityProcessingFact@1.outcome={state=reconciliation_required; availabilityState=conflicting; stateReasonCode}。该Processing Fact、Work Finish与Lane CAS同一Metering事务提交,以Processing Fact完整四元组作为Metering-owned耐久锚点;仅后继Gateway Availability Current-changed Event可创建新Generation恢复。
available路径中的确定性Schema/Identity/Mapping/Normalization/Receipt/Repository冲突不属于上述终态。Metering只追加内容寻址ProviderCostProcessingConflictEvidence@1与MeteringInputWorkAttemptBlocked@1,把Fence置为coordinationState=blocked_on_processing_conflict,释放Lease但保留同一Work/Operation/Generation及activeWork.state=pending;严格禁止写Processing Fact、Work Finish、ProviderCostFact或Lane终态CAS。修复Owner/Registry/Repository状态后以同Work重新Lease,合法新Current Event仍可Supersede旧Work。Owner暂不可读、超时、锁冲突或其他可重试错误连Conflict Evidence/Blocked Fact也不写,仅返回retryable。所有分支都不创建或引用第三种Reconciliation Case。当前十三Pair Delivery Set没有Provider Cost Processing/Conflict Event,首期只保证Metering定向诊断和明确标注不完整的best-effort告警,不得声称Operations拥有完整队列;可靠Admin投影必须以后续Canonical Event、core_operations Mandatory Delivery、Owner Read和严格Projection契约为前置。Contracts和跨进程测试覆盖全部Availability状态、Gateway-conflicting终态、确定性冲突Blocked后同Work重试、暂态错误零领域Fact/Finish/CAS、Evidence四元组裁剪/错摘要、A1/E1完成后A2/E2提交但同步响应/Observation丢失且无后续Poll、Event Ack后Worker崩溃、乱序/合并、同Ref异摘要、Pre-binding误发与Event/Current原子回滚。
execution.run-finalization.closed@1
Execution把Run的Attempt Admission不可逆地从accepting_attempts推进为attempts_closed时,必须在同一Execution事务追加RunAttemptSetManifest@1、RunExecutionFinalizationFact@1和Tenant-aware execution.run-finalization.closed@1。该Event是Billing Finalization Coordinator的必达触发事实,不是“已完成结算”的状态。其schemaVersion=1 Payload为:
RunFinalizationClosed@1 = {
tenantKind / tenantId / workspaceId / projectId?
billingReservationId / billingAccountId / runId
closureOperationId
runAdmissionManifestRef / runAdmissionManifestSchemaVersion
runAdmissionManifestDigestAlgorithmVersion / runAdmissionManifestDigest
runExecutionFinalizationRef / runExecutionFinalizationSchemaVersion
runExecutionFinalizationDigestAlgorithmVersion / runExecutionFinalizationDigest
runExecutionFinalizationStateVersion
attemptSetManifestRef / attemptSetManifestSchemaVersion
attemptSetManifestDigestAlgorithmVersion / attemptSetManifestDigest
attemptCount
}producer必须是认证后的Execution Workload;Envelope Actor/Tenant/Workspace/Project与不可变Run Admission/Finalization Fact逐项相等,Payload重复Tenant字段还必须与Envelope相等。Envelope operationId == closureOperationId,aggregateId == runId,aggregateRevision=1;同一Run终身只能有一个Closure Event。Payload中的Finalization Fact与Attempt Set Manifest四元组、State Version、Attempt Count必须和同事务Owner对象双向等值,Run Admission Manifest四元组必须与所有Attempt Manifest冻结值一致。关闭CAS、Owner Fact/Manifest、Event、Canonical Envelope Digest、Delivery Set Definition四元组或billing_finalization_coordinator Mandatory Delivery任一失败,整笔Execution事务回滚;关闭后不得新增Attempt、重开Run或产生第二个Event。
Billing Consumer只能把该Event用于推进BillingFinalizationFence@1的Execution Closure Watermark。它必须验证完整Event与Owner四元组,在单一Billing Inbox事务冻结新的BillingFinalizationTriggerSnapshot@1、递增Fence Revision/Work Generation、insert-or-compare BillingFinalizationWork@1并写版本化Consumer Receipt后才Ack;不能把Event本身当作Finalization Decision、Ledger或Reservation终态。每代Work的billingFinalizationOperationId必须由Contracts固定Namespace按billingReservationId + runId + workGeneration + Trigger Snapshot完整摘要确定性派生并持久化,响应丢失或Worker重启恢复同一Operation。Execution的Precondition/Finalization Owner Read或Current Validation若返回确定性冲突,只能返回内容寻址RunExecutionFinalizationConflictFact@1完整四元组,不得返回或预先创建reconciliationCaseRef;Billing把该Conflict Fact冻结进Finalization输入并由正式Finalization Decision决定是否按下节Run级Identity协议创建Operations Case。Owner暂不可读仍为retryable,不生成Conflict Fact或Case。
Contracts与跨进程测试必须覆盖Attempt集合空/非空、Count/排序、Fact/Manifest/Event任一四元组裁剪或摘要错配、跨Tenant/Account/Reservation/Run重绑、同Run第二Event、关闭CAS后Event/Delivery失败回滚、Event早于Fence异常可见、Consumer/Ack响应丢失,以及Execution关闭后在任何同步Billing调用或排队前崩溃仍由Mandatory Delivery创建可恢复Finalization Work。
billing.finalization-reconciliation.case-requested@1
Finalization Work首次对某一代判定reconciliation_required前,Billing必须先通过Operations的无业务Case副作用预留契约取得稳定Case Ref。预留不创建可见Case;Billing随后才在同一Finalization事务追加reconciliation_required Decision、推进Fence、完成本代Work,并发布Tenant-aware billing.finalization-reconciliation.case-requested@1。schemaVersion=1的封闭Payload为:
FinalizationReconciliationCaseIdentityReservation@1 = {
finalizationCaseIdentityReservationRef / finalizationCaseIdentityReservationSchemaVersion
finalizationCaseIdentityReservationDigestAlgorithmVersion=jcs-sha256-v1
finalizationCaseIdentityReservationDigest
reconciliationCaseRef
sourceOwner=billing / sourceKind=billing_finalization
tenantKind / tenantId / workspaceId / projectId?
billingReservationId / billingAccountId / runId / billingFinalizationFenceId
reconciliationGeneration / caseRequestOperationId
predecessorCase =
{ state=root }
| { state=supersedes; previousReconciliationGeneration; previousReconciliationCaseRef;
previousFinalizationCaseIdentityReservationRef / previousFinalizationCaseIdentityReservationSchemaVersion;
previousFinalizationCaseIdentityReservationDigestAlgorithmVersion / previousFinalizationCaseIdentityReservationDigest }
reservedAt
}
FinalizationReconciliationCaseRequestedEvent@1 = {
tenantKind / tenantId / workspaceId / projectId?
billingReservationId / billingAccountId / runId / billingFinalizationFenceId
reconciliationCaseRef / reconciliationGeneration / expectedResolutionRevision=1
caseRequestOperationId / billingFinalizationOperationId
finalizationCaseIdentityReservationRef / finalizationCaseIdentityReservationSchemaVersion
finalizationCaseIdentityReservationDigestAlgorithmVersion / finalizationCaseIdentityReservationDigest
billingFinalizationDecisionRef / billingFinalizationDecisionSchemaVersion
billingFinalizationDecisionDigestAlgorithmVersion / billingFinalizationDecisionDigest
triggeringBillingFinalizationWorkRef / triggeringBillingFinalizationWorkSchemaVersion
triggeringBillingFinalizationWorkDigestAlgorithmVersion / triggeringBillingFinalizationWorkDigest
triggeringBillingFinalizationWorkGeneration
reasonCode
}
readBillingFinalizationDecisionForOperations(
purpose=operations_finalization_case_projection,
caseRequestedEvent={ eventId / eventType / eventSchemaVersion;
eventEnvelopeDigestAlgorithmVersion / eventEnvelopeSha256 },
expected tenantKind, tenantId, workspaceId, projectId?, billingAccountId,
billingReservationId, runId, billingFinalizationFenceId,
reconciliationCaseRef, reconciliationGeneration, expectedResolutionRevision=1,
caseRequestOperationId, billingFinalizationOperationId,
finalizationCaseIdentityReservation完整四元组,
billingFinalizationDecision完整四元组,
triggeringBillingFinalizationWork完整四元组, triggeringBillingFinalizationWorkGeneration
)
-> { result=found; decision=BillingFinalizationDecision@1; triggeringWork=BillingFinalizationWork@1 }
| { result=not_found }
| { result=conflicting; conflictRef / conflictSchemaVersion;
conflictDigestAlgorithmVersion / conflictDigest }
PendingFinalizationCaseSuccessor@1 = {
projectionName / projectionVersion
sourceOwner=billing / sourceKind=billing_finalization
tenantKind / tenantId / workspaceId / projectId?
billingReservationId / billingAccountId / runId / billingFinalizationFenceId
successorReconciliationCaseRef / successorReconciliationGeneration
successorFinalizationCaseIdentityReservation完整四元组
predecessorCase = { state=supersedes; previousReconciliationGeneration;
previousReconciliationCaseRef; previousFinalizationCaseIdentityReservation完整四元组 }
caseRequestOperationId / billingFinalizationOperationId / expectedResolutionRevision=1
caseRequestedEvent={ eventId / eventType / eventSchemaVersion;
eventEnvelopeDigestAlgorithmVersion / eventEnvelopeSha256 }
billingFinalizationDecision完整四元组
triggeringBillingFinalizationWork完整四元组 / triggeringBillingFinalizationWorkGeneration
reasonCode
state = { kind=pending }
| { kind=consumed; predecessorResolutionEvent完整四元组;
predecessorResolutionAppliedFact完整四元组;
successorMaterializedCaseRevision; consumedAt }
firstSeenAt
}caseRequestOperationId由Contracts固定Namespace按规范化sourceOwner/sourceKind + tenantKind/tenantId/workspaceId/projectId? + billingReservationId/billingAccountId/runId/billingFinalizationFenceId + reconciliationGeneration确定性派生。reserveFinalizationReconciliationCaseIdentity与readFinalizationReconciliationCaseIdentityBySource只接受精确Billing Finalization Case Identity Workload Audience/Scope;请求/Token绑定Tenant、Account/Reservation/Run/Fence、Generation、Operation与Predecessor,服务端重算Operation和摘要,严格返回reserved|found|not_found|conflicting{conflict Reservation quartet},禁止Admin、列表、latest或裸Ref读取。Reservation摘要覆盖除Repository-owned Ref/Time和摘要自身外的完整稳定Identity;不得包含Triggering Work或billingFinalizationOperationId。同Source/Generation重放采用原Reservation;reserve(G1) → G2 Work supersedes G1 → G1 CAS失败 → G2必须复用同Identity。
producer必须是认证后的Billing Workload;Envelope Actor/Tenant/Workspace/Project和Payload租户字段与Run/Fence/Decision逐项相等,operationId == caseRequestOperationId。aggregateId == reconciliationCaseRef且aggregateRevision=1;Case Ref必须来自完整FinalizationReconciliationCaseIdentityReservation@1四元组,不得在Event投影时临时生成。Identity只冻结稳定Tenant/Account/Reservation/Run/Fence/Generation/Case Request Operation/Predecessor,不绑定可被并发后继超越的Work或Finalization Operation;Event中显式billingFinalizationOperationId与最终Winning Work四元组才是投影精确Owner Read的输入。Payload中Tenant、Account/Reservation/Run/Fence、Case/Generation/Expected Resolution Revision/Case Request Operation、Identity Reservation、Billing Finalization Operation、Decision、Triggering Work与Reason必须和同事务Fence/Work Finish/Decision及已预留Identity双向等值。Operations Projector只能以Event完整四元组与全部Expected身份调用readBillingFinalizationDecisionForOperations;Owner和Projector双方重算摘要,not_found保持Delivery non-terminal,conflicting隔离,禁止回查当前Fence或按裸Ref读取。Decision、Fence、Work Finish、Audit、Event、Canonical Envelope Digest、冻结Delivery Set Definition四元组或唯一core_operations → operations_projector Mandatory Delivery任一失败,整笔Billing事务回滚。
Operations Projector只能在验证Event、Identity Reservation、Billing Decision和Triggering Work完整四元组后,按预分配Ref幂等创建sourceKind=billing_finalization的Run级Case;投影不得读取当前Fence猜测创建输入。同Event同摘要返回首次Applied Receipt;同Case/代次异Identity、Decision、Work或Reason进入Quarantine且不产生Case副作用。Identity或Owner Fact暂不可读时不写Receipt,保持Delivery非终态重试。Operations可拥有调查、评论、指派和审批时间线,但只有消费Billing-owned Resolution Applied Event才能使该业务Case进入终态。
若后继Case Requested先于直接前代的Billing Resolution终态到达,Projector必须锁定同Source的前代与Pending集合,将上述严格PendingFinalizationCaseSuccessor@1(state=pending)和本Event的OperationsProjectionAppliedReceipt@1在同一事务insert-or-compare后Ack,不得创建第二个Open Case或丢弃已Ack的后继。前代Resolution到达时,若前代Case已存在,必须在一个Operations事务验证并终结前代、物化后继并把Pending推进为consumed,冻结前代Resolution Event完整Envelope与Applied Fact四元组;若前代Request仍缺失,则先保存既有pending_resolution,待其到达后在同一事务直接物化Resolved前代、消费后继并创建后继。多代乱序按Generation和完整Predecessor Identity消费最大连续已验证链,至多最后一代可Open。C2 Request → C1 Resolution → C1 Request、Resolution/Request并发、同代异Candidate和Shadow Rebuild必须确定性收敛。
Contracts/Repository/跨进程测试覆盖预留响应丢失后按Source/Generation/Operation恢复同一Identity、reserve(G1) → G2 Work supersedes G1 → G1 CAS失败 → G2复用同Identity并以自己Operation/Work成功提交、Event与Identity/Decision/Winning Work/Fence/Finalization Operation任一错配、同Case异摘要、Case Request投影响应丢失不创建第二Case、C2 Request → C1 Resolution → C1 Request的Pending Successor原子消费、Owner Read暂不可用保持Delivery非终态,以及Decision/Fence/Work/Event/Delivery中途失败无半完成状态。
billing.finalization-reconciliation-resolution.applied@1
Billing Finalization进入reconciliation_required后,Operations只能编排受控调查与授权;Billing验证当前Case代次、Resolution Decision、一次性Command Grant与已授权调查引用集合后,必须在同一Billing事务追加FinalizationReconciliationResolutionApplied@1与Tenant-aware billing.finalization-reconciliation-resolution.applied@1。该Event只表示“受控重评授权已应用”的必达触发事实,不声称Owner事实已经修复,也不是Ledger、Reservation终态或忽略冲突的命令。schemaVersion=1的封闭Payload为:
FinalizationReconciliationResolutionCommandRequest@1 = {
finalizationReconciliationResolutionCommandRequestRef / finalizationReconciliationResolutionCommandRequestSchemaVersion
finalizationReconciliationResolutionCommandRequestDigestAlgorithmVersion=jcs-sha256-v1
finalizationReconciliationResolutionCommandRequestDigest
adminCommandId / domainIdempotencyKey / actorWorkforcePrincipalId
tenantKind / tenantId / workspaceId / projectId?
billingAccountId / billingReservationId / runId / billingFinalizationFenceId
reconciliationCaseRef / reconciliationGeneration / expectedCaseRevision / expectedResolutionRevision=1
finalizationCaseIdentityReservation完整四元组
caseRequestEventId / caseRequestEventType / caseRequestEventSchemaVersion
caseRequestEventEnvelopeDigestAlgorithmVersion / caseRequestEventEnvelopeSha256
action=reevaluate_current_owner_state
investigationEvidenceReferenceCount
investigationEvidenceReferences[]=sorted { ownerDomain=execution|metering|gateway|billing; evidenceKind;
evidenceRef / evidenceSchemaVersion / evidenceDigestAlgorithmVersion / evidenceDigest }
reasonCode
}
FinalizationReconciliationResolutionDecision@1 = {
resolutionDecisionRef / resolutionDecisionSchemaVersion
resolutionDecisionDigestAlgorithmVersion=jcs-sha256-v1 / resolutionDecisionDigest
resolutionDecisionInputDigestAlgorithmVersion=jcs-sha256-v1 / resolutionDecisionInputDigest
resolutionDecisionIdempotencyKey
finalizationReconciliationResolutionCommandRequest完整四元组
adminCommandCandidate完整四元组
actorWorkforcePrincipalId
tenantKind / tenantId / workspaceId / projectId?
billingAccountId / billingReservationId / runId / billingFinalizationFenceId
reconciliationCaseRef / reconciliationGeneration / expectedCaseRevision / expectedResolutionRevision=1
finalizationCaseIdentityReservation完整四元组
caseRequestEventId / caseRequestEventType / caseRequestEventSchemaVersion
caseRequestEventEnvelopeDigestAlgorithmVersion / caseRequestEventEnvelopeSha256
action=reevaluate_current_owner_state
investigationEvidenceReferenceCount
investigationEvidenceReferences[]=FinalizationReconciliationResolutionCommandRequest@1.investigationEvidenceReferences[]
decision=authorized / decidedAt
}
FinalizationReconciliationResolutionApplied@1 = {
finalizationReconciliationResolutionAppliedRef / finalizationReconciliationResolutionAppliedSchemaVersion
finalizationReconciliationResolutionAppliedDigestAlgorithmVersion=jcs-sha256-v1
finalizationReconciliationResolutionAppliedDigest
resolutionOperationId
tenantKind / tenantId / workspaceId / projectId?
billingReservationId / billingAccountId / runId / billingFinalizationFenceId
reconciliationCaseRef / reconciliationGeneration / resolutionRevision=1
finalizationCaseIdentityReservation完整四元组
caseRequestEventId / caseRequestEventType / caseRequestEventSchemaVersion
caseRequestEventEnvelopeDigestAlgorithmVersion / caseRequestEventEnvelopeSha256
finalizationReconciliationResolutionCommandRequest完整四元组
adminCommandCandidate完整四元组
finalizationReconciliationResolutionDecision完整四元组
commandExecutionGrantRef / commandExecutionGrantSchemaVersion
commandExecutionGrantDigestAlgorithmVersion / commandExecutionGrantDigest / commandExecutionGrantJti
action=reevaluate_current_owner_state
investigationEvidenceReferenceCount
investigationEvidenceReferences[]=FinalizationReconciliationResolutionCommandRequest@1.investigationEvidenceReferences[]
actorWorkforcePrincipalId / appliedAt
}
FinalizationReconciliationResolutionAppliedEvent@1 = {
tenantKind / tenantId / workspaceId / projectId?
billingReservationId / billingAccountId / runId / billingFinalizationFenceId
reconciliationCaseRef / reconciliationGeneration / resolutionRevision=1 / resolutionOperationId
finalizationCaseIdentityReservationRef / finalizationCaseIdentityReservationSchemaVersion
finalizationCaseIdentityReservationDigestAlgorithmVersion / finalizationCaseIdentityReservationDigest
finalizationReconciliationResolutionAppliedRef / finalizationReconciliationResolutionAppliedSchemaVersion
finalizationReconciliationResolutionAppliedDigestAlgorithmVersion / finalizationReconciliationResolutionAppliedDigest
action = reevaluate_current_owner_state
}
readFinalizationReconciliationResolutionAppliedForOperations(
purpose=operations_finalization_case_projection,
resolutionEvent={ eventId / eventType / eventSchemaVersion;
eventEnvelopeDigestAlgorithmVersion / eventEnvelopeSha256 },
expected tenantKind, tenantId, workspaceId, projectId?, billingAccountId,
billingReservationId, runId, billingFinalizationFenceId,
reconciliationCaseRef, reconciliationGeneration, resolutionRevision=1,
resolutionOperationId, actorWorkforcePrincipalId,
finalizationCaseIdentityReservation完整四元组,
finalizationReconciliationResolutionApplied完整四元组
)
-> { result=found; fact=FinalizationReconciliationResolutionApplied@1 }
| { result=not_found }
| { result=conflicting; conflictRef / conflictSchemaVersion;
conflictDigestAlgorithmVersion / conflictDigest }
readFinalizationReconciliationResolutionAppliedByOperation(
resolutionOperationId,
expected tenantKind, tenantId, workspaceId, projectId?, billingAccountId, billingReservationId, runId,
billingFinalizationFenceId, reconciliationCaseRef, reconciliationGeneration,
resolutionRevision=1, actorWorkforcePrincipalId,
finalizationCaseIdentityReservation完整四元组,
finalizationReconciliationResolutionCommandRequest完整四元组,
adminCommandCandidate完整四元组,
finalizationReconciliationResolutionDecision完整四元组,
signedWorkforceCommandExecutionGrant完整四元组 / commandGrantJti
)
-> { result=found; fact=FinalizationReconciliationResolutionApplied@1 }
| { result=not_found }
| { result=conflicting; conflictRef / conflictSchemaVersion;
conflictDigestAlgorithmVersion / conflictDigest }Request、Decision与Fact摘要都排除Repository-owned Ref/Time和摘要自身;Request不含Candidate/Decision/Grant/Fact/Operation,Decision不含Grant/Operation。通用AdminCommandCandidate@1使用commandType=finalization_reconciliation_resolution,且其既有commandRequest.payload严格选择本Request分支并保存完整四元组;不得另造第二个Candidate specialization字段。只有SignedWorkforceCommandExecutionGrant@1.commandSpecialization.kind=finalization_reconciliation_resolution逐项回显Request稳定身份,并额外绑定Decision完整四元组和由其确定性派生的resolutionOperationId。依赖顺序固定为:先冻结Request业务Payload,Preview/Approval/JIT基于它完成,最终Candidate聚合全部,Operations在Candidate后形成Decision,Core最后签Grant;因此不存在摘要环。Grant aud=billing_finalization_reconciliation_resolution_command、boundWorkload=Admin Command Gateway Workload Principal且等于实际呈递Domain调用的Caller JWT sub,Grant sub=actorWorkforcePrincipalId;动作唯一为reevaluate_current_owner_state。任何对象都禁止金额、Ledger、Reservation终态、替代Snapshot、忽略冲突或自由Map。调查引用必须非空、按Contracts Registry的封闭Tuple排序去重并与Count等值,只用于授权与审计关联;Billing只验证Request/Candidate/Decision/Grant/Fact之间集合等值及注册Schema,不得解引用它们来证明Owner已经修复。真正安全性由触发后既有Finalization严格Owner Reads与Current Receipts保证;Owner状态仍冲突时重评必须创建新Case代次。当前ADR-030阶段不启用此命令;任何Producer可能进入reconciliation_required前,必须与ADR-031完整命令链同批启用。
Command Grant Exchange本身也必须能从“Core已提交Grant但响应丢失”恢复。commandGrantExchangeOperationId由Contracts固定Namespace仅按actorAssertionIssuer + actorAssertionJti确定性派生;两字段分别逐字节等于已验证Actor Assertion JWT的iss + jti。Candidate不进入Operation ID,而与Bound Workload、Request Digest/Nonce、Actor一起成为首次Result的insert-or-compare正文,使同JTI偷换任何正文稳定命中conflicting。Core持久化内容寻址CommandGrantExchangeResult@1,完整绑定Operation、actorAssertionIssuer + actorAssertionJti、Actor、Bound Workload、Request Digest/Nonce、Candidate quartet、Grant quartet/JTI。readCommandGrantExchangeResultByOperation(operationId, expected actorAssertionIssuer/actorAssertionJti/commandType/actor/boundWorkload/requestDigest+nonce/Candidate quartet)严格返回found{result; grant=SignedWorkforceCommandExecutionGrant@1} | not_found | conflicting{conflict quartet}。Core先认证Caller Workload JWT并要求sub == boundWorkload,随后在检查Assertion当前时间/JTI前执行该Read:found同输入返回首次Grant,即使Assertion已过期或JTI已消费;conflicting停写;仅not_found才校验Assertion、消费JTI,并在一个事务写Grant、Result与签发Audit。Grant/Result摘要或Audit任一失败全部回滚。
Billing命令入口必须在Grant时间窗/JTI或当前Fence检查之前调用readFinalizationReconciliationResolutionAppliedByOperation:found时完整比较Actor、Request/Candidate/Decision/Grant/JTI并返回首次Fact,即使Grant已过期或JTI已消费;conflicting停写;仅not_found进入新执行。producer必须是认证后的Billing Workload;Envelope Tenant/Workspace/Project、Payload租户字段与Owner Fact必须逐项相等,Envelope actorPrincipalId == FinalizationReconciliationResolutionApplied@1.actorWorkforcePrincipalId,且该Actor必须继续等于Resolution Decision Actor、Command Grant sub、Candidate Actor与Audit Actor;operationId == resolutionOperationId。每个Reconciliation Generation只允许一次Resolution Applied:Case Requested/Request/Decision/Grant固定expectedResolutionRevision=1,Applied Fact/Event固定resolutionRevision=1;重评仍冲突必须开启后继Generation。aggregateId由Contracts固定Namespace按billingFinalizationFenceId + reconciliationGeneration确定性派生,aggregateRevision=1,每个Aggregate只有该Event;同Revision异摘要或跨Fence/Case/代次重绑拒绝。Payload中Identity Reservation与Resolution Fact两个完整四元组、Case/Generation/Revision/Operation与Action必须与同事务Owner Fact双向等值;Fact又必须绑定同一Identity、已投影Case Request Event完整Envelope摘要、Request/Candidate/Decision、Command Grant JTI和排序的Investigation Evidence References。Operations Projector只用Event四元组及Expected身份调用readFinalizationReconciliationResolutionAppliedForOperations,双方重算Fact摘要;Owner暂不可读返回retryable且不得写Outcome/Receipt。Fact、Grant消费、Audit、Event、Canonical Envelope Digest、冻结Delivery Set Definition四元组或billing_finalization_coordinator + core_operations两条Mandatory Delivery任一失败,整笔Billing事务回滚。
Coordinator收到事件后先严格验证Event/Identity/Fact与当前Fence。若仍是同一Case/代次和期望Resolution Revision,它必须在单一Billing Inbox事务单调推进reconciliationResolutionWatermark,把该Watermark冻结进新BillingFinalizationTriggerSnapshot@1,产生新Generation的内容寻址BillingFinalizationWork@1,再与BillingFinalizationCoordinatorConsumerReceipt@1同事务Ack。只有同一Resolution Event已经成功应用并创建了它自己的重评Work,或该Work的重评已推进/被该次Resolution吸收时,重投才可返回绑定原Event/Identity/Fact与当时Fence Revision的already_superseded Receipt;普通Closure/Settlement Owner输入在Case持有态只推进Watermark(Settlement返回held Receipt),不能创建后继Work或使Resolution变成superseded。Owner Fact暂不可读、Fence锁不可用或无法判定Current/Stale时返回retryable且不写Receipt/Ack;摘要或身份冲突才是non_retryable。同一Event的core_operations投影只能在验证Billing Fact、Identity与Case Request身份后将对应Operations Case进入Billing-authoritative终态,不得因Coordinator先行接收就本地关闭Case。
Contracts Golden与跨进程测试必须覆盖Current Resolution创建唯一后继Work、同一Resolution已应用/重评推进后的重投返回already_superseded、Closure/Settlement先到只推进Watermark且不得令Resolution superseded、同Event/Ack响应丢失幂等、Fact或Case/Generation/Revision/Operation/Fence错绑、Envelope/Decision/Grant/Candidate/Audit跨Actor重绑、同Revision异摘要、旧Grant跨代重放、二次评估仍冲突则创建新Case代次,以及Fact/Event/Delivery中途失败不留半完成状态。
metering.settlement-input.current-changed@1
Metering 每次原子追加 SettlementInputSnapshot@1 并推进该维度 Current Pointer 时,都必须在同一 Metering 事务追加 Tenant-aware metering.settlement-input.current-changed@1。这不是可以丢失的遥测通知,而是终局后结算更正的必达唤醒事实。其 Event Type 与 schemaVersion=1 是精确字面量,Payload 为未知字段拒绝的封闭 DTO:
SettlementInputCurrentChanged@1 = {
tenantKind / tenantId / workspaceId / projectId?
billingReservationId / billingAccountId / runId / runStepId
executionAttemptId / chargeDimensionKey
settlementEconomicIdentity = SettlementEconomicIdentity@1
currentSettlementInputSnapshotRef / currentSettlementInputSnapshotSchemaVersion
currentSettlementInputSnapshotDigestAlgorithmVersion / currentSettlementInputSnapshotDigest
currentSettlementInputStateVersion
supersedesSettlementInputSnapshot =
{ state=root }
| { state=supersedes; ref; schemaVersion; digestAlgorithmVersion; digest; stateVersion }
}该事件使用 Tenant-aware Canonical Envelope;producer 必须是认证后的 Metering Workload,Envelope 的 actorPrincipalId + tenantKind + tenantId + workspaceId + projectId? 必须与 Owner Snapshot 逐项相等,Payload 中重复的 Tenant 字段还必须与 Envelope 严格等值。correlationId 必填;operationId / causationId / traceId沿用Canonical Envelope的可选性,存在时也只追踪原Metering操作/因果/遥测,任何Consumer都不得用它们替代Snapshot五元组、直接前驱或Billing操作身份。
aggregateId 是 Contracts 固定 Namespace 按 (billingReservationId, runStepId, executionAttemptId, chargeDimensionKey) 确定性派生的 Settlement Dimension ID;aggregateRevision == currentSettlementInputStateVersion。Revision 1 只允许 state=root;后续 Revision 只允许 state=supersedes,且其中 Ref/Schema Version/Digest Algorithm Version/Digest/State Version 五元组必须完整指向同一维度、同一经济身份的 Current 直接前驱,predecessor.stateVersion + 1 == aggregateRevision。Payload、同事务 Snapshot 正文和提交后的 Current Pointer必须逐项等值;同一 Snapshot 事务恰好产生一个同 eventId + eventEnvelopeDigestAlgorithmVersion + eventEnvelopeSha256 的 Event,并冻结适用 Delivery Set完整四元组及 billing_settlement_catch_up Mandatory Delivery。Snapshot、Pointer、Event、Digest、Definition或Delivery Row任一失败,整笔 Metering事务回滚。
Billing Consumer只能把该Event解释为“一个完整Settlement Input版本需要被追平”:它不能据此写Ledger、构造Transition或宣称已结算。Consumer按Event自带身份和完整Snapshot五元组单调推进SettlementInputWakeupWatermark。Reservation尚未成功Finalization且Fence不在Case中时,它必须在同一Inbox事务推进Admission时已创建的BillingFinalizationFence、冻结新的Trigger Snapshot并产生/合并Finalization Work;Fence处于reconciliation_required时只单调推进Watermark并返回绑定Finalization Fence Revision、Case/Generation/Expected Resolution Revision的held_for_resolution Receipt,严格不创建Work、不退出Case,只由后续同代Current Resolution Applied新Trigger一次性吸收这些Watermark。成功Finalization后则重新打开对应BillingSettlementCatchUpFence并insert-or-compare内容寻址的BillingSettlementCatchUpWork@1。Catch-up Receipt必须冻结Fence Revision、Work完整四元组/Generation与确定性billingCatchUpValidationOperationId,并与同事务Fence Active Work逐项相等。若Catch-up Worker从readSettlementInput或Current Validation得到确定性冲突,它必须持久化内容寻址SettlementInputConflictFact@1与BillingSettlementCatchUpWorkAttemptBlocked@1完整四元组,把Fence置为coordinationState=blocked_on_owner_conflict,保留同一Work/Operation/Generation及activeWork.state=pending并释放Lease;不得写Finished、把Fence推进终态、创建/引用Operations Case或伪造held_for_resolution。修复后同Work重试;本地CAS竞争本身只重基线或retryable,不伪造Owner Conflict Fact。若极端乱序令Finalization Fence尚不可绑定,只能先耐久保存Inbox Watermark并返回retryable,不得写成功Receipt或Ack后永久停在awaiting_finalization。Ack只证明Watermark与所选Fence/Work/持有结果、版本化Consumer Receipt已经原子提交,不证明Snapshot已被Billing消费。真正的Current线性化仍必须使用readSettlementInput与validateSettlementInputCurrent(purpose=billing_catch_up_fence),其中billingCatchUpValidationOperationId由purpose + BillingCatchUp Basis带算法摘要 + 待验证Snapshot五元组确定性派生;该Receipt不能替代post_finalization_transition Receipt或驱动Ledger。上述Owner-local Conflict Fact首期没有Canonical Event与core_operations Mandatory Delivery,只能用于Owner诊断和不完整best-effort告警。
Contracts Golden、Producer/Repository、Dispatcher/Billing Inbox与跨进程测试必须拒绝缺失/裁剪五元组、未知Schema/算法、同Revision异Digest、第二Root、跳号/分叉/错前驱、跨Tenant/Account/Reservation/Run/Step/Attempt/Dimension/经济身份重绑、弱changed=true Payload和同Snapshot重复Event。恢复测试至少覆盖Event先于/晚于Finalization、Ack后Consumer或Dispatcher崩溃、较旧合法Revision的already_observed、Receipt与新Snapshot竞态、S2→S3合并追平,以及S1 Receipt → S2 Event先到且已Ack → Finalization按S1提交 → 此后没有S3仍由早到Watermark与Finalization自建Work最终追平S2。
结算与对账经济边界事件
Operations 不能从 Wallet 当前状态、Ledger 表、Case 文案或 Observation 反推经济边界。Contracts 固定下列五个 Tenant-aware Canonical Event,Payload 都是严格、低敏、未知字段拒绝的 DTO:
| Event Type | Payload Contract | 唯一 Producer |
|---|---|---|
billing.finalization.committed@1 | BillingFinalizationCommitted@1 | Billing |
billing.settlement-transition.committed@1 | BillingSettlementTransitionCommitted@1 | Billing |
billing.late-settlement-exposure.case-requested@1 | LateSettlementExposureCaseRequested@1 | Billing |
billing.case-resolution.applied@1 | BillingCaseResolutionApplied@1 | Billing |
billing.late-settlement-command-result.committed@1 | LateSettlementCommandResultCommitted@1 | Billing |
五类事件都强制非空 correlationId + operationId + causationId,Tenant/Workspace/Project 与原 Run/Reservation 一致。它们不共用 Dimension State Revision 冒充 Event Aggregate Revision:aggregateId 固定为当次不可变 Owner Fact/Result Ref,aggregateRevision=1;领域先后关系由 Payload 内 State/Exposure Revision 与 Envelope causationId 表达。这避免同一 Billing 事务中 Transition、Exposure/Case Request、Command Result 与 Applied Fact 争用同一 Aggregate Revision。自动 Finalization/Transition/Case Request 的 actorPrincipalId 继承 Run 不可变 Actor,不用 Billing Workload 冒充业务主体;Late Command Result 使用已授权 Workforce Actor,Applied 事件按其 Source 继承 Run 或 Workforce Actor。producer 始终是已认证 Billing Workload。
经济价值种类复用下列严格联合:
SettlementEconomicIdentity@1 =
{ kind=credits }
| { kind=entitlement; entitlementKey; unit }
| { kind=money; currency }billing.finalization.committed@1
Billing 成功终局使用新的 Billing-owned 内容寻址提交事实,而不是把“Decision 已允许”当成 Ledger 已提交:
BillingFinalizationCommitted@1 = {
billingFinalizationCommittedRef / billingFinalizationCommittedSchemaVersion
billingFinalizationCommittedDigestAlgorithmVersion = jcs-sha256-v1
billingFinalizationCommittedDigest
billingFinalizationOperationId
billingFinalizationDecisionRef / billingFinalizationDecisionSchemaVersion
billingFinalizationDecisionDigestAlgorithmVersion / billingFinalizationDecisionDigest
billingFinalizationDecisionInputDigestAlgorithmVersion / billingFinalizationDecisionInputDigest
finalizationInputManifestRef / finalizationInputManifestSchemaVersion
finalizationInputManifestDigestAlgorithmVersion / finalizationInputManifestDigest
finalizationValidationBundleRef / finalizationValidationBundleSchemaVersion
finalizationValidationBundleDigestAlgorithmVersion / finalizationValidationBundleDigest
billingReservationId / billingAccountId / runId
billingFinalizationFenceId / billingFinalizationFenceRevision
closureSourceEventId / closureSourceEventType / closureSourceEventSchemaVersion
closureSourceEventEnvelopeDigestAlgorithmVersion / closureSourceEventEnvelopeDigest
closureConsumerReceiptRef / closureConsumerReceiptSchemaVersion
closureConsumerReceiptDigestAlgorithmVersion / closureConsumerReceiptDigest
billingPolicyRevisionId / evidenceFinalizationRuleVersion
settlementEconomicIdentity = SettlementEconomicIdentity@1
status = settle_and_release_remainder | release_all
dimensionCommitmentCount
dimensionCommitments[] = sorted {
runStepId / executionAttemptId / chargeDimensionKey
settlementInputConsumptionRef / settlementInputConsumptionSchemaVersion
settlementInputConsumptionDigestAlgorithmVersion / settlementInputConsumptionDigest
resultingDimensionStateRevision
}
ledgerEffectCount
ledgerEffects[] = sorted {
runStepId / executionAttemptId / chargeDimensionKey
ledgerEffectRef / ledgerEffectSchemaVersion
ledgerEffectDigestAlgorithmVersion / ledgerEffectDigest
}
releaseEffect =
{ state=none }
| { state=applied; releaseEffectRef / releaseEffectSchemaVersion;
releaseEffectDigestAlgorithmVersion / releaseEffectDigest }
resultingReservationStatus = settled | released
resultingReservationRevision
committedAt
}Committed Digest 覆盖除 Repository-owned Ref/Time 和摘要自身外的完整 Candidate;Envelope operationId必须等于Payload billingFinalizationOperationId。Count 与数组长度相等,Dimension 按 (runStepId, executionAttemptId, chargeDimensionKey)、Ledger Effect 再加 Ref 排序且不重复。Decision 的 Ref/Schema Version/Digest Algorithm Version/Digest 完整四元组以及 inputDigestAlgorithmVersion + inputDigest 都必须逐项映射 Owner Decision。成功Fact/Event还必须冻结提交时BillingFinalizationFence Revision、已由Billing Consumer耐久应用的Closure Source Event ID/Type/Schema/Envelope摘要及BillingFinalizationCoordinatorConsumerReceipt@1完整四元组,并与Fence Closure Watermark、Trigger、Execution Owner Read、Input Manifest和Execution Validation Receipt逐项相等;Admission首代Work在该Closure Receipt应用前只能产生closure_event_not_applied阻塞结果,禁止Owner Read、Input Manifest、Decision或Ledger成功路径。dimensionCommitments[] 必须与 Finalization Input Manifest 的全部 (runStepId, executionAttemptId, chargeDimensionKey) 一一等值,每项 Consumption 是该 Finalization 原子提交的对应输入消费且 State Revision 与 Billing Repository提交后值一致;ledgerEffects[]同样按完整Step/Attempt/Dimension与实际Ledger精确对应。两组runStepId必须与Owner Fact、Input Manifest、Consumption、Dimension State及Ledger/Event逐项相等,禁止通过Attempt反查Step。Ledger/Release集合必须与同事务实际 Effect精确等值,不能漏项、夹带或只让 Count自洽。status=release_all 当且仅当 Decision 为 release_all、所有目标净额为零、无 Ledger Effect、Reservation变为 released且余量 Release符合 Decision;status=settle_and_release_remainder 当且仅当 Decision同值、Reservation变为 settled、Ledger Effect精确实现 Manifest目标净额并按剩余额产生 none/applied Release。任一双向 Refinement或集合等值失败都拒绝 Event并回滚账务事务。Committed Fact 自身四元组才是“Closure Mandatory Event已应用,且Decision、Ledger、Release、Reservation终态与全Dimension State已在同一Billing事务提交”的 Owner 证明。causationId 指向原 wallet.reserved@2.0 Event ID。blocked | reconciliation_required 不得伪造 Committed Event。Contracts/Repository负测必须拒绝Closure Event/Receipt或Fence Revision缺项/错摘要/未应用、任一数组缺Step、错Step、旧排序键或跨Step重绑。
billing.settlement-transition.committed@1
BillingSettlementTransitionCommitted@1 = {
billingSettlementTransitionRef / billingSettlementTransitionSchemaVersion
billingSettlementTransitionDigestAlgorithmVersion / billingSettlementTransitionDigest
billingTransitionOperationId
billingTransitionValidationOperationId
billingReservationId / billingAccountId / runId / runStepId / executionAttemptId / chargeDimensionKey
settlementEconomicIdentity = SettlementEconomicIdentity@1
fromStateRevision / toStateRevision
currentSettlementInputSnapshotRef / currentSettlementInputSnapshotSchemaVersion
currentSettlementInputSnapshotDigestAlgorithmVersion / currentSettlementInputSnapshotDigest
currentSettlementInputStateVersion
settlementInputLineage[] = ordered non-empty {
settlementInputSnapshotRef / settlementInputSnapshotSchemaVersion
settlementInputSnapshotDigestAlgorithmVersion / settlementInputSnapshotDigest
settlementInputStateVersion
}
settlementInputConsumptionRef / settlementInputConsumptionSchemaVersion
settlementInputConsumptionDigestAlgorithmVersion / settlementInputConsumptionDigest
ledgerEffect =
{ state=none }
| { state=applied; ledgerEffectRef / ledgerEffectSchemaVersion;
ledgerEffectDigestAlgorithmVersion / ledgerEffectDigest }
caseAction =
{ kind=none }
| { kind=request_open; caseGeneration; caseRequestOperationId; reconciliationCaseRef;
reconciliationCaseIdentityReservationRef / reconciliationCaseIdentityReservationSchemaVersion;
reconciliationCaseIdentityReservationDigestAlgorithmVersion / reconciliationCaseIdentityReservationDigest;
lateSettlementExposureRef / lateSettlementExposureSchemaVersion;
lateSettlementExposureDigestAlgorithmVersion / lateSettlementExposureDigest / exposureRevision }
| { kind=resolve; caseGeneration; caseRequestOperationId; resolutionOperationId; reconciliationCaseRef;
reconciliationCaseIdentityReservationRef / reconciliationCaseIdentityReservationSchemaVersion;
reconciliationCaseIdentityReservationDigestAlgorithmVersion / reconciliationCaseIdentityReservationDigest;
resolvedExposureRef / resolvedExposureSchemaVersion;
resolvedExposureDigestAlgorithmVersion / resolvedExposureDigest / resolvedExposureRevision }
}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五元组,服务端与调用方逐项比较;禁止Admin使用、跨Event复用或枚举同Run其他Snapshot。
Envelope operationId必须等于Payload的逻辑billingTransitionOperationId。Payload只是已提交BillingSettlementTransition@1的低敏不变量投影,所有四元组、经济身份、State Revision、Input Consumption、Ledger与Case Action必须与Owner Fact逐项相等。runStepId是该Dimension的自包含经济身份,必须与Transition、Consumption、每个Settlement Input、Exposure、Applied Fact和Owner Read Expected Identity逐项相等;禁止从Attempt或相邻Event反查Step。settlementInputLineage[]必须与Owner Fact同构、非空并按State Version严格递增:第一项的前驱五元组等于Owner Fact的fromLastEvaluatedInput,每个后项直接指向数组前项,末项完整五元组等于Payload的Current Settlement Input;Consumer用每项完整身份调用readSettlementInput读取精确历史或Current正文并重算,不能要求中间项仍是Current、跳项或从相邻Event补值。Owner Read的conflicting分支只返回内容寻址SettlementInputConflictFact@1完整四元组,不返回Case Ref;Transition/Catch-up Worker据此阻塞当前Work,禁止把读冲突直接伪装成Late-settlement或Finalization Case。只有完成全部Owner验证后形成的正式Billing Transition/Finalization Decision,才可分别按已定义Dimension/Run级Identity协议请求Operations Case。billingTransitionValidationOperationId必须等于 Contracts 固定 Namespace 对 (billingTransitionOperationId, billingReservationId, runStepId, executionAttemptId, chargeDimensionKey, fromStateRevision, Owner Fact内Basis完整摘要) 的确定性派生值,并与Owner Fact、Consumption和Metering Receipt回显值相等;它不是Envelope Operation,也不能替换逻辑Operation。caseAction=resolve 时,resolutionOperationId 必须由 Contracts 固定 Namespace 按 (billingTransitionOperationId, caseGeneration, predecessor Exposure完整四元组/Revision) 确定性派生,并与Resolved Exposure、同事务Applied Fact逐项相等;调用方随机值或错误前驱派生值都拒绝。首个Dimension Transition的causationId指向该Reservation的billing.finalization.committed@1。后续Transition必须指向同Dimension直接前一个推进State Revision的边界Event:billing.settlement-transition.committed@1或billing.late-settlement-command-result.committed@1严格二选一,并要求前驱Payload toStateRevision == 当前 fromStateRevision;不能因Late Command插入而继续回指更早Transition,也不能按到达顺序或时间猜前驱。并发前驱使旧Receipt对应的CAS失败时,Writer必须按新From State/Basis派生新的Validation Operation,旧Receipt不得进入Event;already_evaluated是零副作用响应,不得发出新Event。
billing.late-settlement-exposure.case-requested@1
LateSettlementExposureCaseRequested@1 = {
caseGeneration / caseRequestOperationId / reconciliationCaseRef
reconciliationCaseIdentityReservationRef / reconciliationCaseIdentityReservationSchemaVersion
reconciliationCaseIdentityReservationDigestAlgorithmVersion / reconciliationCaseIdentityReservationDigest
billingSettlementTransitionRef / billingSettlementTransitionSchemaVersion
billingSettlementTransitionDigestAlgorithmVersion / billingSettlementTransitionDigest
billingTransitionOperationId / fromStateRevision / toStateRevision
lateSettlementExposureRef / lateSettlementExposureSchemaVersion
lateSettlementExposureDigestAlgorithmVersion / lateSettlementExposureDigest / exposureRevision
billingReservationId / billingAccountId / runId / runStepId / executionAttemptId / chargeDimensionKey
pricingSnapshotId / billingPolicyRevisionId
settlementEconomicIdentity = SettlementEconomicIdentity@1
exposureState = open
}aggregateId=lateSettlementExposureRef、aggregateRevision=1,operationId=caseRequestOperationId,causationId 精确指向同一 Billing 事务预分配的 billing.settlement-transition.committed@1 Event ID。Event 必须同时携带预分配 Case Ref、Case Identity Reservation 完整四元组、Originating Transition完整四元组及其 billingTransitionOperationId + fromStateRevision + toStateRevision、完整Account/Run/Step/Attempt/Dimension身份,以及 Current Open Exposure 四元组/Revision,不能只发裸 Case/Exposure Ref。Consumer必须只凭这些自包含字段构造 readBillingSettlementTransition,即使被因果引用的Transition Event尚未到达也能验证Owner Fact;Case Request自己的Envelope operationId=caseRequestOperationId不能冒充Transition Operation,也不得从Attempt或相邻Event补Step/其他参数。Exposure 后续替换仍使用同一 Case Ref,但以新 Exposure Ref/Revision 和新事件表达单调 Source 更新。
billing.case-resolution.applied@1
BillingCaseResolutionApplied@1 = {
billingCaseResolutionAppliedRef / billingCaseResolutionAppliedSchemaVersion
billingCaseResolutionAppliedDigestAlgorithmVersion / billingCaseResolutionAppliedDigest
billingCaseResolutionAppliedIdempotencyKey
resolutionOperationId / reconciliationCaseRef / caseGeneration
reconciliationCaseIdentityReservationRef / reconciliationCaseIdentityReservationSchemaVersion
reconciliationCaseIdentityReservationDigestAlgorithmVersion / reconciliationCaseIdentityReservationDigest
billingReservationId / billingAccountId / runId / runStepId / executionAttemptId / chargeDimensionKey
settlementEconomicIdentity = SettlementEconomicIdentity@1
resolvedExposureRef / resolvedExposureSchemaVersion
resolvedExposureDigestAlgorithmVersion / resolvedExposureDigest / resolvedExposureRevision
resolutionSource =
{ kind=settlement_transition;
billingSettlementTransitionRef / billingSettlementTransitionSchemaVersion;
billingSettlementTransitionDigestAlgorithmVersion / billingSettlementTransitionDigest }
| { kind=late_settlement_command;
lateSettlementCommandResultRef / lateSettlementCommandResultSchemaVersion;
lateSettlementCommandResultDigestAlgorithmVersion / lateSettlementCommandResultDigest }
ledgerEffect =
{ state=none }
| { state=applied; ledgerEffectRef / ledgerEffectSchemaVersion;
ledgerEffectDigestAlgorithmVersion / ledgerEffectDigest;
settlementEconomicIdentity=SettlementEconomicIdentity@1; amountDecimal }
resultingSettlementState = {
toStateRevision
settledInput = BillingSettlementInputPointer@1
}
recordedAt
}aggregateId=billingCaseResolutionAppliedRef、aggregateRevision=1,operationId=resolutionOperationId。causationId 必须指向与 resolutionSource 同一 Owner Fact 的 billing.settlement-transition.committed@1 或 billing.late-settlement-command-result.committed@1 Event ID。Payload 的 Case Identity Reservation、Resolved Exposure、Source、Ledger、经济身份与 State Revision 必须与 BillingCaseResolutionApplied@1 Owner Fact 精确相等。Owner Read还必须验证Source专属Exposure前驱与Operation:settlement_transition首次解决只允许同代Current Open→Resolved,连续零/负更正允许同Generation、同Reservation的Current Resolved→Resolved直接后继,且resolutionOperationId必须等于由Transition Operation、Generation和直接前驱Exposure确定性派生的值;late_settlement_command只允许其授权Current Open→Resolved,并固定resolutionOperationId=lateSettlementOperationId。任何跳过直接前驱、反复回指旧Open、跨Generation/Reservation、随机Operation或以Late Command解决已Resolved Exposure都拒绝。
billing.late-settlement-command-result.committed@1
LateSettlementCommandResultCommitted@1 = {
lateSettlementCommandResultRef / lateSettlementCommandResultSchemaVersion
lateSettlementCommandResultDigestAlgorithmVersion / lateSettlementCommandResultDigest
lateSettlementOperationId / status=applied
reconciliationCaseRef / caseGeneration
reconciliationCaseIdentityReservationRef / reconciliationCaseIdentityReservationSchemaVersion
reconciliationCaseIdentityReservationDigestAlgorithmVersion / reconciliationCaseIdentityReservationDigest
billingReservationId / billingAccountId / runId / runStepId / executionAttemptId / chargeDimensionKey
pricingSnapshotId / billingPolicyRevisionId / fundingPolicyRevisionId
settlementEconomicIdentity = SettlementEconomicIdentity@1
authorizedOpenExposureRef / authorizedOpenExposureSchemaVersion
authorizedOpenExposureDigestAlgorithmVersion / authorizedOpenExposureDigest / authorizedExposureRevision
resolvedExposureRef / resolvedExposureSchemaVersion
resolvedExposureDigestAlgorithmVersion / resolvedExposureDigest / resolvedExposureRevision
ledgerEffectRef / ledgerEffectSchemaVersion
ledgerEffectDigestAlgorithmVersion / ledgerEffectDigest
billingCaseResolutionAppliedRef / billingCaseResolutionAppliedSchemaVersion
billingCaseResolutionAppliedDigestAlgorithmVersion / billingCaseResolutionAppliedDigest
fromStateRevision / toStateRevision
}aggregateId=lateSettlementCommandResultRef、aggregateRevision=1,operationId=lateSettlementOperationId,actorPrincipalId 必须等于 Result 中已授权 Workforce Actor。causationId 指向授权 Open Exposure Revision 最新的 billing.late-settlement-exposure.case-requested@1 Event。Payload 必须与 Result 的 Case Identity Reservation逐项相等,并携带同事务产生的 BillingCaseResolutionApplied@1 完整四元组;Consumer 使用 Result 四元组、Case/Identity Reservation 与 Resolved Exposure 构造受限 readLateSettlementCommandResult,再使用 Applied Fact四元组读取解决事实,不能依赖相邻 Event 补字段。Event 不复制 Grant Token、JTI、Approval/JIT 正文或金额。
Transition、Case Requested、Applied与Late Result四个Payload都强制携带并逐项继承同一billingAccountId + billingReservationId + runId + runStepId + executionAttemptId + chargeDimensionKey;对应Owner Fact、Exposure、Case Identity Reservation、Consumption、Settlement Input和定向Read Expected Identity必须相等。任何缺Step、跨Step重绑或尝试用Attempt→Step映射补值都按严格Schema/身份冲突拒绝。
原子 Append、因果与 Case 乱序收敛
Billing 必须在领域事务开始前生成所需 Event ID,以便将精确 causationId 冻结到后续事件。每个成功边界在同一 Billing PostgreSQL 事务原子提交 Owner Fact/Result、Ledger/Release/Reservation 或 Dimension State、必要的 Exposure/Applied Fact、Audit、Canonical Event、完整 Event Digest、冻结 Delivery Set 四元组与精确 Mandatory Delivery Rows。Finalization 提交必须包含 Finalization Committed Event;每个已应用 Transition 必须包含 Transition Event,request_open 同事务再包含 Case Requested Event,resolve 同事务再包含 Case Resolution Applied Event;Late Command 成功事务同时包含 Result Committed 与 Case Resolution Applied Event。任一 Event、Outbox、Delivery Row 或 Audit 失败使整个账务事务回滚,不得先改 Ledger 再补事件。
Operations 是 ReconciliationCaseIdentityReservation@1 与 Case 的唯一 Owner。身份预留只分配稳定 reconciliationCaseRef,严格 Reservation还包含 caseGeneration 与 predecessorCase = root | supersedes{previousCaseGeneration, previousReconciliationCaseRef, previous Identity Reservation完整四元组};摘要覆盖除Repository-owned Reservation Ref/Time和摘要自身外的全部字段。唯一 Source/Generation键与caseRequestOperationId确定性派生输入统一固定为 (sourceOwner=billing, sourceKind=late_settlement, billingReservationId, billingAccountId, runId, runStepId, executionAttemptId, chargeDimensionKey, caseGeneration);调用方不能覆盖,Operations必须用相同Contracts Namespace重算。第一代必须为1/root,后继必须恰好前代+1且完整引用直接前代。同Generation/Operation重放返回原Reservation/Case Ref,同Generation异候选冲突。预留不创建可见Case、告警或审批。受限 readReconciliationCaseIdentityReservationBySource 只接受精确 Billing Case Identity Workload Audience/Scope,并要求完整Source Key、确定性 Operation、Generation、Predecessor 和返回Reservation完整四元组逐项相等;它不提供列表、模糊查询或裸Ref读取。若预留已提交而Billing在保存响应或Transition前崩溃,新Worker必须先以同一Source/Generation/Predecessor调用该Read或重放reserve,严格验证并采用既有Reservation,禁止生成第二个Case Ref或让孤儿预留永久封锁Generation。Case Requested Consumer先按四元组验证本地不可变Reservation、Generation与Predecessor,再按 (caseGeneration, reconciliationCaseRef, exposureRevision) 单调幂等创建/更新;旧代Event/Ack不能覆盖新代或迁移旧Approval/JIT/Grant。
Case Create/Ack 的恢复只允许使用 Operations Owner 的版本化定向读取,不能从Case列表、Timeline或当前告警猜测:
readReconciliationCaseByReservedIdentity(
reconciliationCaseIdentityReservationRef / reconciliationCaseIdentityReservationSchemaVersion,
reconciliationCaseIdentityReservationDigestAlgorithmVersion / reconciliationCaseIdentityReservationDigest,
expected reconciliationCaseRef, billingReservationId, billingAccountId, runId, runStepId, executionAttemptId,
chargeDimensionKey, caseGeneration, caseRequestOperationId
)
-> { result=not_created }
| { result=found; reconciliationCaseRef; caseRevision;
sourceExposureRef / sourceExposureSchemaVersion;
sourceExposureDigestAlgorithmVersion / sourceExposureDigest / sourceExposureRevision;
caseState }
| { result=conflicting; conflictRef }该Read只向精确Billing Case Recovery Workload Audience/Scope开放。服务端先按Reservation完整四元组读取不可变Owner记录并重算摘要,再逐项验证Case Ref、Source/Generation/Operation、Billing Reservation、Attempt/Dimension与Repository绑定;found中的Exposure完整四元组/Revision必须等于Case当前冻结Source,调用方也必须严格解码和复验。not_created只表示该预留尚未形成业务Case,不表示预留不存在或可重新分配;不存在/错摘要/跨身份不得伪装成not_created。若Create已提交而Ack丢失,Billing以原Reservation和期望身份读取并采用found,不得另建同代Case。
一个或多个 Resolution Applied 先于可见 Case 创建到达时,Operations 必须先验证 Case Identity Reservation、Generation/Predecessor、每个Exposure的直接前驱链和 readBillingCaseResolutionApplied,再把每个 (caseGeneration, reconciliationCaseRef, resolvedExposureRevision, Applied Fact完整四元组) 以唯一约束耐久写入 pending_resolution。后到同代Case Requested不得短暂或最终创建Open,而是在同一Operations事务选择最高连续、已验证的Resolved Revision,直接建立绑定对应Applied Fact的Resolved Case。Case已存在时只在同Generation内按Exposure Source Revision处理 applied | superseded_noop | already_applied | conflict;已Resolved Case收到同Generation/Reservation、更高且无缺口的Resolved直接后继时必须追加Timeline并原子推进Source Binding,状态保持Resolved。中间Owner Fact暂不可读或链暂不完整时必须返回retryable,不写Outcome、不写Applied/Event Receipt、不提交任何投影副作用,让原Event Delivery保持non-terminal并按Lease重试;不得把该瞬态保存为deferred或把合法Resolved→Resolved当作冲突。更高Generation使用新Reservation/Case Ref,旧代本地评论、指派、审批与授权都不得迁移。晚到旧代/旧Revision Create/Ack只能返回 already_resolved | superseded_noop,绝不能覆盖新代或把Resolved恢复为Open。只有完整验证后的pending_resolution | applied | superseded_noop | already_applied | conflict投影结果才与Applied/Event Receipt在同一Operations事务提交;失败只重试事件消费,不回滚已提交Billing账务。
Contracts 必须为五个 Payload、所有 Owner Fact/Result 四元组、Settlement Economic Identity、Case Generation/Predecessor/Exposure联合、排序 Count 与 Causation Refinement 发布 Golden JSON/Canonical Bytes/Digest。负向测试覆盖 Owner Ref/Schema正确但Digest错误、摘要算法未知、Envelope Operation与Finalization/Transition Payload Operation不等、逻辑/Validation Operation混用或Validation Operation派生错误、Lineage为空/漏项/乱序/分叉/前驱五元组或末项Current错配、Case Requested缺失/篡改Transition Operation或from/to Revision、跨Reservation/Account/Run/Case/Generation/Exposure/Dimension/经济身份重绑、第一代非1/root、后继跳代或错Predecessor、错Aggregate/Revision/Actor/Causation、Count/排序/分支错误、同Owner Fact两个Event、事务提交后缺Outbox/Delivery、Case Requested先于Transition Event仍可独立读取Owner、F→T(open)→Late Result→T(correction)乱序仍按State Revision直接前驱收敛、缺失/错误前驱Event或前驱toRevision不等当前fromRevision、Billing停在S1而Metering已追加S2→S3时按完整[S2,S3]一次catch-up且中间S2只做精确历史Owner Read、resolve-before-create、多条Resolution先于Create后选择最高连续Revision、Open O1→Resolved R2→Resolved R3合法直接链及R3回指旧Open分叉、Transition随机/错误前驱派生Resolution Operation、Late Command的Resolution Operation不等于Command Operation、Create/Applied Ack丢失、Case Owner Read的not_created/found/conflicting三分支、旧代Create晚到、更高Exposure Revision或Generation超越与投影崩溃重放;恢复测试还必须覆盖reserve提交后Billing保存响应或预提交Transition前崩溃,由新Worker在相同或更新Snapshot下按Source/Generation/Predecessor读回并采用同一Reservation,证明只有一个Case Ref且该Generation仍可继续,Create提交后Ack丢失由Billing凭Reservation完整四元组读回同一Case,Metering Receipt提交后Billing预提交前崩溃时新Worker重建同一Resolution Operation/Basis/Validation Operation,以及F→旧Basis/Receipt→并发前驱提交→CAS失败→重读新From State/Basis→新Validation Operation→成功Event且旧Receipt不被采用。
Operational Observation Submission 与 Accepted Envelope
准入前拒绝、每次 HTTP 接收和幂等重放无法由 Run Domain Event 表达。API Edge 使用独立的内部 OperationalObservationSubmission@1.0 向 Core Operations Intake 提交低敏记录。Submission Body 只包含生产者能够声明的内容:
observationId
schemaVersion
sourceClaimedObservedAt?
requestId
errorId?
traceId?
correlationId?
operationId?
runId?
surface
apiVersion?
operation?
claimedTenantKind? / claimedTenantId?
claimedWorkspaceId?
claimedServiceAccountId?
claimedDeveloperCredentialId?
payload =
RequestAttempt@1 {
requestKind / outcome / submissionState / normalizedCode?
httpStatusClass? / idempotencyDisposition?
attributes # 固定白名单子 Schema
}
| 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?
executionAttemptId?
executionAnchor? =
{ kind=text_invocation; gatewayInvocationId }
| { kind=media_task; gatewayTaskId }
providerAttemptId?
capability / configRevision
normalizedStatus / stateVersion / submissionCertainty
attemptRouteBindingRef? / attemptRouteBindingSchemaVersion?
attemptRouteBindingDigestAlgorithmVersion? / attemptRouteBindingDigest?
gatewayRouteSnapshotRef? / gatewayRouteSnapshotSchemaVersion?
gatewayRouteSnapshotDigestAlgorithmVersion? / gatewayRouteSnapshotDigest?
usageEvidenceAvailability =
{ 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 =
{ 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) }payload.type 是严格判别字段;每个分支拒绝其他分支字段和未知字段。ErrorOccurrence@1 必须有 errorId,其他分支若没有错误不得伪造它;normalizedCode 是三份契约统一的规范字段名,不再并存 normalizedErrorCode 或裸 code。Request、Error 与 Gateway 的 Attributes/Parameters 都由 Contracts 逐字段规定类型、长度、编码和数据分类,不能退化成任意 Map。Producer 必须提交实际 Adapter 使用、已由 Contracts 注册的 errorNormalizationPolicyVersion;Intake 依据受信 Workload/Deployment 支持矩阵校验该版本,并校验 normalizedCode 属于该 Policy 的注册值,未知或退役组合必须拒绝。Producer 不提交 service、errorFingerprint 或 errorFingerprintVersion;可信 Service 只能由 Core 根据已认证 Workload Registry 注入的 source.service 推导,不能接受同名 Claim。出现派生指纹或可信来源字段都按未知字段拒绝。
ErrorOccurrence@1.gatewayExecution 是严格嵌套 Refinement,不能把这些字段散落到外层或从相邻 GatewayDiagnostic 补齐。非 Gateway 错误必须省略它;Gateway 在 Binding 创建前且证明未发生 Provider Side Effect 的拒绝可以省略它,并只保留实际已知的 Run/Step/Attempt。Binding 已创建或可能发生外部 Side Effect 时,外层 runId 与 Error Payload 的 runStepId + executionAttemptId + gatewayExecution 全部必填;Binding 与 Route 的 Ref/Schema Version/Digest Algorithm Version/Digest 必须各自完整成组,executionAnchor 严格二选一,不能同时/缺失 Invocation 与 Task。Core Intake以Binding完整四元组、期望Attempt/Deployment和Audience调用readAttemptRouteBinding,从响应取得并验证Manifest/Slot/Route/Anchor后,再把同一Error Observation自包含的Run/Step/Attempt、Gateway Deployment、Binding、Route Snapshot和Anchor逐项交叉验证;Bootstrap Read不得反过来要求调用方预知Slot/Manifest/Anchor。不能查询“当前版本”、从相邻记录补字段或只比较裸Ref。错配进入Quarantine,不建立Attempt关联。Contracts/Intake测试必须覆盖同一Run多Attempt、错Binding/Route、任一四元组字段缺项、合法Ref/Schema搭配错误Digest、未知算法、两种Anchor同时或均未给、三件套缺项以及未知字段,并验证JSON/数据库round-trip不丢失任一四元组字段。
普通 GatewayDiagnostic@1 只携带低敏 OceanWay Deployment、受控执行身份与两个当前 GatewayEvidenceAvailabilitySnapshot@1 摘要,不携带 Gateway 私有 backendModelId、Supply/Channel、Provider Model 或 Provider Credential Version;Pool 字段唯一规范名是 gatewayPool=text|media,旧 gatewayPoolId 必须按未知字段拒绝。resultSummary必填并使用上述封闭低敏联合:reported要求正数且不超过Contracts上限的Count和至少一个按注册顺序排序去重、与Capability相容的规范模态,no_output固定零Count/空集合,not_available禁止全部输出字段;Pre-binding固定not_available。它禁止文本、Prompt、URL、文件名、MIME、尺寸、Hash、Asset ID、Provider原始状态和自由Map,不能驱动Run/Output成功。Usage/Cost 两类 Availability 均必填,且所有状态都必须携带 Snapshot Ref/Schema Version/Digest Algorithm Version/Digest/State Version/Kind/固定 Dimension/严格 availabilityIdentity/前驱联合/Reason;supersedesAvailability.state=supersedes 必须携带直接前驱 Snapshot 的 Ref/Schema Version/Digest Algorithm Version/Digest/State Version 五元组并逐项相等,root 分支禁止全部前驱字段。attempt_bound 的 Binding 和 Route 以及 available 的 Evidence Head 都必须携带完整 Ref/Schema Version/Digest Algorithm Version/Digest;当后续 Owner Read 展开 Evidence 时,其 supersedesEvidence 也只能是 root 或直接前驱 Evidence 完整四元组,不能接受裸 Ref/Schema。其他 Availability 状态禁止全部 Evidence 四元字段。缺任一 Snapshot 或前驱字段、半联合、声明版本/摘要/State Version 与 Read Contract 不等、第二 Dimension、错类 Ref、非法状态和未知字段都拒绝。
身份只能二选一,且两者都要求外层非空 gatewayAttemptDispatchSlotId。attempt_bound 另外要求外层 executionAttemptId + Binding完整四元组 + Route Snapshot完整四元组 + executionAnchor 全部存在并与两类 Snapshot 和 readAttemptRouteBinding 返回值相等,禁止只给子集;其 Slot 必须等于已验证 AttemptRouteBinding 冻结的 Slot,不能靠相同 Attempt 猜测。pre_binding_no_execution 仅用于 Binding 创建前且可证明未发生 Provider Side Effect 的拒绝,外层 gatewayAttemptDispatchSlotId + gatewayPreBindingRequestId 必须与两类 Snapshot 逐项相等,必须省略 Attempt/Binding/Route/Anchor,两类 Snapshot 只能是 stateVersion=1 + root + not_reported|unavailable 且禁止 Evidence 四元组,resultSummary固定not_available。Pre-binding Snapshot 不能进入 Metering、Eligibility 或 Finalization;不能证明无 Side Effect 时必须使用 Attempt-bound。Anchor 严格二选一并匹配 Pool:Text 只能是 text_invocation,Media 只能是 media_task;both/neither/wrong-pool/错 ID 都是非法输入。Contracts/Intake/Projector 负向测试必须覆盖 Slot 缺失、外层与任一 Pre-binding Snapshot Slot/Request ID 不等、Attempt-bound Slot 与 Binding 不等、同 Attempt 换 Slot、Binding/Route/Evidence 任一四元字段裁剪或摘要错配、Result Summary缺失/分支混合/零负超限Count/重复未知错Capability模态/内容或URL注入,以及 JSON/数据库 round-trip 丢失 Slot/摘要/Result Summary。需要展开私有路由时,必须使用 Case/JIT 授权的 Gateway Read Contract,结果只作为受限 technicalDetailRef,不得复制进普通 Accepted Observation、Operations Projection 或 Query DTO。
Observation对readAttemptRouteBinding的Bootstrap验证使用两个互斥Audience:Intake在Accepted Envelope形成前必须用operations_observation_intake并把Token Scope绑定Observation ID/Schema、Canonical Submission完整摘要、Tenant/Run(若有)、Attempt/Deployment、Binding四元组及可选Operation;Projector只能用operations_observation_projection并改为绑定Accepted Envelope完整摘要。两者只返回低敏不可变Binding正文,禁止摘要类型互换、跨Observation复用、读取Availability/Evidence/Receipt或Route私有正文;错误Audience/Scope不得泄露Binding存在性。
Error Fingerprint 规范
Error Fingerprint 的唯一规范 Owner 是 oceanway-contracts,唯一 Writer 是 Core Operations Projector。Projector 只从通过 Schema 和关联校验的字段计算,不信任 Producer、日志或 Gateway 上报的现成指纹。每条投影结果必须同时保存:
errorNormalizationPolicyVersion
errorFingerprintVersion
errorFingerprinterrorNormalizationPolicyVersion 固定 Producer Adapter 在发生时实际使用的 Raw/Provider Error 到 normalizedCode 映射,并由 Intake 按 Workload/Deployment 支持矩阵验证;Projector 不使用“当前最新版本”回填历史记录。errorFingerprintVersion="1" 固定以下七项输入,全部来自同一条 Accepted Error Observation。所有字符串先按 Contracts 字段规则校验并转为 UTF-8 NFC,枚举使用注册表中的 canonical 小写值:
{
"errorFingerprintVersion": "1",
"errorNormalizationPolicyVersion": "...",
"layer": "...",
"normalizedCode": "...",
"phase": "...",
"service": "...",
"surface": "..."
}service 取 Core 注入的 source.service,surface 取外层必填字段,其余取已校验 Error Payload。Projector 不通过相邻 Gateway Diagnostic、Telemetry 或当前 Deployment 状态补字段。对象使用 RFC 8785 JSON Canonicalization Scheme 生成 UTF-8 字节,计算 SHA-256,小写十六进制并加 efp1_ 前缀。Request/Trace/Operation/Run/User/Tenant ID、Deployment/Config、Capability、位置/行号、时间戳、Prompt、自由文本、动态 URL、Provider Task ID 和 Secret 永不进入 v1 输入;未来增加维度必须升级 Fingerprint Version。
固定测试向量:
{"errorFingerprintVersion":"1","errorNormalizationPolicyVersion":"2026-09-01","layer":"gateway_protocol","normalizedCode":"provider.timeout","phase":"submit","service":"media-gateway","surface":"api"}结果必须是 efp1_2443900e8feff84eb23160caf67f5b6c68e941678519db6fb9db40a231c7309b。Contracts 必须同时测试 Key 顺序变化结果不变、Unicode 非 NFC 输入先规范化、未知 Policy/Code 被拒绝、Workload/Deployment 与 Policy 不匹配被 Intake 拒绝,以及 Producer 携带任何 Fingerprint 派生字段被拒绝。每个 Producer Adapter 还必须对该 Policy 的固定 Raw Classification 测试集通过一致性测试;原始 Provider 正文仍不进入 Observation。算法或输入字段变化必须发布新的 errorFingerprintVersion;Normalization 映射变化升级 errorNormalizationPolicyVersion;旧分组保留原版本,不能后台静默重算后混入同一 Group。
Core 验证 Workload、Schema 与资源限制后,生成并不可变保存 AcceptedOperationalObservationEnvelope@1.0。它完整包含原 Submission,并只由 Core 增加:
acceptedAt
source.workloadPrincipalId
source.service
source.environment
source.region?
source.deploymentId?
source.instanceId?
observationDigestAlgorithmVersion = jcs-sha256-v1
submissionPayloadSha256Operations低敏投影使用下列封闭摘要,不把attributes或sanitizedParameters正文变成新的自由Map:
AcceptedObservationSummary@1 = {
observationId / schemaVersion / requestId
errorId? / traceId? / correlationId? / operationId? / runId?
surface / apiVersion? / operation?
claimedTenantKind? / claimedTenantId? / claimedWorkspaceId?
claimedServiceAccountId? / claimedDeveloperCredentialId?
sourceClaimedObservedAt? / acceptedAt
source = { workloadPrincipalId; service; environment; region?; deploymentId?; instanceId? }
observationDigestAlgorithmVersion
submissionPayloadSha256 / acceptedEnvelopeSha256
payloadSummary =
{ kind=request_attempt;
requestKind; outcome; submissionState; normalizedCode?;
httpStatusClass?; idempotencyDisposition?;
attributesSchemaVersion; attributesDigestAlgorithmVersion; attributesDigest }
| { kind=error_occurrence;
layer; phase; normalizedCode; errorNormalizationPolicyVersion; severity;
certainty; retryDisposition; compensationDisposition;
messageTemplateKey;
sanitizedParametersSchemaVersion;
sanitizedParametersDigestAlgorithmVersion; sanitizedParametersDigest;
causeErrorId?; technicalDetailRef?; runStepId?; executionAttemptId?;
errorFingerprintVersion; errorFingerprint }
| { kind=gateway_diagnostic;
gatewayPool; gatewayDeploymentId; providerAttemptId?;
capability; configRevision; normalizedStatus; gatewayStateVersion;
submissionCertainty; resultSummary=GatewayDiagnosticResultSummary@1 }
}Projector只从同一Accepted Envelope和Registry/Receipt中的acceptedEnvelopeSha256逐字段形成该摘要。Request Attributes与Error Sanitized Parameters按各自已注册子Schema计算Schema Version + jcs-sha256-v1 Digest,正文不进入普通Search;Gateway分支完整保留强制Result Summary。三个Payload分支严格互斥,未知字段、Common或分支字段错映射、子摘要篡改、Result Summary缺失以及JSON/数据库round-trip丢字段均拒绝。需要返回Observation详情时,Summary、关联证据、Error Gateway Execution或Gateway Dispatch/Availability使用Operations Read Contract定义的整体available|redacted权限单元,不能只裁一个Sibling泄漏Payload Kind或对象存在性。
Core 另维护 append-only 的低敏 observation_id_registry,其最小记录为:
observationId
firstSourceWorkloadPrincipalId
observationDigestAlgorithmVersion
submissionPayloadSha256
originalAcceptedAt
acceptedEnvelopeSha256Payload 清理状态使用另一份 append-only、每个 observationId 最多一条的 observation_payload_purge_markers(observationId, purgedAt, retentionAuditRef),不更新 Registry。
Registry 是 Observation ID 命名空间与幂等绑定的完整性事实,不是可查询 Observation 正文。它在环境的 Observation ID 命名空间生命周期内保留,或迁入 Intake 每次接受前都必须检查的不可变完整性归档;普通 Retention 不得删除、更新或绕过它。
Submission 与 Accepted Envelope 是两个不同 Schema;生产者不能提交或覆盖 acceptedAt、source.*、observationDigestAlgorithmVersion、submissionPayloadSha256,Intake 对这些保留字段一律拒绝,不能采用“忽略后继续”的歧义行为。Producer 提供的可选 sourceClaimedObservedAt 必须原样作为 Claim 保存,不能被 Intake 重写成 occurredAt 或 Core 时间。
Intake 对 Canonical Submission 计算 submissionPayloadSha256。只有当前 Workload JWT Subject 与首次保存的 source.workloadPrincipalId 相同、且 Digest Algorithm Version、observationId + submissionPayloadSha256 均相同时,才是幂等重放;首次接受时生成唯一的 acceptedAt/source.* 并对最终信封计算存储侧 acceptedEnvelopeSha256。Source Payload 尚存时,同一 ID、同一受信 Source、同一算法版本与同一 Submission 摘要返回 accepted_existing、原 Accepted Envelope 与原 acceptedAt;已有 Purge Marker 时返回 accepted_existing_purged,只包含 observationId + originalAcceptedAt + observationDigestAlgorithmVersion,不能重构信封、重新投影或复活 Source。两者都不能重新生成信封。同一 ID 来自不同 Workload、算法版本错配,或同一 Source 下摘要不同,都必须作为完整性冲突拒绝、追加低敏 Audit/告警,且不能返回首次 Source 的内容。Projector Receipt 比较同一算法版本下的 acceptedEnvelopeSha256,不能把两个摘要字段或不同算法版本混用。
Observation Digest 规范
两个 Observation 摘要统一由 oceanway-contracts 的 jcs-sha256-v1 实现生成,不能由 Producer、数据库 JSON 序列化器或各 Consumer 自行选择算法:
- 原始 JSON 必须按 UTF-8 严格解码,并在构造普通对象前拒绝重复 Member Name;所有 Member Name 与字符串值转为 Unicode NFC,规范化后产生重名也拒绝。
- 规范化对象再通过对应的严格版本 Schema;未知字段拒绝。v1 的可选字段缺失时保持缺失,不注入默认值;除 Schema 明确声明 Nullable 的分支外,显式
null一律拒绝,因此“缺失”与null不能获得同一摘要。数组顺序保持不变,数值必须先符合 Contracts 的有限、范围与整数/十进制规则。 - 对验证后的精确 DTO 使用 RFC 8785 JSON Canonicalization Scheme,取得 UTF-8 Canonical Bytes,再计算 SHA-256,输出 64 位小写十六进制。
submissionPayloadSha256覆盖完整 Canonical Submission,不包含由 Core 注入的算法版本。acceptedEnvelopeSha256覆盖完整 Canonical Accepted Envelope,包含observationDigestAlgorithmVersion和submissionPayloadSha256;它自身只存在于 Registry/Receipt,不进入被摘要的 Envelope,避免递归。
共享 Canonicalization 固定向量以 NFD e\u0301 作为输入,NFC 与 RFC 8785 后的字节必须精确为 {"a":"é","z":[3,2,1]},SHA-256 必须为 35ca92c06e5e44b0729f57937d5aaf7476ccbc3b6202e41ac669ed3780815988。Contracts Release 还必须随包发布一份完整 Submission 与对应 Accepted Envelope 的 Golden JSON/Canonical Bytes/两个摘要;Node、Web、Core、Projector、Archive Replay 和 Rebuild 全部读取同一制品验证,不能复制测试常量后各自实现。Key 顺序变化摘要不变,缺失与非法 null、未知字段、规范化重名、Envelope 字段或算法版本篡改必须失败;算法或字段覆盖范围变化必须发布新的 Digest Algorithm Version 和 Observation Schema Version,旧记录始终按原版本重放,禁止后台静默重算。
RequestAttempt 种类
RequestAttempt@1.requestKind 首期至少区分:
request_received:Edge 接收到一次合法 Host 上的请求;不表示认证或准入成功。request_rejected:在提交 Core 前被拒绝,携带规范化低敏原因。admission_submitted:Edge 已向 Core 发起 Admission;不能单凭它推断事务提交。admission_accepted:Core 返回已存在或新建的reservedRun。idempotency_replayed:本次 Request ID 返回既有 Run。admission_outcome_unknown:Edge 无法确定 Core 是否提交,需要依赖稳定 Key 重试与后续事实查询。
错误另用 ErrorOccurrence@1,不把 Layer/Phase/Certainty/Fingerprint 等塞进 Request Attributes;Gateway 诊断使用 GatewayDiagnostic@1。三个分支共享外层稳定关联 ID,但各自严格 round-trip,任何分支错置字段、normalizedErrorCode/裸 code 旧名或未知参数都必须拒绝。
Observation 安全边界
-
未完成认证时,Claimed Tenant、Workspace、Service Account 和 Credential ID 可以为空;它们必须来自 Edge 已验证的请求上下文,不得从客户 Body 接受。即使如此,它们仍只是 Producer Claim,不是 Core 领域事实。
-
不发送 Raw Secret、Key Digest、Authorization、Cookie、Session、请求/响应 Body、Prompt、输入摘要、完整 IP、User-Agent 原文或堆栈。
-
RequestAttempt.attributes、ErrorOccurrence.sanitizedParameters与GatewayDiagnostic都只能使用 Contracts 明确的判别子 Schema、白名单和长度/分类约束;不能作为逃逸的任意 JSON 日志袋。 -
Intake 使用面向
oceanway-core的短期 Workload JWT 与operations-observation:writeScope;Core 从 JWT 确认来源 Workload,不能信任 Body 自报 Producer。 -
Intake 数据库 Role 不直接读取或写入 Source/Registry/Purge Marker,只能执行受控
accept_operational_observationPrimitive;Primitive 在唯一约束下原子 insert-or-compare,只有当前受信 Workload 与首次 Source、Digest Algorithm Version 和 Submission 摘要全部一致时才返回accepted_new | accepted_existing | accepted_existing_purged之一及允许字段,跨 Workload 或跨算法版本冲突不得返回旧内容。 -
Producer、Environment、Region、Deployment/Instance 由 Core 根据受信 Workload Registry 注入;Submission Body 出现
acceptedAt、source.*、observationDigestAlgorithmVersion或submissionPayloadSha256必须拒绝。acceptedAt是 Core 权威接收时间,sourceClaimedObservedAt只是来源声明;Observation 固定按(acceptedAt, observationId)排序,Summary 的first_accepted_observation_at/latest_accepted_observation_at、accepted health window、Retention/Archive 和 Freshness 只能使用acceptedAt,claimed time 只在 Timeline 展示。 -
claimedTenantKind、claimedTenantId、claimedWorkspaceId、claimedServiceAccountId、claimedDeveloperCredentialId、operationId与runId不能直接用于 Tenant 授权或operation_linked。Operations Projector 只有通过同一 Operation/Run 的 Canonical Domain Event,或 Core 签发且可验证的不可变 Admission Context 交叉验证后,才能把关联提升为domain_verified。关联证据使用严格联合: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 } }Domain Event 分支必须逐项复制已应用 Canonical 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 Claims、目标领域事实及 Tenant 分区逐项相等,并把完整联合原子写入投影;不能只存裸 Ref、只比 ID/Schema、从当前 Run/Session 补值或混合两分支。
producer_claimed必须且只能携带state=none,domain_verified必须且只能携带一个完整证据分支;未知 Schema/算法、摘要错配、缺项、两分支混合、同 Ref 异正文或错标 Tenant/Run 都隔离关联且不能污染领域时间线。Contracts/Repository/Query round-trip 与跨租户负向测试必须覆盖所有互斥、篡改和裁剪场景。 -
Contracts 与部署配置共同限制单条 Payload、批次数量、字段长度和白名单;Intake 按 Workload 执行配置化配额、背压和异常告警。资源保护不能靠在代码中散落固定次数或固定延时。
-
Telemetry/Intake 失败不阻塞客户请求,也不改变公共响应。首期没有 Edge 持久队列,因此 Observation 是 best-effort。
-
后续 Gateway Observation 对 Usage/Cost 只允许携带两类严格 Evidence Availability,并只在
available分支携带对应 Evidence Ref/Schema Version/Digest Algorithm Version/Digest完整四元组;不得嵌入或冒充规范MeterEvent/ProviderCostFact,也不得作为 Billing 结算命令。Gateway 只拥有不可变 Evidence,规范 Fact 由 Metering 唯一追加。 -
Core 接受后将 Envelope 以
source.workloadPrincipalId + observationDigestAlgorithmVersion + observationId + submissionPayloadSha256验证幂等,并以全局唯一observationId原子写入 append-only Source 与 ID Registry;跨 Workload 的同 ID 或算法版本错配在该命名空间内始终是冲突。数据库 Trigger 与 Column Grant 禁止修改已接受的 Source、Registry、Purge Marker、Envelope、算法版本、两个摘要和接受时间。Intake 不直接修改当前投影。 -
Projector 以
(consumerName, projectionName, projectionVersion, observationId)建立 Receipt,Receipt 保存observationDigestAlgorithmVersion + acceptedEnvelopeSha256,并与 Observation Projection Mutation 放在同一事务;其他 Consumer/Projection 的 Receipt 不能满足当前投影,相同 Accepted Envelope 重投不重复应用,算法版本或摘要冲突拒绝并告警。 -
Edge 没有 Durable Spool,也不发送可证明连续性的 Sequence。Core 只能报告已经接受的记录集合、接受时间范围和 Intake/Source 已知健康窗口;不能声称请求覆盖完整、计算丢失数量或检测 Observation Gap。
-
缺少 Observation 不能反证请求没有到达 Edge;已知健康窗口也只表示组件健康信号可用,不表示该窗口内每个请求都已有 Observation。
-
契约与 PostgreSQL 负向测试必须覆盖 Producer 错标 Tenant/Run、Claim 与 Domain Event 不一致、未验证 Claim 被 Tenant-scoped Grant 查询,以及验证证据撤销/缺失;未验证 Claim 只能由显式跨租户 Observation 调查 Scope 读取。时间负测还必须覆盖 claimed time 与接受顺序反转、claimed time 位于未来/Retention 边界两侧、相同
acceptedAt的稳定排序,以及operational_observation缺少acceptedAt、伪造occurredAt或把 claimed time 写入 Summary;这些输入均不得改变基于acceptedAt的结果。
Schema Registry 与兼容门禁
事件和 Observation 的运行时 Schema、TypeScript 类型与 JSON Schema 都由固定版本 oceanway-contracts 发布。消费仓只依赖精确 Release:
- Contracts 新版本先发布并通过 Package/Release 制品一致性验证。
- Core Producer、Dispatcher、Consumer 和 Query 使用同一受支持版本矩阵。
- API Edge 升级后才能发送新 Observation 版本。
- 未知事件类型、未知版本、严格 Schema 失败或完整性摘要冲突进入隔离,不得跳过后继续声称投影完整。
- 删除旧 Consumer 版本前,必须证明保留事件已全部可由当前 Projector 重建,或已按治理策略归档相应 Schema 与重放运行时。
任何版本门禁失败都不能通过“从日志补字段”“直接查询生产表”或“修改历史 Event Payload”绕过。