KAFKA · ASYNCAPI 3.1 · AT-LEAST-ONCE
События и правила consumer
AsyncAPI описывает фактически публикуемые event families и отдельно помеченные schema-only candidates. Local broker использует plaintext; production TLS, SASL, ACL, quotas и DR не заявлены.
- Граница выполнения
- LOCAL Kafka · plaintext development broker
- Canonical-derived AsyncAPI event catalog
- РЕАЛИЗОВАНО
Контракты и исходные файлыOpenAPI, схемы и примеры для скачивания.
OpenAPI, схемы и примеры для скачивания.
Фильтруемый reference генерируется из canonical AsyncAPI и связанных JSON Schemas. Каждая карточка отделяет объявленный контракт от runtime evidence и сохраняет обе версии события, если они делят один topic.
GENERATED · CANONICAL-DERIVED
AsyncAPI event catalog
6 event versions across 5 Kafka topics. Producer and consumer labels describe only operations declared in canonical AsyncAPI.
- Events with producer operation
- 4
- Kafka not produced
- 2
- Events with consumer operation
- 0
- Source SHA-256
99597d6bb4b0…
Immutable eligibility decision and audit receipt.
Canonical AsyncAPI declares a send operation. This documents the producer contract; it does not by itself prove a live or production publisher.
- Channel / topic
certarail.decision.v1channel: eligibilityDecisions- Version
- v1AsyncAPI document 1.3.1 · transport header 1
Producer
Send operation emitEligibilityDecision is declared in canonical AsyncAPI. This is a contract declaration, not runtime evidence.
emitEligibilityDecisionPublish committed eligibility decision evidence.
Consumer
No named consumer operation is declared in canonical AsyncAPI; the catalog does not infer one.
Payload schema
CertaRail eligibility decision event v1
urn:certarail:schema:event:certarail.eligibility.decision.v1
| Field | Requirement | Type / constraints | Description |
|---|---|---|---|
event_id | required | stringpattern ^evt_ · minLength 5 | No field description declared. |
event_type | required | stringconst "certarail.eligibility.decision.v1" | No field description declared. |
occurred_at | required | stringformat date-time | No field description declared. |
tenant_id | required | stringminLength 1 | No field description declared. |
result | required | object#/$defs/evaluationResult | No field description declared. |
Full payload schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "urn:certarail:schema:event:certarail.eligibility.decision.v1",
"title": "CertaRail eligibility decision event v1",
"description": "Immutable event envelope written to the PostgreSQL outbox in the same transaction as the decision and audit receipt.",
"type": "object",
"additionalProperties": false,
"required": [
"event_id",
"event_type",
"occurred_at",
"tenant_id",
"result"
],
"properties": {
"event_id": {
"type": "string",
"pattern": "^evt_",
"minLength": 5
},
"event_type": {
"const": "certarail.eligibility.decision.v1"
},
"occurred_at": {
"type": "string",
"format": "date-time"
},
"tenant_id": {
"type": "string",
"minLength": 1
},
"result": {
"$ref": "#/$defs/evaluationResult"
}
},
"$defs": {
"evaluationResult": {
"type": "object",
"additionalProperties": false,
"required": [
"decision",
"audit"
],
"properties": {
"decision": {
"$ref": "#/$defs/decision"
},
"audit": {
"$ref": "#/$defs/auditReceipt"
}
}
},
"decision": {
"type": "object",
"additionalProperties": false,
"required": [
"decision_id",
"request_id",
"tenant_id",
"outcome",
"enforceable",
"evaluated_at",
"operation",
"asset_code",
"input_digest",
"checks"
],
"properties": {
"decision_id": {
"type": "string",
"minLength": 1
},
"request_id": {
"type": "string",
"minLength": 1
},
"tenant_id": {
"type": "string",
"minLength": 1
},
"outcome": {
"enum": [
"ALLOW",
"DENY",
"REVIEW"
]
},
"enforceable": {
"type": "boolean"
},
"evaluated_at": {
"type": "string",
"format": "date-time"
},
"evaluator_version": {
"const": "certarail.eligibility.go.v1",
"description": "Immutable rule-engine semantics identity. Optional in v1 only for pre-migration committed replays; every new decision includes it."
},
"operation": {
"enum": [
"BUY",
"SELL",
"DEPOSIT",
"WITHDRAW",
"EXCHANGE"
]
},
"asset_code": {
"type": "string",
"minLength": 1
},
"policy_id": {
"type": "string",
"minLength": 1
},
"policy_version": {
"type": "string",
"minLength": 1
},
"policy_digest": {
"$ref": "#/$defs/digest"
},
"policy_mode": {
"enum": [
"SANDBOX",
"PRODUCTION"
]
},
"input_digest": {
"$ref": "#/$defs/digest"
},
"checks": {
"type": "array",
"items": {
"$ref": "#/$defs/check"
}
},
"obligations": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"check": {
"type": "object",
"additionalProperties": false,
"required": [
"id",
"status",
"summary"
],
"properties": {
"id": {
"type": "string",
"minLength": 1
},
"status": {
"enum": [
"PASS",
"FAIL",
"REVIEW"
]
},
"summary": {
"type": "string"
},
"rule_id": {
"type": "string",
"minLength": 1
},
"source_refs": {
"type": "array",
"items": {
"type": "string"
}
}
}
},
"auditReceipt": {
"type": "object",
"additionalProperties": false,
"required": [
"lane",
"sequence",
"recorded_at",
"event_hash"
],
"properties": {
"lane": {
"type": "integer",
"minimum": 0,
"maximum": 31
},
"sequence": {
"type": "integer",
"minimum": 1
},
"recorded_at": {
"type": "string",
"format": "date-time"
},
"evaluator_version": {
"const": "certarail.eligibility.go.v1",
"description": "Evaluator identity bound into the audit event hash. Optional in v1 only for pre-migration committed replays; every new audit event includes it."
},
"previous_hash": {
"$ref": "#/$defs/digest"
},
"event_hash": {
"$ref": "#/$defs/digest"
}
}
},
"digest": {
"type": "string",
"pattern": "^[0-9a-f]{64}$"
}
}
}Example
Schema-valid synthetic payload
Generated from the canonical JSON Schema for documentation. It is not a broker record, provider receipt, or runtime evidence.
{
"event_id": "evt_x",
"event_type": "certarail.eligibility.decision.v1",
"occurred_at": "2026-01-01T00:00:00Z",
"tenant_id": "tenant_example",
"result": {
"decision": {
"decision_id": "decision_example",
"request_id": "request_example",
"tenant_id": "tenant_example",
"outcome": "ALLOW",
"enforceable": false,
"evaluated_at": "2026-01-01T00:00:00Z",
"evaluator_version": "certarail.eligibility.go.v1",
"operation": "BUY",
"asset_code": "BTC",
"input_digest": "0000000000000000000000000000000000000000000000000000000000000000",
"checks": [
{
"id": "example",
"status": "PASS",
"summary": "example"
}
]
},
"audit": {
"lane": 0,
"sequence": 1,
"recorded_at": "2026-01-01T00:00:00Z",
"evaluator_version": "certarail.eligibility.go.v1",
"event_hash": "0000000000000000000000000000000000000000000000000000000000000000"
}
}
}Correlation fields
Identifiers and trace context
Identifier, reference, trace, and Kafka-key fields are listed from the canonical schemas and message bindings.
| Location | Field | Requirement | Meaning |
|---|---|---|---|
| payload | payload.event_id | required | Identifier declared without a field description. |
| payload | payload.tenant_id | required | Identifier declared without a field description. |
| payload | payload.result.decision.decision_id | required | Identifier declared without a field description. |
| payload | payload.result.decision.request_id | required | Identifier declared without a field description. |
| payload | payload.result.decision.tenant_id | required | Identifier declared without a field description. |
| payload | payload.result.decision.policy_id | optional | Identifier declared without a field description. |
| payload | payload.result.decision.checks.[].id | required | Identifier declared without a field description. |
| payload | payload.result.decision.checks.[].rule_id | optional | Identifier declared without a field description. |
| header | headers.certarail-event-id | required | Stable consumer deduplication identifier; equal to payload event_id. |
| header | headers.traceparent | optional | Optional W3C Trace Context for the Kafka publisher attempt. |
| header | headers.tracestate | optional | Optional W3C Trace Context tracestate value; omitted when empty. |
| kafka-key | message.key | declared | Pseudonymous ordering key computed as `subject_` plus the first 16 bytes of SHA-256(tenant_id || NUL || subject_ref), encoded as lowercase hexadecimal. It is not an authentication or tenancy boundary. |
Ordering semantics
declared
Ordering is guaranteed only for one Kafka key within certarail.decision.v1.
Scope: one Kafka key within certarail.decision.v1
Pseudonymous ordering key computed as `subject_` plus the first 16 bytes of SHA-256(tenant_id || NUL || subject_ref), encoded as lowercase hexadecimal. It is not an authentication or tenancy boundary.
Retry semantics
declared
at-least-once delivery is declared. Consumers must deduplicate by payload.event_id and tolerate redelivery.
Deduplication key: payload.event_id
This event records an observation and does not execute funds or asset movement.
Canonical AsyncAPI declares a send operation. This documents the producer contract; it does not by itself prove a live or production publisher.
- Channel / topic
certarail.movement.v1channel: assetMovements- Version
- v1AsyncAPI document 1.3.1 · transport header 1
Producer
Send operation emitAssetMovement is declared in canonical AsyncAPI. This is a contract declaration, not runtime evidence.
emitAssetMovementPublish committed asset movement observation evidence.
Consumer
No named consumer operation is declared in canonical AsyncAPI; the catalog does not infer one.
Payload schema
CertaRail Asset Movement Event v1
https://schemas.certarail.local/events/certarail.asset.movement.v1.schema.json
| Field | Requirement | Type / constraints | Description |
|---|---|---|---|
event_id | required | stringminLength 1 | No field description declared. |
event_type | required | stringconst "certarail.asset.movement.v1" | No field description declared. |
occurred_at | required | stringformat date-time | No field description declared. |
tenant_id | required | stringminLength 1 · maxLength 128 | No field description declared. |
movement | required | object | No field description declared. |
Full payload schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://schemas.certarail.local/events/certarail.asset.movement.v1.schema.json",
"title": "CertaRail Asset Movement Event v1",
"type": "object",
"additionalProperties": false,
"required": [
"event_id",
"event_type",
"occurred_at",
"tenant_id",
"movement"
],
"properties": {
"event_id": {
"type": "string",
"minLength": 1
},
"event_type": {
"const": "certarail.asset.movement.v1"
},
"occurred_at": {
"type": "string",
"format": "date-time"
},
"tenant_id": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"movement": {
"type": "object",
"additionalProperties": false,
"required": [
"movement_id",
"request_id",
"tenant_id",
"decision_id",
"kind",
"direction",
"asset_code",
"quantity",
"network",
"status",
"wallet_ref",
"source_system",
"source_event_ref",
"observed_at",
"recorded_at",
"evidence_digest"
],
"properties": {
"movement_id": {
"type": "string",
"minLength": 1
},
"request_id": {
"type": "string",
"minLength": 1
},
"tenant_id": {
"type": "string",
"minLength": 1
},
"decision_id": {
"type": "string",
"minLength": 1
},
"kind": {
"enum": [
"BUY_FILL",
"SELL_FILL",
"DEPOSIT",
"WITHDRAWAL",
"INTERNAL_TRANSFER",
"EXTERNAL_TRANSFER",
"SWAP_LEG",
"FEE",
"REFUND",
"REVERSAL",
"REWARD",
"AIRDROP",
"FORK",
"MINING",
"STAKING",
"CUSTODY_MOVE",
"ADJUSTMENT"
]
},
"direction": {
"enum": [
"CREDIT",
"DEBIT"
]
},
"asset_code": {
"type": "string",
"minLength": 1
},
"quantity": {
"type": "object",
"additionalProperties": false,
"required": [
"atomic_units",
"decimals"
],
"properties": {
"atomic_units": {
"type": "string",
"pattern": "^[1-9][0-9]{0,77}$"
},
"decimals": {
"type": "integer",
"minimum": 0,
"maximum": 38
}
}
},
"network": {
"type": "string",
"minLength": 1
},
"status": {
"enum": [
"OBSERVED",
"PENDING",
"CONFIRMED",
"FAILED",
"REVERSED"
]
},
"wallet_ref": {
"type": "string",
"minLength": 1
},
"counterparty_ref": {
"type": "string"
},
"venue_ref": {
"type": "string"
},
"blockchain_transaction_id": {
"type": "string"
},
"confirmations": {
"type": "integer",
"minimum": 0
},
"source_system": {
"type": "string",
"minLength": 1
},
"source_event_ref": {
"type": "string",
"minLength": 1
},
"kyt_assessment_ref": {
"type": "string"
},
"address_verification_ref": {
"type": "string"
},
"travel_rule_ref": {
"type": "string"
},
"observed_at": {
"type": "string",
"format": "date-time"
},
"recorded_at": {
"type": "string",
"format": "date-time"
},
"evidence_digest": {
"type": "string",
"pattern": "^[a-f0-9]{64}$"
}
}
}
}
}Example
Schema-valid synthetic payload
Generated from the canonical JSON Schema for documentation. It is not a broker record, provider receipt, or runtime evidence.
{
"event_id": "event_example",
"event_type": "certarail.asset.movement.v1",
"occurred_at": "2026-01-01T00:00:00Z",
"tenant_id": "tenant_example",
"movement": {
"movement_id": "movement_example",
"request_id": "request_example",
"tenant_id": "tenant_example",
"decision_id": "decision_example",
"kind": "BUY_FILL",
"direction": "CREDIT",
"asset_code": "BTC",
"quantity": {
"atomic_units": "1",
"decimals": 0
},
"network": "bitcoin",
"status": "OBSERVED",
"wallet_ref": "wallet_example",
"source_system": "example",
"source_event_ref": "source_event_example",
"observed_at": "2026-01-01T00:00:00Z",
"recorded_at": "2026-01-01T00:00:00Z",
"evidence_digest": "0000000000000000000000000000000000000000000000000000000000000000"
}
}Correlation fields
Identifiers and trace context
Identifier, reference, trace, and Kafka-key fields are listed from the canonical schemas and message bindings.
| Location | Field | Requirement | Meaning |
|---|---|---|---|
| payload | payload.event_id | required | Identifier declared without a field description. |
| payload | payload.tenant_id | required | Identifier declared without a field description. |
| payload | payload.movement.movement_id | required | Identifier declared without a field description. |
| payload | payload.movement.request_id | required | Identifier declared without a field description. |
| payload | payload.movement.tenant_id | required | Identifier declared without a field description. |
| payload | payload.movement.decision_id | required | Identifier declared without a field description. |
| payload | payload.movement.wallet_ref | required | Identifier declared without a field description. |
| payload | payload.movement.counterparty_ref | optional | Identifier declared without a field description. |
| payload | payload.movement.venue_ref | optional | Identifier declared without a field description. |
| payload | payload.movement.blockchain_transaction_id | optional | Identifier declared without a field description. |
| payload | payload.movement.source_event_ref | required | Identifier declared without a field description. |
| payload | payload.movement.kyt_assessment_ref | optional | Identifier declared without a field description. |
| payload | payload.movement.address_verification_ref | optional | Identifier declared without a field description. |
| payload | payload.movement.travel_rule_ref | optional | Identifier declared without a field description. |
| header | headers.certarail-event-id | required | Stable consumer deduplication identifier; equal to payload event_id. |
| header | headers.traceparent | optional | Optional W3C Trace Context for the Kafka publisher attempt. |
| header | headers.tracestate | optional | Optional W3C Trace Context tracestate value; omitted when empty. |
| kafka-key | message.key | declared | Pseudonymous ordering key computed as `movement_` plus the first 16 bytes of SHA-256(tenant_id || NUL || wallet_ref), encoded as lowercase hexadecimal. It is not an authentication or tenancy boundary. |
Ordering semantics
declared
Ordering is guaranteed only for one Kafka key within certarail.movement.v1.
Scope: one Kafka key within certarail.movement.v1
Pseudonymous ordering key computed as `movement_` plus the first 16 bytes of SHA-256(tenant_id || NUL || wallet_ref), encoded as lowercase hexadecimal. It is not an authentication or tenancy boundary.
Retry semantics
declared
at-least-once delivery is declared. Consumers must deduplicate by payload.event_id and tolerate redelivery.
Deduplication key: payload.event_id
Legacy records may omit or empty subject_ref and may contain BUY or SELL, or contain a three-letter quote outside the current v2 set. Existing orders have immutable event_contract=v1 and retain v1 for all later lifecycle facts. Historical v1 payloads may omit event_contract. No legacy order can be newly created. A legacy SELL can only be locally cancelled and cannot receive a fill. V1 represents only CertaRail sandbox state and is not live execution or a balance.
Canonical AsyncAPI declares a send operation. This documents the producer contract; it does not by itself prove a live or production publisher.
- Channel / topic
certarail.trade.v1channel: sandboxTradingOrders- Version
- v1AsyncAPI document 1.3.1 · transport header 1
Producer
Send operation emitSandboxTradingOrderFact is declared in canonical AsyncAPI. This is a contract declaration, not runtime evidence.
emitSandboxTradingOrderFactPublish a committed local non-monetary sandbox trading fact.
Consumer
No named consumer operation is declared in canonical AsyncAPI; the catalog does not infer one.
Payload schema
CertaRail legacy durable sandbox trading order event v1
urn:certarail:schema:event:certarail.trade.order.v1
| Field | Requirement | Type / constraints | Description |
|---|---|---|---|
event_id | required | stringpattern ^evt-trade_[0-9a-f]{32}$ | No field description declared. |
event_type | required | stringconst "certarail.trade.order.v1" | No field description declared. |
action | required | stringORDER_OPENED | SYNTHETIC_FILL_RECORDED | ORDER_CANCELLED | No field description declared. |
occurred_at | required | stringformat date-time | No field description declared. |
tenant_id | required | stringminLength 1 · maxLength 128 | No field description declared. |
order_id | required | stringpattern ^ord_[0-9a-f]{32}$ | No field description declared. |
decision_id | required | stringminLength 1 · maxLength 160 | No field description declared. |
order | required | object#/$defs/order | No field description declared. |
fill | optional | object#/$defs/fill | No field description declared. |
evidence_digest | required | string#/$defs/digest | No field description declared. |
safety | required | object#/$defs/safety | No field description declared. |
external_calls | required | numberconst 0 | No field description declared. |
Full payload schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "urn:certarail:schema:event:certarail.trade.order.v1",
"title": "CertaRail legacy durable sandbox trading order event v1",
"description": "Frozen legacy PostgreSQL transactional-outbox contract emitted before subject binding, the BUY-only gate, the current RUB/USD/EUR quote set and explicit order provenance. V1 may omit subject_ref, may contain BUY or SELL, and accepts any uppercase three-letter quote. Existing rows are assigned immutable event_contract v1 and retain v1 for subsequent lifecycle facts; historical v1 payloads may omit event_contract. A legacy SELL can only be locally cancelled and cannot receive a fill. V1 never authorizes a new legacy order or asserts a live venue call, market price, settlement, custody balance, ledger posting, asset movement, or legal permission to trade.",
"type": "object",
"additionalProperties": false,
"required": [
"event_id",
"event_type",
"action",
"occurred_at",
"tenant_id",
"order_id",
"decision_id",
"order",
"evidence_digest",
"safety",
"external_calls"
],
"properties": {
"event_id": {
"type": "string",
"pattern": "^evt-trade_[0-9a-f]{32}$"
},
"event_type": {
"const": "certarail.trade.order.v1"
},
"action": {
"enum": [
"ORDER_OPENED",
"SYNTHETIC_FILL_RECORDED",
"ORDER_CANCELLED"
]
},
"occurred_at": {
"type": "string",
"format": "date-time"
},
"tenant_id": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"order_id": {
"type": "string",
"pattern": "^ord_[0-9a-f]{32}$"
},
"decision_id": {
"type": "string",
"minLength": 1,
"maxLength": 160
},
"order": {
"$ref": "#/$defs/order"
},
"fill": {
"$ref": "#/$defs/fill"
},
"evidence_digest": {
"$ref": "#/$defs/digest"
},
"safety": {
"$ref": "#/$defs/safety"
},
"external_calls": {
"const": 0
}
},
"allOf": [
{
"if": {
"properties": {
"action": {
"const": "SYNTHETIC_FILL_RECORDED"
}
},
"required": [
"action"
]
},
"then": {
"properties": {
"fill": {
"$ref": "#/$defs/fill"
}
},
"required": [
"fill"
]
},
"else": {
"properties": {
"fill": false
}
}
}
],
"$defs": {
"digest": {
"type": "string",
"pattern": "^[0-9a-f]{64}$"
},
"safety": {
"type": "object",
"additionalProperties": false,
"required": [
"environment",
"production_enforceable",
"monetary"
],
"properties": {
"environment": {
"const": "sandbox"
},
"production_enforceable": {
"const": false
},
"monetary": {
"const": false
}
}
},
"instrument": {
"type": "object",
"additionalProperties": false,
"required": [
"base_asset",
"quote_currency"
],
"properties": {
"base_asset": {
"type": "string",
"minLength": 1,
"maxLength": 64
},
"quote_currency": {
"type": "string",
"pattern": "^[A-Z]{3}$"
}
}
},
"positiveQuantity": {
"type": "object",
"additionalProperties": false,
"required": [
"atomic_units",
"decimals"
],
"properties": {
"atomic_units": {
"type": "string",
"pattern": "^[1-9][0-9]{0,77}$"
},
"decimals": {
"type": "integer",
"minimum": 0,
"maximum": 38
}
}
},
"nonNegativeQuantity": {
"type": "object",
"additionalProperties": false,
"required": [
"atomic_units",
"decimals"
],
"properties": {
"atomic_units": {
"type": "string",
"pattern": "^(0|[1-9][0-9]{0,77})$"
},
"decimals": {
"type": "integer",
"minimum": 0,
"maximum": 38
}
}
},
"decimal": {
"type": "object",
"additionalProperties": false,
"required": [
"value",
"scale"
],
"properties": {
"value": {
"type": "string",
"pattern": "^[1-9][0-9]{0,77}$"
},
"scale": {
"type": "integer",
"minimum": 0,
"maximum": 38
}
}
},
"money": {
"type": "object",
"additionalProperties": false,
"required": [
"currency",
"minor_units"
],
"properties": {
"currency": {
"type": "string",
"pattern": "^[A-Z]{3}$"
},
"minor_units": {
"type": "integer",
"minimum": 1,
"maximum": 9223372036854776000
}
}
},
"order": {
"type": "object",
"additionalProperties": false,
"required": [
"order_id",
"request_id",
"tenant_id",
"decision_id",
"portfolio_ref",
"approval_ref",
"connection_id",
"account_ref",
"client_order_id",
"instrument",
"side",
"order_type",
"base_quantity",
"time_in_force",
"max_notional",
"command_id",
"provider_order_ref",
"status",
"submit_disposition",
"venue_state",
"filled_quantity",
"version",
"created_at",
"updated_at",
"evidence_digest",
"safety",
"external_calls"
],
"properties": {
"order_id": {
"type": "string",
"pattern": "^ord_[0-9a-f]{32}$"
},
"request_id": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"tenant_id": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"decision_id": {
"type": "string",
"minLength": 1,
"maxLength": 160
},
"event_contract": {
"const": "certarail.trade.order.v1",
"description": "Immutable order event-schema provenance. Optional because historical v1 payloads predate the field."
},
"subject_ref": {
"type": "string",
"minLength": 0,
"maxLength": 256,
"description": "Optional legacy field. It may be absent or empty because v1 predates mandatory eligibility subject binding."
},
"portfolio_ref": {
"type": "string",
"minLength": 1,
"maxLength": 256
},
"approval_ref": {
"type": "string",
"minLength": 1,
"maxLength": 256,
"description": "Correlation only; not evidence that CertaRail performed maker-checker approval."
},
"connection_id": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"account_ref": {
"type": "string",
"minLength": 1,
"maxLength": 256
},
"client_order_id": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"instrument": {
"$ref": "#/$defs/instrument"
},
"side": {
"enum": [
"BUY",
"SELL"
],
"description": "Legacy v1 facts may contain SELL. This does not make SELL executable in the current runtime."
},
"order_type": {
"const": "MARKET"
},
"base_quantity": {
"$ref": "#/$defs/positiveQuantity"
},
"limit_price": {
"$ref": "#/$defs/decimal"
},
"time_in_force": {
"const": "IOC"
},
"max_notional": {
"$ref": "#/$defs/money"
},
"command_id": {
"type": "string",
"pattern": "^cmd_[0-9a-f]{32}$"
},
"provider_order_ref": {
"type": "string",
"pattern": "^syn-order_[0-9a-f]{32}$",
"description": "Deterministic local reference, never a provider receipt."
},
"status": {
"enum": [
"OPEN",
"PARTIALLY_FILLED",
"FILLED",
"CANCELLED"
]
},
"submit_disposition": {
"const": "ACKNOWLEDGED",
"description": "Local PostgreSQL acknowledgement only."
},
"venue_state": {
"enum": [
"OPEN",
"PARTIALLY_FILLED",
"FILLED",
"CANCELLED"
]
},
"filled_quantity": {
"$ref": "#/$defs/nonNegativeQuantity"
},
"version": {
"type": "integer",
"minimum": 1
},
"created_at": {
"type": "string",
"format": "date-time"
},
"updated_at": {
"type": "string",
"format": "date-time"
},
"evidence_digest": {
"$ref": "#/$defs/digest"
},
"safety": {
"$ref": "#/$defs/safety"
},
"external_calls": {
"const": 0
}
}
},
"fill": {
"type": "object",
"additionalProperties": false,
"required": [
"fill_id",
"order_id",
"tenant_id",
"fill_ref",
"quantity",
"price",
"observed_at",
"recorded_at",
"evidence_digest",
"safety"
],
"properties": {
"fill_id": {
"type": "string",
"pattern": "^fill_[0-9a-f]{32}$"
},
"order_id": {
"type": "string",
"pattern": "^ord_[0-9a-f]{32}$"
},
"tenant_id": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"fill_ref": {
"type": "string",
"minLength": 1,
"maxLength": 256,
"description": "Synthetic deduplication reference, not a live venue fill ID."
},
"quantity": {
"$ref": "#/$defs/positiveQuantity"
},
"price": {
"allOf": [
{
"$ref": "#/$defs/decimal"
}
],
"description": "Caller-supplied local fixture, not real-time market data."
},
"observed_at": {
"type": "string",
"format": "date-time"
},
"recorded_at": {
"type": "string",
"format": "date-time"
},
"evidence_digest": {
"allOf": [
{
"$ref": "#/$defs/digest"
}
],
"description": "Legacy lowercase SHA-256 evidence digest. Consumers must not assume v2 fill-preimage semantics for already published v1 payloads."
},
"evidence_format": {
"enum": [
"SYNTHETIC_FILL_EVIDENCE_V1",
"LEGACY_COMMAND_PREIMAGE_V1"
],
"description": "Optional compatibility tag. Historical v1 payloads may omit it; LEGACY_COMMAND_PREIMAGE_V1 identifies a repaired row whose digest is bound to the original immutable command preimage."
},
"safety": {
"$ref": "#/$defs/safety"
}
}
}
}
}Example
Schema-valid synthetic payload
Generated from the canonical JSON Schema for documentation. It is not a broker record, provider receipt, or runtime evidence.
{
"event_id": "evt-trade_00000000000000000000000000000000",
"event_type": "certarail.trade.order.v1",
"action": "ORDER_OPENED",
"occurred_at": "2026-01-01T00:00:00Z",
"tenant_id": "tenant_example",
"order_id": "ord_00000000000000000000000000000000",
"decision_id": "decision_example",
"order": {
"order_id": "ord_00000000000000000000000000000000",
"request_id": "request_example",
"tenant_id": "tenant_example",
"decision_id": "decision_example",
"event_contract": "certarail.trade.order.v1",
"portfolio_ref": "portfolio_example",
"approval_ref": "approval_example",
"connection_id": "connection_example",
"account_ref": "account_example",
"client_order_id": "client_order_example",
"instrument": {
"base_asset": "BTC",
"quote_currency": "RUB"
},
"side": "BUY",
"order_type": "MARKET",
"base_quantity": {
"atomic_units": "1",
"decimals": 0
},
"time_in_force": "IOC",
"max_notional": {
"currency": "RUB",
"minor_units": 1
},
"command_id": "cmd_00000000000000000000000000000000",
"provider_order_ref": "syn-order_00000000000000000000000000000000",
"status": "OPEN",
"submit_disposition": "ACKNOWLEDGED",
"venue_state": "OPEN",
"filled_quantity": {
"atomic_units": "0",
"decimals": 0
},
"version": 1,
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-01T00:00:00Z",
"evidence_digest": "0000000000000000000000000000000000000000000000000000000000000000",
"safety": {
"environment": "sandbox",
"production_enforceable": false,
"monetary": false
},
"external_calls": 0
},
"evidence_digest": "0000000000000000000000000000000000000000000000000000000000000000",
"safety": {
"environment": "sandbox",
"production_enforceable": false,
"monetary": false
},
"external_calls": 0
}Correlation fields
Identifiers and trace context
Identifier, reference, trace, and Kafka-key fields are listed from the canonical schemas and message bindings.
| Location | Field | Requirement | Meaning |
|---|---|---|---|
| payload | payload.event_id | required | Identifier declared without a field description. |
| payload | payload.tenant_id | required | Identifier declared without a field description. |
| payload | payload.order_id | required | Identifier declared without a field description. |
| payload | payload.decision_id | required | Identifier declared without a field description. |
| payload | payload.order.order_id | required | Identifier declared without a field description. |
| payload | payload.order.request_id | required | Identifier declared without a field description. |
| payload | payload.order.tenant_id | required | Identifier declared without a field description. |
| payload | payload.order.decision_id | required | Identifier declared without a field description. |
| payload | payload.order.subject_ref | optional | Optional legacy field. It may be absent or empty because v1 predates mandatory eligibility subject binding. |
| payload | payload.order.portfolio_ref | required | Identifier declared without a field description. |
| payload | payload.order.approval_ref | required | Correlation only; not evidence that CertaRail performed maker-checker approval. |
| payload | payload.order.connection_id | required | Identifier declared without a field description. |
| payload | payload.order.account_ref | required | Identifier declared without a field description. |
| payload | payload.order.client_order_id | required | Identifier declared without a field description. |
| payload | payload.order.command_id | required | Identifier declared without a field description. |
| payload | payload.order.provider_order_ref | required | Deterministic local reference, never a provider receipt. |
| payload | payload.fill.fill_id | optional | Identifier declared without a field description. |
| payload | payload.fill.order_id | optional | Identifier declared without a field description. |
| payload | payload.fill.tenant_id | optional | Identifier declared without a field description. |
| payload | payload.fill.fill_ref | optional | Synthetic deduplication reference, not a live venue fill ID. |
| header | headers.certarail-event-id | required | Stable consumer deduplication identifier; equal to payload event_id. |
| header | headers.traceparent | optional | Optional W3C Trace Context for the Kafka publisher attempt. |
| header | headers.tracestate | optional | Optional W3C Trace Context tracestate value; omitted when empty. |
| kafka-key | message.key | declared | Pseudonymous ordering key computed as `order_` plus the first 16 bytes of SHA-256(tenant_id || NUL || order_id), encoded as lowercase hexadecimal. It is not authentication, tenancy, provider identity, or proof of execution. |
Ordering semantics
declared
Ordering is guaranteed only for one order key within certarail.trade.v1.
Scope: one order key within certarail.trade.v1
Pseudonymous ordering key computed as `order_` plus the first 16 bytes of SHA-256(tenant_id || NUL || order_id), encoded as lowercase hexadecimal. It is not authentication, tenancy, provider identity, or proof of execution.
Retry semantics
declared
at-least-once delivery is declared. Consumers must deduplicate by payload.event_id and tolerate redelivery.
Deduplication key: payload.event_id
Scope: one event_id across v1 and v2
Current v2 requires non-empty eligibility-bound subject_ref and side=BUY. New orders receive immutable event_contract=v2, which routes the full lifecycle to this schema. The event represents only CertaRail sandbox state. It is not a live venue execution, provider receipt, market price, bank/custody balance, asset movement, settlement record, or accounting posting.
Canonical AsyncAPI declares a send operation. This documents the producer contract; it does not by itself prove a live or production publisher.
- Channel / topic
certarail.trade.v1channel: sandboxTradingOrders- Version
- v2AsyncAPI document 1.3.1 · transport header 1
Producer
Send operation emitSandboxTradingOrderFact is declared in canonical AsyncAPI. This is a contract declaration, not runtime evidence.
emitSandboxTradingOrderFactPublish a committed local non-monetary sandbox trading fact.
Consumer
No named consumer operation is declared in canonical AsyncAPI; the catalog does not infer one.
Payload schema
CertaRail durable sandbox trading order event v2
urn:certarail:schema:event:certarail.trade.order.v2
| Field | Requirement | Type / constraints | Description |
|---|---|---|---|
event_id | required | stringpattern ^evt-trade_[0-9a-f]{32}$ | No field description declared. |
event_type | required | stringconst "certarail.trade.order.v2" | No field description declared. |
action | required | stringORDER_OPENED | SYNTHETIC_FILL_RECORDED | ORDER_CANCELLED | No field description declared. |
occurred_at | required | stringformat date-time | No field description declared. |
tenant_id | required | stringminLength 1 · maxLength 128 | No field description declared. |
order_id | required | stringpattern ^ord_[0-9a-f]{32}$ | No field description declared. |
decision_id | required | stringminLength 1 · maxLength 160 | No field description declared. |
order | required | object#/$defs/order | No field description declared. |
fill | optional | object#/$defs/fill | No field description declared. |
evidence_digest | required | string#/$defs/digest | No field description declared. |
safety | required | object#/$defs/safety | No field description declared. |
external_calls | required | numberconst 0 | No field description declared. |
Full payload schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "urn:certarail:schema:event:certarail.trade.order.v2",
"title": "CertaRail durable sandbox trading order event v2",
"description": "Current PostgreSQL transactional-outbox fact from the BUY-only non-monetary sandbox. V2 requires the eligibility-bound opaque subject_ref and never asserts a live venue call, market price, settlement, custody balance, ledger posting, asset movement, or legal permission to trade.",
"type": "object",
"additionalProperties": false,
"required": [
"event_id",
"event_type",
"action",
"occurred_at",
"tenant_id",
"order_id",
"decision_id",
"order",
"evidence_digest",
"safety",
"external_calls"
],
"properties": {
"event_id": {
"type": "string",
"pattern": "^evt-trade_[0-9a-f]{32}$"
},
"event_type": {
"const": "certarail.trade.order.v2"
},
"action": {
"enum": [
"ORDER_OPENED",
"SYNTHETIC_FILL_RECORDED",
"ORDER_CANCELLED"
]
},
"occurred_at": {
"type": "string",
"format": "date-time"
},
"tenant_id": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"order_id": {
"type": "string",
"pattern": "^ord_[0-9a-f]{32}$"
},
"decision_id": {
"type": "string",
"minLength": 1,
"maxLength": 160
},
"order": {
"$ref": "#/$defs/order"
},
"fill": {
"$ref": "#/$defs/fill"
},
"evidence_digest": {
"$ref": "#/$defs/digest"
},
"safety": {
"$ref": "#/$defs/safety"
},
"external_calls": {
"const": 0
}
},
"allOf": [
{
"if": {
"properties": {
"action": {
"const": "SYNTHETIC_FILL_RECORDED"
}
},
"required": [
"action"
]
},
"then": {
"properties": {
"fill": {
"$ref": "#/$defs/fill"
}
},
"required": [
"fill"
]
},
"else": {
"properties": {
"fill": false
}
}
}
],
"$defs": {
"digest": {
"type": "string",
"pattern": "^[0-9a-f]{64}$"
},
"safety": {
"type": "object",
"additionalProperties": false,
"required": [
"environment",
"production_enforceable",
"monetary"
],
"properties": {
"environment": {
"const": "sandbox"
},
"production_enforceable": {
"const": false
},
"monetary": {
"const": false
}
}
},
"instrument": {
"type": "object",
"additionalProperties": false,
"required": [
"base_asset",
"quote_currency"
],
"properties": {
"base_asset": {
"type": "string",
"minLength": 1,
"maxLength": 64
},
"quote_currency": {
"enum": [
"RUB",
"USD",
"EUR"
]
}
}
},
"positiveQuantity": {
"type": "object",
"additionalProperties": false,
"required": [
"atomic_units",
"decimals"
],
"properties": {
"atomic_units": {
"type": "string",
"pattern": "^[1-9][0-9]{0,77}$"
},
"decimals": {
"type": "integer",
"minimum": 0,
"maximum": 38
}
}
},
"nonNegativeQuantity": {
"type": "object",
"additionalProperties": false,
"required": [
"atomic_units",
"decimals"
],
"properties": {
"atomic_units": {
"type": "string",
"pattern": "^(0|[1-9][0-9]{0,77})$"
},
"decimals": {
"type": "integer",
"minimum": 0,
"maximum": 38
}
}
},
"decimal": {
"type": "object",
"additionalProperties": false,
"required": [
"value",
"scale"
],
"properties": {
"value": {
"type": "string",
"pattern": "^[1-9][0-9]{0,77}$"
},
"scale": {
"type": "integer",
"minimum": 0,
"maximum": 38
}
}
},
"money": {
"type": "object",
"additionalProperties": false,
"required": [
"currency",
"minor_units"
],
"properties": {
"currency": {
"type": "string",
"pattern": "^[A-Z]{3}$"
},
"minor_units": {
"type": "integer",
"minimum": 1,
"maximum": 9223372036854776000
}
}
},
"order": {
"type": "object",
"additionalProperties": false,
"required": [
"order_id",
"request_id",
"tenant_id",
"decision_id",
"event_contract",
"subject_ref",
"portfolio_ref",
"approval_ref",
"connection_id",
"account_ref",
"client_order_id",
"instrument",
"side",
"order_type",
"base_quantity",
"time_in_force",
"max_notional",
"command_id",
"provider_order_ref",
"status",
"submit_disposition",
"venue_state",
"filled_quantity",
"version",
"created_at",
"updated_at",
"evidence_digest",
"safety",
"external_calls"
],
"properties": {
"order_id": {
"type": "string",
"pattern": "^ord_[0-9a-f]{32}$"
},
"request_id": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"tenant_id": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"decision_id": {
"type": "string",
"minLength": 1,
"maxLength": 160
},
"event_contract": {
"const": "certarail.trade.order.v2",
"description": "Immutable event-schema provenance assigned when a new order is created."
},
"subject_ref": {
"type": "string",
"minLength": 1,
"maxLength": 256,
"description": "Opaque reference bound to client.subject_ref in the verified eligibility evidence."
},
"portfolio_ref": {
"type": "string",
"minLength": 1,
"maxLength": 256
},
"approval_ref": {
"type": "string",
"minLength": 1,
"maxLength": 256,
"description": "Correlation only; not evidence that CertaRail performed maker-checker approval."
},
"connection_id": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"account_ref": {
"type": "string",
"minLength": 1,
"maxLength": 256
},
"client_order_id": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"instrument": {
"$ref": "#/$defs/instrument"
},
"side": {
"const": "BUY",
"description": "Current v2 sandbox events are BUY-only; SELL is not executable."
},
"order_type": {
"const": "MARKET"
},
"base_quantity": {
"$ref": "#/$defs/positiveQuantity"
},
"limit_price": {
"$ref": "#/$defs/decimal"
},
"time_in_force": {
"const": "IOC"
},
"max_notional": {
"$ref": "#/$defs/money"
},
"command_id": {
"type": "string",
"pattern": "^cmd_[0-9a-f]{32}$"
},
"provider_order_ref": {
"type": "string",
"pattern": "^syn-order_[0-9a-f]{32}$",
"description": "Deterministic local reference, never a provider receipt."
},
"status": {
"enum": [
"OPEN",
"PARTIALLY_FILLED",
"FILLED",
"CANCELLED"
]
},
"submit_disposition": {
"const": "ACKNOWLEDGED",
"description": "Local PostgreSQL acknowledgement only."
},
"venue_state": {
"enum": [
"OPEN",
"PARTIALLY_FILLED",
"FILLED",
"CANCELLED"
]
},
"filled_quantity": {
"$ref": "#/$defs/nonNegativeQuantity"
},
"version": {
"type": "integer",
"minimum": 1
},
"created_at": {
"type": "string",
"format": "date-time"
},
"updated_at": {
"type": "string",
"format": "date-time"
},
"evidence_digest": {
"$ref": "#/$defs/digest"
},
"safety": {
"$ref": "#/$defs/safety"
},
"external_calls": {
"const": 0
}
}
},
"fill": {
"type": "object",
"additionalProperties": false,
"required": [
"fill_id",
"order_id",
"tenant_id",
"fill_ref",
"quantity",
"price",
"observed_at",
"recorded_at",
"evidence_digest",
"evidence_format",
"safety"
],
"properties": {
"fill_id": {
"type": "string",
"pattern": "^fill_[0-9a-f]{32}$"
},
"order_id": {
"type": "string",
"pattern": "^ord_[0-9a-f]{32}$"
},
"tenant_id": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"fill_ref": {
"type": "string",
"minLength": 1,
"maxLength": 256,
"description": "Synthetic deduplication reference, not a live venue fill ID."
},
"quantity": {
"$ref": "#/$defs/positiveQuantity"
},
"price": {
"allOf": [
{
"$ref": "#/$defs/decimal"
}
],
"description": "Caller-supplied local fixture, not real-time market data."
},
"observed_at": {
"type": "string",
"format": "date-time"
},
"recorded_at": {
"type": "string",
"format": "date-time"
},
"evidence_digest": {
"allOf": [
{
"$ref": "#/$defs/digest"
}
],
"description": "SHA-256 of the persisted versioned canonical synthetic-fill evidence bytes."
},
"evidence_format": {
"const": "SYNTHETIC_FILL_EVIDENCE_V1",
"description": "Exact current canonical evidence format hashed by evidence_digest."
},
"safety": {
"$ref": "#/$defs/safety"
}
}
}
}
}Example
Schema-valid synthetic payload
Generated from the canonical JSON Schema for documentation. It is not a broker record, provider receipt, or runtime evidence.
{
"event_id": "evt-trade_00000000000000000000000000000000",
"event_type": "certarail.trade.order.v2",
"action": "ORDER_OPENED",
"occurred_at": "2026-01-01T00:00:00Z",
"tenant_id": "tenant_example",
"order_id": "ord_00000000000000000000000000000000",
"decision_id": "decision_example",
"order": {
"order_id": "ord_00000000000000000000000000000000",
"request_id": "request_example",
"tenant_id": "tenant_example",
"decision_id": "decision_example",
"event_contract": "certarail.trade.order.v2",
"subject_ref": "subject_example",
"portfolio_ref": "portfolio_example",
"approval_ref": "approval_example",
"connection_id": "connection_example",
"account_ref": "account_example",
"client_order_id": "client_order_example",
"instrument": {
"base_asset": "BTC",
"quote_currency": "RUB"
},
"side": "BUY",
"order_type": "MARKET",
"base_quantity": {
"atomic_units": "1",
"decimals": 0
},
"time_in_force": "IOC",
"max_notional": {
"currency": "RUB",
"minor_units": 1
},
"command_id": "cmd_00000000000000000000000000000000",
"provider_order_ref": "syn-order_00000000000000000000000000000000",
"status": "OPEN",
"submit_disposition": "ACKNOWLEDGED",
"venue_state": "OPEN",
"filled_quantity": {
"atomic_units": "0",
"decimals": 0
},
"version": 1,
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-01T00:00:00Z",
"evidence_digest": "0000000000000000000000000000000000000000000000000000000000000000",
"safety": {
"environment": "sandbox",
"production_enforceable": false,
"monetary": false
},
"external_calls": 0
},
"evidence_digest": "0000000000000000000000000000000000000000000000000000000000000000",
"safety": {
"environment": "sandbox",
"production_enforceable": false,
"monetary": false
},
"external_calls": 0
}Correlation fields
Identifiers and trace context
Identifier, reference, trace, and Kafka-key fields are listed from the canonical schemas and message bindings.
| Location | Field | Requirement | Meaning |
|---|---|---|---|
| payload | payload.event_id | required | Identifier declared without a field description. |
| payload | payload.tenant_id | required | Identifier declared without a field description. |
| payload | payload.order_id | required | Identifier declared without a field description. |
| payload | payload.decision_id | required | Identifier declared without a field description. |
| payload | payload.order.order_id | required | Identifier declared without a field description. |
| payload | payload.order.request_id | required | Identifier declared without a field description. |
| payload | payload.order.tenant_id | required | Identifier declared without a field description. |
| payload | payload.order.decision_id | required | Identifier declared without a field description. |
| payload | payload.order.subject_ref | required | Opaque reference bound to client.subject_ref in the verified eligibility evidence. |
| payload | payload.order.portfolio_ref | required | Identifier declared without a field description. |
| payload | payload.order.approval_ref | required | Correlation only; not evidence that CertaRail performed maker-checker approval. |
| payload | payload.order.connection_id | required | Identifier declared without a field description. |
| payload | payload.order.account_ref | required | Identifier declared without a field description. |
| payload | payload.order.client_order_id | required | Identifier declared without a field description. |
| payload | payload.order.command_id | required | Identifier declared without a field description. |
| payload | payload.order.provider_order_ref | required | Deterministic local reference, never a provider receipt. |
| payload | payload.fill.fill_id | optional | Identifier declared without a field description. |
| payload | payload.fill.order_id | optional | Identifier declared without a field description. |
| payload | payload.fill.tenant_id | optional | Identifier declared without a field description. |
| payload | payload.fill.fill_ref | optional | Synthetic deduplication reference, not a live venue fill ID. |
| header | headers.certarail-event-id | required | Stable consumer deduplication identifier; equal to payload event_id. |
| header | headers.traceparent | optional | Optional W3C Trace Context for the Kafka publisher attempt. |
| header | headers.tracestate | optional | Optional W3C Trace Context tracestate value; omitted when empty. |
| kafka-key | message.key | declared | Pseudonymous ordering key computed as `order_` plus the first 16 bytes of SHA-256(tenant_id || NUL || order_id), encoded as lowercase hexadecimal. It is not authentication, tenancy, provider identity, or proof of execution. |
Ordering semantics
declared
Ordering is guaranteed only for one order key within certarail.trade.v1.
Scope: one order key within certarail.trade.v1
Pseudonymous ordering key computed as `order_` plus the first 16 bytes of SHA-256(tenant_id || NUL || order_id), encoded as lowercase hexadecimal. It is not authentication, tenancy, provider identity, or proof of execution.
Retry semantics
declared
at-least-once delivery is declared. Consumers must deduplicate by payload.event_id and tolerate redelivery.
Deduplication key: payload.event_id
Scope: one event_id across v1 and v2
Compatibility design only. The current checkout runtime does not emit this Kafka event. Partner webhook delivery signs exact HTTP body bytes under the separately documented webhook contract and must not be inferred from this schema.
Canonical AsyncAPI marks this Kafka event as NOT_PRODUCED. Its schema is browsable, but it is not evidence of a running publisher.
- Channel / topic
certarail.checkout.lifecycle.v1channel: checkoutLifecycle- Version
- v1AsyncAPI document 1.3.1 · transport header 1
Producer
No producer operation is declared because the canonical Kafka publication state is NOT_PRODUCED.
Consumer
No named consumer operation is declared in canonical AsyncAPI; the catalog does not infer one.
Payload schema
CertaRail checkout lifecycle event v1
urn:certarail:schema:event:certarail.checkout.lifecycle.v1
| Field | Requirement | Type / constraints | Description |
|---|---|---|---|
event_id | required | stringpattern ^checkout_evt_[A-Za-z0-9._:/-]{1,112}$ | No field description declared. |
event_type | required | stringcheckout.session.created | checkout.session.expired | deal.action_required | deal.review | +13 | No field description declared. |
event_version | required | numberconst 1 | No field description declared. |
tenant_id | required | stringminLength 1 · maxLength 128 | No field description declared. |
occurred_at | required | stringformat date-time | No field description declared. |
aggregate_id | required | stringpattern ^(checkout_|deal_)[A-Za-z0-9._:/-]{1,120}$ | No field description declared. |
correlation_id | required | stringminLength 1 · maxLength 128 | No field description declared. |
sequence | required | integerminimum 1 | No field description declared. |
payload | required | object#/$defs/safePayload | No field description declared. |
safety | required | object#/$defs/safety | No field description declared. |
Full payload schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "urn:certarail:schema:event:certarail.checkout.lifecycle.v1",
"title": "CertaRail checkout lifecycle event v1",
"description": "PII-minimized lifecycle evidence for the sandbox Crypto Checkout. This event is not proof of a real payment, venue execution, custody balance, or blockchain transfer.",
"type": "object",
"additionalProperties": false,
"required": [
"event_id",
"event_type",
"event_version",
"tenant_id",
"occurred_at",
"aggregate_id",
"correlation_id",
"sequence",
"payload",
"safety"
],
"properties": {
"event_id": {
"type": "string",
"pattern": "^checkout_evt_[A-Za-z0-9._:/-]{1,112}$"
},
"event_type": {
"enum": [
"checkout.session.created",
"checkout.session.expired",
"deal.action_required",
"deal.review",
"deal.confirmed",
"payment.pending",
"payment.confirmed",
"payment.failed",
"execution.submitted",
"execution.unknown",
"execution.filled",
"delivery.pending",
"delivery.confirmed",
"deal.completed",
"deal.failed",
"refund.pending",
"refund.completed"
]
},
"event_version": {
"const": 1
},
"tenant_id": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"occurred_at": {
"type": "string",
"format": "date-time"
},
"aggregate_id": {
"type": "string",
"pattern": "^(checkout_|deal_)[A-Za-z0-9._:/-]{1,120}$"
},
"correlation_id": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"sequence": {
"type": "integer",
"minimum": 1
},
"payload": {
"$ref": "#/$defs/safePayload"
},
"safety": {
"$ref": "#/$defs/safety"
}
},
"$defs": {
"axes": {
"type": "object",
"additionalProperties": false,
"required": [
"admission",
"quote",
"payment",
"execution",
"delivery",
"ledger"
],
"properties": {
"admission": {
"enum": [
"NOT_STARTED",
"EVALUATING",
"ALLOW",
"DENY",
"REVIEW",
"ACTION_REQUIRED"
]
},
"quote": {
"enum": [
"NOT_REQUESTED",
"REQUESTING",
"AVAILABLE",
"EXPIRED",
"REJECTED",
"SUPERSEDED"
]
},
"payment": {
"enum": [
"NOT_STARTED",
"RESERVING",
"RESERVED",
"PENDING",
"CONFIRMED",
"FAILED",
"RELEASE_PENDING",
"RELEASED",
"REFUND_PENDING",
"REFUNDED",
"UNKNOWN"
]
},
"execution": {
"enum": [
"NOT_STARTED",
"SUBMITTING",
"UNKNOWN",
"ACKNOWLEDGED",
"PARTIALLY_FILLED",
"FILLED",
"CANCEL_PENDING",
"CANCELLED",
"REJECTED",
"FAILED"
]
},
"delivery": {
"enum": [
"NOT_STARTED",
"PENDING",
"PROCESSING",
"CONFIRMED",
"FAILED",
"UNKNOWN"
]
},
"ledger": {
"enum": [
"NOT_STARTED",
"RESERVED",
"POSTED",
"REVERSED",
"RECONCILIATION_REQUIRED"
]
}
}
},
"safePayload": {
"type": "object",
"additionalProperties": false,
"required": [
"checkout_session_reference",
"overall_status",
"axes"
],
"properties": {
"checkout_session_reference": {
"type": "string",
"pattern": "^checkout_[A-Za-z0-9._:/-]{1,120}$"
},
"deal_reference": {
"type": "string",
"pattern": "^deal_[A-Za-z0-9._:/-]{1,120}$"
},
"overall_status": {
"enum": [
"DRAFT",
"CHECKING",
"ACTION_REQUIRED",
"READY_TO_CONFIRM",
"QUOTE_EXPIRED",
"PROCESSING_PAYMENT",
"EXECUTING",
"DELIVERING",
"COMPLETED",
"REVIEW",
"FAILED",
"CANCELLED",
"REFUNDING"
]
},
"axes": {
"$ref": "#/$defs/axes"
},
"action": {
"enum": [
"RETRY",
"REFRESH_QUOTE",
"REDUCE_AMOUNT",
"COMPLETE_VERIFICATION",
"CHANGE_PAYMENT_METHOD",
"CONTACT_SUPPORT",
"RETURN_TO_PARTNER",
"WAIT",
"NONE"
]
},
"receipt_reference": {
"type": "string",
"pattern": "^receipt_[A-Za-z0-9._:/-]{1,118}$"
}
}
},
"safety": {
"type": "object",
"additionalProperties": false,
"required": [
"environment",
"external_calls",
"monetary",
"production_enforceable",
"execution_mode"
],
"properties": {
"environment": {
"const": "SANDBOX"
},
"external_calls": {
"const": 0
},
"monetary": {
"const": false
},
"production_enforceable": {
"const": false
},
"execution_mode": {
"const": "SANDBOX"
}
}
}
}
}Example
Schema-valid synthetic payload
Generated from the canonical JSON Schema for documentation. It is not a broker record, provider receipt, or runtime evidence.
{
"event_id": "checkout_evt_A",
"event_type": "checkout.session.created",
"event_version": 1,
"tenant_id": "tenant_example",
"occurred_at": "2026-01-01T00:00:00Z",
"aggregate_id": "checkout_A",
"correlation_id": "correlation_example",
"sequence": 1,
"payload": {
"checkout_session_reference": "checkout_A",
"overall_status": "DRAFT",
"axes": {
"admission": "NOT_STARTED",
"quote": "NOT_REQUESTED",
"payment": "NOT_STARTED",
"execution": "NOT_STARTED",
"delivery": "NOT_STARTED",
"ledger": "NOT_STARTED"
}
},
"safety": {
"environment": "SANDBOX",
"external_calls": 0,
"monetary": false,
"production_enforceable": false,
"execution_mode": "SANDBOX"
}
}Correlation fields
Identifiers and trace context
Identifier, reference, trace, and Kafka-key fields are listed from the canonical schemas and message bindings.
| Location | Field | Requirement | Meaning |
|---|---|---|---|
| payload | payload.event_id | required | Identifier declared without a field description. |
| payload | payload.tenant_id | required | Identifier declared without a field description. |
| payload | payload.aggregate_id | required | Identifier declared without a field description. |
| payload | payload.correlation_id | required | Identifier declared without a field description. |
| payload | payload.payload.checkout_session_reference | required | Identifier declared without a field description. |
| payload | payload.payload.deal_reference | optional | Identifier declared without a field description. |
| payload | payload.payload.receipt_reference | optional | Identifier declared without a field description. |
| header | headers.certarail-event-id | required | Stable consumer deduplication identifier; equal to payload event_id. |
| header | headers.traceparent | optional | Optional W3C Trace Context for the Kafka publisher attempt. |
| header | headers.tracestate | optional | Optional W3C Trace Context tracestate value; omitted when empty. |
| kafka-key | message.key | declared | Tenant-scoped aggregate ordering key; never an authentication credential. |
Ordering semantics
contract-only
A Kafka key schema is documented for compatibility, but runtime ordering is not claimed while publication is NOT_PRODUCED.
Tenant-scoped aggregate ordering key; never an authentication credential.
Retry semantics
not-applicable
Kafka retry and redelivery semantics do not apply while the canonical publication state is NOT_PRODUCED. Other transport retries are separate contracts.
PostgreSQL-backed sandbox Inbox delivery evidence exists and checkout.completed is linked to the tenant movement outbox fact. The current runtime does not emit this candidate as a Kafka event.
Canonical AsyncAPI marks this Kafka event as NOT_PRODUCED. Its schema is browsable, but it is not evidence of a running publisher.
- Channel / topic
certarail.checkout.partner-delivery.v1channel: checkoutPartnerDeliveries- Version
- v1AsyncAPI document 1.3.1 · transport header 1
Producer
No producer operation is declared because the canonical Kafka publication state is NOT_PRODUCED.
Consumer
No named consumer operation is declared in canonical AsyncAPI; the catalog does not infer one.
Payload schema
CertaRail checkout partner delivery event v1
urn:certarail:schema:event:certarail.checkout.partner-delivery.v1
| Field | Requirement | Type / constraints | Description |
|---|---|---|---|
delivery_event_id | required | stringpattern ^webhook_evt_[A-Za-z0-9._:/-]{1,111}$ | No field description declared. |
event_type | required | stringconst "certarail.checkout.partner-delivery.v1" | No field description declared. |
event_version | required | numberconst 1 | No field description declared. |
tenant_id | required | stringminLength 1 · maxLength 128 | No field description declared. |
aggregate_id | required | stringpattern ^(checkout_|deal_)[A-Za-z0-9._:/-]{1,120}$ | No field description declared. |
correlation_id | required | stringminLength 1 · maxLength 128 | No field description declared. |
occurred_at | required | stringformat date-time | No field description declared. |
partner_endpoint_reference | required | stringminLength 1 · maxLength 160 | No field description declared. |
key_id | required | stringminLength 1 · maxLength 96 | No field description declared. |
body_sha256 | required | stringpattern ^[a-f0-9]{64}$ | No field description declared. |
attempt | required | integerminimum 1 · maximum 32 | No field description declared. |
state | required | stringPENDING | IN_FLIGHT | DELIVERED | RETRY_SCHEDULED | +1 | No field description declared. |
next_attempt_at | optional | stringformat date-time | No field description declared. |
delivered_at | optional | stringformat date-time | No field description declared. |
safety | required | object | No field description declared. |
Full payload schema
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "urn:certarail:schema:event:certarail.checkout.partner-delivery.v1",
"title": "CertaRail checkout partner delivery event v1",
"description": "Durable, PII-minimized partner-webhook delivery evidence. The exact body bytes are replayed for retries; URLs and signing secrets are deliberately excluded.",
"type": "object",
"additionalProperties": false,
"required": [
"delivery_event_id",
"event_type",
"event_version",
"tenant_id",
"aggregate_id",
"correlation_id",
"occurred_at",
"partner_endpoint_reference",
"key_id",
"body_sha256",
"attempt",
"state",
"safety"
],
"properties": {
"delivery_event_id": {
"type": "string",
"pattern": "^webhook_evt_[A-Za-z0-9._:/-]{1,111}$"
},
"event_type": {
"const": "certarail.checkout.partner-delivery.v1"
},
"event_version": {
"const": 1
},
"tenant_id": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"aggregate_id": {
"type": "string",
"pattern": "^(checkout_|deal_)[A-Za-z0-9._:/-]{1,120}$"
},
"correlation_id": {
"type": "string",
"minLength": 1,
"maxLength": 128
},
"occurred_at": {
"type": "string",
"format": "date-time"
},
"partner_endpoint_reference": {
"type": "string",
"minLength": 1,
"maxLength": 160
},
"key_id": {
"type": "string",
"minLength": 1,
"maxLength": 96
},
"body_sha256": {
"type": "string",
"pattern": "^[a-f0-9]{64}$"
},
"attempt": {
"type": "integer",
"minimum": 1,
"maximum": 32
},
"state": {
"enum": [
"PENDING",
"IN_FLIGHT",
"DELIVERED",
"RETRY_SCHEDULED",
"QUARANTINED"
]
},
"next_attempt_at": {
"type": "string",
"format": "date-time"
},
"delivered_at": {
"type": "string",
"format": "date-time"
},
"safety": {
"type": "object",
"additionalProperties": false,
"required": [
"environment",
"execution_mode",
"external_calls",
"monetary",
"production_enforceable"
],
"properties": {
"environment": {
"const": "SANDBOX"
},
"execution_mode": {
"const": "SANDBOX"
},
"external_calls": {
"const": 0
},
"monetary": {
"const": false
},
"production_enforceable": {
"const": false
}
}
}
}
}Example
Schema-valid synthetic payload
Generated from the canonical JSON Schema for documentation. It is not a broker record, provider receipt, or runtime evidence.
{
"delivery_event_id": "webhook_evt_A",
"event_type": "certarail.checkout.partner-delivery.v1",
"event_version": 1,
"tenant_id": "tenant_example",
"aggregate_id": "checkout_A",
"correlation_id": "correlation_example",
"occurred_at": "2026-01-01T00:00:00Z",
"partner_endpoint_reference": "example",
"key_id": "key_example",
"body_sha256": "0000000000000000000000000000000000000000000000000000000000000000",
"attempt": 1,
"state": "PENDING",
"safety": {
"environment": "SANDBOX",
"execution_mode": "SANDBOX",
"external_calls": 0,
"monetary": false,
"production_enforceable": false
}
}Correlation fields
Identifiers and trace context
Identifier, reference, trace, and Kafka-key fields are listed from the canonical schemas and message bindings.
| Location | Field | Requirement | Meaning |
|---|---|---|---|
| payload | payload.delivery_event_id | required | Identifier declared without a field description. |
| payload | payload.tenant_id | required | Identifier declared without a field description. |
| payload | payload.aggregate_id | required | Identifier declared without a field description. |
| payload | payload.correlation_id | required | Identifier declared without a field description. |
| payload | payload.partner_endpoint_reference | required | Identifier declared without a field description. |
| payload | payload.key_id | required | Identifier declared without a field description. |
| header | headers.certarail-event-id | required | Stable consumer deduplication identifier; equal to payload event_id. |
| header | headers.traceparent | optional | Optional W3C Trace Context for the Kafka publisher attempt. |
| header | headers.tracestate | optional | Optional W3C Trace Context tracestate value; omitted when empty. |
| kafka-key | message.key | declared | Tenant-scoped aggregate ordering key; never an endpoint or signing secret. |
Ordering semantics
contract-only
A Kafka key schema is documented for compatibility, but runtime ordering is not claimed while publication is NOT_PRODUCED.
Tenant-scoped aggregate ordering key; never an endpoint or signing secret.
Retry semantics
not-applicable
Kafka retry and redelivery semantics do not apply while the canonical publication state is NOT_PRODUCED. Other transport retries are separate contracts.
- Producer и Consumer показывают только send/receive operations, объявленные в canonical AsyncAPI; пустое состояние не заменяется предположением об имени сервиса.
- Correlation fields включают идентификаторы, references, trace headers и Kafka key, реально присутствующие в schema и message bindings.
- Ordering и retry читаются отдельно для каждой версии события: schema-only запись не превращается в published event.
- Synthetic example валидируется по canonical JSON Schema, но не является broker record или доказательством внешнего эффекта.
Проектируйте обработчик так, будто одна и та же запись придёт повторно: producer гарантирует durable publication, но не end-to-end exactly-once между PostgreSQL, Kafka и состоянием consumer.
Факт и outbox envelope фиксируются одной PostgreSQL-транзакцией, после чего отдельный publisher отправляет запись в Kafka. API не ждёт broker delivery и не откатывает уже committed факт при недоступности Kafka.
Главное окно дубликата возникает, когда Kafka ACK уже получен, а отметка PUBLISHED в PostgreSQL не зафиксирована. После lease recovery publisher имеет право отправить тот же immutable envelope и event_id ещё раз.
Считать event_id обязательным ключом обработки
Сверьте certarail-event-id с payload event_id и не выполняйте domain effect до успешной durable dedupe-проверки.
Фиксировать результат до offset
Сначала атомарно сохраните inbox и локальный effect, затем commit Kafka offset. Обратный порядок создаёт окно потери.
Ожидать повтор после любого ambiguous outcome
Timeout, rebalance или crash после database commit должны сходиться через inbox, а не запускать effect повторно.
Сохранять порядок только в declared scope
Порядок существует для одного Kafka key внутри topic; между keys, partitions и topics глобального порядка нет.
| Окно отказа | Что может произойти | Обязательная реакция |
|---|---|---|
| До Kafka ACK | Producer повторит immutable запись | Dedupe по event_id до effect |
| После ACK, до PUBLISHED | Та же запись может быть опубликована повторно | Сверить сохранённый payload digest и вернуть уже committed результат |
| После consumer DB commit, до offset commit | Kafka повторно отдаст запись | Inbox завершает повтор без второго effect |
| Offset committed до local effect | Запись может быть потеряна для consumer | Такой порядок запрещён |
Dedupe — это durable бизнес-инвариант consumer, а не in-memory cache: повтор должен распознаваться после restart, rebalance, redeploy и восстановления offset.
- Используйте payload event_id как dedupe key; header certarail-event-id обязан совпадать с ним. Kafka partition/offset — позиция доставки, а не business identity.
- Храните event_type и SHA-256 точных payload bytes рядом с event_id. Один event_id с другим type или payload SHA-256 — conflict, а не допустимый duplicate.
- Уникальность задавайте в durable store в scope конкретного consumer: например, UNIQUE (consumer_name, event_id). Не полагайтесь на срок жизни pod или process.
- Duplicate с тем же digest возвращает уже committed outcome без повторного side effect. Duplicate conflict переводится в quarantine и поднимает alert.
- tenant_id нужен для tenant-local state и audit, но не заменяет authenticated service identity, Kafka ACL или authorization boundary.
| Inbox state | Digest/type | Действие |
|---|---|---|
| Записи нет | Валидны | Создать RECEIVED и продолжить обработку |
| PROCESSED | Совпадают | Подтвердить duplicate без повторного effect |
| RECEIVED | Совпадают | Заблокировать строку и завершить либо восстановить обработку |
| Любое | Не совпадают | Conflict: quarantine, alert, offset не продвигать без durable disposition |
Inbox связывает получение Kafka record, проверку duplicate и локальный state transition в одной транзакции; commit offset остаётся последним подтверждением доставки.
Проверить envelope до domain effect
Проверьте обязательные headers, совпадение event_id/event_type, размер и exact JSON Schema. Invalid record ещё не считается обработанным.
Вставить или заблокировать inbox row
UNIQUE key сериализует конкурентные повторы; существующая row требует сравнения type и payload digest.
Выполнить local effect атомарно
Projection, journal или durable downstream command фиксируются в одной транзакции с переходом inbox в PROCESSED и processed_at.
Подтвердить offset после commit
Если процесс упадёт между database commit и offset commit, повтор безопасно завершится через PROCESSED row.
-- Reference pattern owned by one consumer service.
CREATE TABLE consumer_inbox (
consumer_name text NOT NULL,
event_id text NOT NULL,
tenant_id text NOT NULL,
event_type text NOT NULL,
payload_sha256 char(64) NOT NULL,
status text NOT NULL CHECK (status IN ('RECEIVED', 'PROCESSED', 'QUARANTINED')),
received_at timestamptz NOT NULL,
processed_at timestamptz,
quarantine_reason_code text,
PRIMARY KEY (consumer_name, event_id)
);
BEGIN;
-- Insert RECEIVED, or lock the existing event_id row.
-- A duplicate is safe only when event_type and payload_sha256 still match.
-- Validate and apply the local projection or side effect in this transaction.
-- Set status='PROCESSED' and processed_at before COMMIT.
COMMIT;
-- Commit the Kafka offset only after the database commit succeeds.Replay — контролируемое повторное чтение immutable broker history; он не должен создавать новый business fact, менять event_id или обходить исходные authorization и idempotency rules.
Зафиксировать replay manifest
Запишите reason, ticket, owner, source topic, partitions, start/end offsets, schema set, consumer build и ожидаемый результат.
Проверить на shadow consumer group
Сначала используйте shadow consumer group с отключёнными внешними effects либо с тем же durable idempotency boundary.
Сохранить исходную identity
Повторно обрабатывайте те же bytes, key, event_type и тот же event_id; не перепубликовывайте факт как новое событие.
Вести checkpoint по partition
Фиксируйте последний verified offset и счётчики new, duplicate, quarantined и failed для возобновляемого replay.
Выполнить reconciliation
Сравните входные event IDs/digests, inbox outcomes и rebuilt projection до переключения рабочего consumer group.
| Операция | Назначение | Identity rule |
|---|---|---|
| Broker offset replay | Повторно прочитать retained Kafka records | Исходные event_id и bytes неизменны |
| Projection rebuild | Построить новую projection из истории | Новый consumer scope, та же event identity |
| Producer outbox REQUEUE | Снять конкретный quarantined publish после remediation | Исходный envelope и event_id неизменны |
DLQ хранит terminal processing failure отдельно от обычного потока, но не заменяет inbox, quarantine review или исправление причины; consumer DLQ в CertaRail ЕЩЁ НЕ РЕАЛИЗОВАНО.
- Записывайте original topic, partition, offset, key digest, event_id, event_type, schema identity, consumer name/build и first/last failure time.
- Храните bounded error code/class и payload digest. Raw payload допускается только по утверждённой data classification; секреты, credentials и URL с token запрещены.
- DLQ record получает собственную delivery identity, но сохраняет ссылку на исходный event_id. Повтор одного DLQ write также должен дедуплицироваться.
- Commit source offset разрешён только после durable DLQ/quarantine disposition. Если запись terminal evidence не подтверждена, offset остаётся непроведённым.
- Re-drive из DLQ проходит через тот же inbox и idempotent processing, а не вызывает отдельный обходной handler.
| Класс | Disposition | Автоматический retry |
|---|---|---|
| Transient dependency | Оставить на source offset | Bounded backoff |
| Invalid/poison payload | Quarantine или DLQ evidence | Нет |
| Unknown schema/event type | Quarantine до compatibility decision | Нет |
| Duplicate digest conflict | Security quarantine + alert | Нет |
| Application bug | Pause partition, исправить, controlled replay | Нет до fix |
Quarantine останавливает автоматическую обработку записи с permanent, integrity или compatibility failure и сохраняет evidence для отдельного operator-owned решения.
Остановить только затронутый stream
Quarantined producer head блокирует только свой key; unrelated keys продолжают публиковаться. Не расширяйте outage без evidence.
Осмотреть immutable episode
Сверьте event identity, topic/key, attempts, last error и expected digest, не изменяя payload или queue state.
Устранить root cause
Schema/configuration/transport defect исправляется и проверяется до любого REQUEUE. Retry не является remediation.
Применить maker-checker
Разные maker и checker фиксируют reason, ticket и expected digest для конкретного quarantine episode.
Requeue с тем же envelope
Audited REQUEUE сбрасывает delivery attempts, но не меняет event_id, payload, created_at или ordering position.
| Контур | Состояние | Граница |
|---|---|---|
| Producer outbox | РЕАЛИЗОВАНО | QUARANTINED, immutable inspection digest, maker-checker REQUEUE journal |
| Consumer quarantine store | ЕЩЁ НЕ РЕАЛИЗОВАНО | Consumer должен реализовать durable record, ACL, retention и re-drive |
Consumer выбирает schema по payload event_type, проверяет header identity и поддерживает только явно объявленные версии; transport header version не заменяет payload version.
Dispatch по event_type
Сначала сопоставьте certarail-event-type и payload event_type, затем выберите exact JSON Schema. Несовпадение является integrity failure.
Поддерживать overlap версий
На общем trading topic одновременно встречаются v1 и v2. Consumer обязан валидировать обе версии и дедуплицировать один event_id между ними.
Считать schema closed
Published payload schemas используют additionalProperties=false: unknown field не является автоматически совместимым additive change.
Разворачивать consumer первым
Новая версия сначала проходит compatibility CI и canary/shadow consumer, затем producer emission и только после окна поддержки удаляется старая версия.
Quarantine неизвестное
Unknown event_type/schema не пропускается, не преобразуется эвристически и не получает committed offset без durable disposition.
| Изменение | Оценка | Безопасный rollout |
|---|---|---|
| Новое optional поле при additionalProperties=false | Не совместимо со старым strict consumer автоматически | Consumer-first coordination либо новая event version |
| Удаление/required/type/enum change | Breaking | Новая event version; при смене partition contract — новый topic |
| Новый optional header | Совместимо при ignore-unknown policy | Не использовать header как auth/tenant evidence |
| Partition count/key algorithm change | Ordering-breaking | Новый versioned topic и controlled cutover |
Recovery начинается с сохранения evidence и ограничения blast radius: сначала pause и classification, затем исправление, bounded replay и reconciliation, а не blind offset reset.
Зафиксировать incident checkpoint
Сохраните group, topic, partition, current/committed offset, event_id/digest, lag, inbox state, consumer build и время первого отказа.
Остановить затронутые partitions
Pause предотвращает retry storm. Не commit failed offset и не останавливайте unrelated partitions без причины.
Классифицировать отказ
Разделите transient dependency, poison/schema, duplicate conflict, code regression и потерю dedupe store.
Восстановить durable prerequisites
Верните database/inbox, примените совместимый build и проверьте backup/restore до чтения retained history.
Прогнать bounded shadow replay
Начните с сохранённого checkpoint, отключите неподтверждённые external effects и сравните outcomes.
Выполнить reconciliation
Сведите counts, event IDs/digests, duplicates, quarantined records и итоговую projection с ожидаемым manifest.
Возобновить и наблюдать
Resume только после approval; контролируйте lag, error rate, inbox conflicts и offset progress до закрытия incident.
| Симптом | До исправления | Offset rule |
|---|---|---|
| Dependency unavailable | Pause + bounded retry | Не commit failed record |
| Poison/unknown schema | Durable quarantine + alert | Commit только после disposition evidence |
| Consumer bug | Deploy compatible fix + shadow replay | Вернуться к recorded checkpoint |
| Inbox потерян/повреждён | Restore и verify dedupe state | Не replay до восстановления idempotency boundary |
Local broker хранит Kafka log 168 часов и работает с RF=1; это developer profile, а не утверждённая production retention, availability или disaster-recovery policy.
- Consumer inbox хранится не меньше максимального replay window и срока, в котором Kafka record либо архив ещё могут быть повторно прочитаны.
- Payload retention определяется data classification и purpose limitation. Для долгой dedupe достаточно event_id, event_type, payload digest и outcome, если raw payload больше не нужен.
- DLQ и quarantine имеют отдельные access, retention, deletion и legal-hold правила; их нельзя очищать вместе с обычным processed inbox без incident closure.
- Offset retention, broker log retention, inbox retention и projection backup должны образовывать один проверенный recovery window.
- Опубликованные producer outbox rows пока не имеют автоматического purge/archive workflow. ЕЩЁ НЕ РЕАЛИЗОВАНО: approved outbox retention, legal hold и deletion evidence.
| Layer | Текущее evidence | Production decision |
|---|---|---|
| Kafka log | Local default: 168 часов, single node RF=1 | Capacity, RF, archive и restore drill |
| Consumer inbox | Consumer-owned; runtime отсутствует | Не короче replay/recovery window |
| DLQ/quarantine | Producer quarantine есть; consumer store отсутствует | Investigation, legal hold, deletion approval |
| Producer outbox | Durable rows без автоматического purge | Partition/archive/purge и immutable evidence policy |
Idempotent processing означает, что повтор record приводит к тому же durable outcome: atomic inbox и local effect фиксируются вместе, а внешняя неопределённость восстанавливается lookup/reconciliation.
Валидировать до mutation
Envelope, event type, schema, tenant scope и digest проходят fail-closed проверку до начала domain changes.
Открыть одну local transaction
Inbox row блокируется либо создаётся тем же commit, что projection, journal или durable downstream command.
Сделать effect естественно идемпотентным
Используйте unique business key, compare-and-set/version или compensating journal; не полагайтесь только на handler branch.
Связать внешний command с identity
Если egress разрешён, передавайте stable provider idempotency key, производный от consumer и event_id, и храните request digest.
Восстановить ambiguous result
Timeout после submit переводится в UNKNOWN: выполняйте lookup/reconciliation, а не повтор исходного side effect.
Подтвердить delivery последним
Commit Kafka offset выполняется только после durable local commit; crash до offset commit создаёт безопасный duplicate.
receive(record)
-> validate headers, event_type and exact payload schema
-> begin local database transaction
-> insert or lock inbox(event_id, payload_sha256)
-> if PROCESSED with the same digest: commit and acknowledge duplicate
-> if the digest conflicts: quarantine and alert
-> apply projection / create durable command with a stable idempotency key
-> mark inbox PROCESSED and commit
-> commit Kafka offset
If an external response is ambiguous, persist UNKNOWN and reconcile by lookup;
never submit the original external side effect again merely because Kafka redelivered.Production listeners шифруют и взаимно проверяют каждый client, inter-broker и controller channel; plaintext listener не должен существовать ни в workload network, ни как аварийный обход.
- Разрешены TLS 1.3 и, только для утверждённой совместимости, TLS 1.2; более ранние версии и weak cipher suites запрещены. TLS 1.3 является preferred profile.
- Hostname verification и SAN обязательны. IP, DNS name и service discovery identity должны совпадать с сертификатом; insecure skip verify и trust-all callback запрещены.
- Client-to-broker, inter-broker, controller quorum, admin tooling, Schema Registry и replication links получают отдельные reviewed trust boundaries. Шифрование только внешнего listener недостаточно.
- Private keys поступают из bank-managed KMS/HSM или workload secret delivery, никогда не входят в image, environment dump, URL, log или replay manifest.
- Certificate rotation поддерживает перекрытие current/next CA и client certificates, имеет expiry alerts и проверяется без downtime до каждой Production activation.
- Egress policy разрешает workload только broker bootstrap/DNS, identity provider и approved telemetry endpoints; произвольный broker address запрещён.
| Channel | Target protocol | Mandatory verification |
|---|---|---|
| Application workload | SASL_SSL или SSL/mTLS | CA, SAN, principal, revocation/expiry |
| Broker replication | TLS-authenticated private listener | Dedicated broker principal and SAN |
| KRaft controller | TLS-authenticated controller listener | Dedicated controller principal and network segment |
| Admin plane | mTLS-only isolated listener | Named operator/service principal and approval |
Каждый process получает собственную workload identity; один credential нельзя разделять между producer, consumer, operator или tenant, а network reachability никогда не заменяет authentication.
- Target Production hosted profile: SASL_SSL + OAUTHBEARER с machine-to-machine token от approved identity provider. Token short-lived, не дольше 10 минут, audience привязан к Kafka cluster, scope — к одной workload role.
- Kafka unsecured OAUTHBEARER запрещён: production validation проверяет issuer, audience, signature, expiry, not-before и principal mapping; token не попадает в logs, metrics, traces или command history.
- On-prem alternative: SSL/mTLS на отдельном listener с однозначным certificate-to-principal mapping. Он не включается как silent fallback при отказе OAuth.
- Admin и break-glass используют отдельный mTLS principal, hardware-backed key, time-bounded elevation и maker-checker approval. Application identity не получает admin ACL.
- SASL/PLAIN и static shared password запрещены. SCRAM допускается только как отдельно согласованный exception over TLS с vault rotation и не меняет least-privilege ACL.
- Token/certificate refresh проходит до expiry; auth failure не переключает client на plaintext, другой listener, другой principal или бесконечный retry.
| Use case | Authentication | Fail-closed rule |
|---|---|---|
| Hosted application | SASL_SSL / OAUTHBEARER | Validated short-lived JWT; no unsecured token mode |
| On-prem application | SSL / mTLS | Dedicated listener and deterministic DN/SAN mapping |
| Broker/controller | Dedicated TLS or mTLS identity | No application principal reuse |
| Administration | Isolated mTLS + temporary elevation | No standing super-user credential |
Kafka ACL применяется к principal, topic, consumer group и admin resource; он не умеет безопасно ограничить tenant по payload field внутри общего shared topic.
- KRaft StandardAuthorizer работает deny-by-default с allow.everyone.if.no.acl.found=false. Wildcard grants для application principals запрещены; super.users ограничен broker/controller break-glass contour.
- Outbox principal получает Write и Describe только на approved internal topics плюс минимальный IdempotentWrite, если он требуется producer client. Create, Delete, Alter и AlterConfigs ему запрещены.
- Consumer principal получает Read/Describe только на назначенные topics и Read/Describe только на собственный consumer group prefix. Один consumer не может присоединиться к group другой команды.
- Canonical shared topics доступны только внутренним multi-tenant services, которые применяют server-side tenant boundary. Внешнему tenant consumer нельзя дать ACL на shared topic с чужими records.
- Если bank или tenant получает direct Kafka delivery, создаётся отдельный cluster/namespace либо opaque tenant topic prefix и отдельный group prefix. Opaque tenant alias не содержит названия клиента или PII.
- Topic creation, partition count, replication, retention, cleanup policy, quotas и ACL управляются IaC через отдельный release principal. Runtime auto-create выключен.
- Per-principal byte/request quotas берутся из approved load test; unlimited quota или silent throttling без alert и ownership не допускаются.
| Principal | Topic rights | Group/admin rights |
|---|---|---|
| Outbox producer | Write + Describe on explicit internal topics | No group; minimal cluster IdempotentWrite only |
| Internal consumer | Read + Describe on assigned topics | Read + Describe on own group prefix |
| Tenant delivery consumer | Read + Describe on isolated tenant namespace | Own tenant group prefix only |
| Release controller | Reviewed Create/Alter/DescribeConfigs | No application data consumption |
| Break-glass admin | Time-bound audited elevation | Maker-checker and session recording |
Production registry становится контролируемым каталогом immutable JSON Schema identities; canonical repository остаётся источником review, а registry — promotion и runtime resolution layer.
- Формат payload остаётся JSON Schema 2020-12. Каждый promoted schema artifact связывается с canonical $id, event_type, major version, Git commit и SHA-256 exact bytes.
- Subject scope — topic + event_type, чтобы несколько event families или versions на одном topic не делили несовместимую compatibility chain. Key и value subjects разделены.
- Runtime auto-registration=off. CI validates schema/examples/compatibility, затем release principal регистрирует exact digest; application principal может только read approved schema ID.
- Producer wire contract содержит immutable schema ID или утверждённый registry framing. До изменения wire format соответствующий binding/header добавляется в canonical AsyncAPI и проходит consumer overlap.
- Registry endpoint использует TLS, workload authentication, least-privilege ACL и отдельный audit stream. Unknown schema ID или digest mismatch fail closed и отправляет record в quarantine.
- Metadata database, subjects, compatibility settings и soft-delete state входят в encrypted backup/restore drill. Permanent delete в Production запрещён до data-governance approval.
- Consumer держит bounded cache уже проверенных schema IDs для краткого registry outage, но не принимает незнакомую schema по inference или latest lookup.
| Stage | Actor | Required evidence |
|---|---|---|
| Validate | CI | JSON Schema validity, examples, references and digest |
| Compare | CI compatibility gate | Transitive consumer compatibility report |
| Register | Release principal | Approved subject, schema ID and immutable digest |
| Activate | Producer release | Old/new consumer matrix and rollback plan |
| Restore | Platform operator | Backup manifest, recovered IDs and digest equality |
Внутри одного major event subject действует FULL_TRANSITIVE compatibility и consumer-first rollout; любое исключение оформляется как reviewed migration, а не registry override в incident.
- Удаление или переименование field, изменение type/format, добавление required field, сужение range, изменение enum semantics и изменение event identity считаются breaking.
- Current payload schemas используют additionalProperties=false. Поэтому даже новый optional field отвергается strict old consumer и не считается автоматически backward-compatible.
- Breaking evolution получает new major topic и новый subject. Producer overlap публикует обе версии только после capacity, dedupe и reconciliation review; old topic остаётся до подтверждённого consumer migration.
- Frozen v1 и v2 на одном существующем topic являются documented migration exception: consumer dispatches exact event_type и поддерживает overlap. Этот precedent не разрешает новые mixed-version migrations без approval.
- Compatibility gate проверяет все исторические promoted schemas, schema-valid examples и current consumer matrix. Registry setting нельзя ослаблять вручную для прохождения release.
- Enum расширение рассматривается отдельно: consumer должен иметь explicit UNKNOWN/quarantine path либо новая value требует major version. Silent default запрещён.
| Change | Default decision | Release path |
|---|---|---|
| Description/example only | Compatible after validation | Same subject |
| Optional field with strict old consumer | Breaking in current contract | Consumer-first migration or new major |
| New required field | Breaking | New major topic and subject |
| Removed/narrowed field or type | Breaking | New major topic and subject |
| Enum value or meaning changed | Breaking unless UNKNOWN path is proven | Compatibility review or new major |
| Auth/topic/key semantics changed | Breaking operational contract | New version plus migration runbook |
Production monitoring измеряет не только records lag, но и lag age до durable consumer outcome; маленькое число records может скрывать старый или остановленный partition.
- Для каждого approved consumer group/topic/partition собираются log-end offset, committed offset, records lag, oldest-unprocessed lag age, last successful process time и fetch/error rate.
- Отдельно наблюдаются paused partition count/reason, rebalance duration/count, assigned partitions, commit failures, inbox conflicts, quarantine/DLQ backlog и schema lookup failures.
- Alert строится по workload, environment, consumer group, topic и bounded outcome. tenant_id, event_id, partition key, raw error и customer reference не являются metric label.
- Dashboard показывает max и p95 lag age, total records lag, zero-throughput с растущим end offset, partition skew и связь с producer outbox backlog.
- Warning открывается до исчерпания freshness SLO; critical page срабатывает при projected SLO breach, stopped consumption, unknown schema или growing quarantine.
- Planned replay использует отдельный group и annotation, но не скрывает lag рабочего consumer. Silence time-bounded, approved и остаётся в audit evidence.
| Signal | Interpretation | Required action |
|---|---|---|
| records lag grows | Consumer throughput below ingress | Scale or throttle after bottleneck evidence |
| lag age grows with few records | Old record is blocked or partition paused | Inspect exact partition and disposition |
| zero fetch + moving log end | Consumer stopped or unauthorized | Page owner; verify auth and assignment |
| rebalance storm | Unstable membership or processing timeout | Stabilize group before offset changes |
| quarantine grows | Integrity/schema/application failure | Pause affected partition and remediate |
SLO фиксирует измеряемую границу от committed fact до broker acknowledgement и durable consumer outcome; local alert threshold или successful smoke не становится Production SLO.
- Окно оценки — rolling 30 days; planned maintenance исключается только по заранее утверждённому change window, а incident silence не удаляет bad events из SLI.
- Error budget считается раздельно для publication, broker availability и каждого critical consumer. Fast/slow burn alerts имеют owner, page route и linked runbook.
- Invalid record считается успешным только после durable quarantine disposition в установленный срок; silent skip, manual offset jump или loss исключать из denominator запрещено.
- Цели активируются только после 30-day baseline, security-on load test, broker/AZ failover и consumer replay drill. До этого это design targets, не измеренная гарантия.
| SLI | Target | Measurement boundary |
|---|---|---|
| Publication freshness | 99.9% within 60 секунд | PostgreSQL commit time → Kafka durable ACK |
| Critical consumer freshness | 99.9% within 120 секунд | Broker append time → inbox PROCESSED or durable quarantine |
| Authorized broker API availability | 99.95% monthly | Successful eligible produce/fetch requests |
| Integrity and silent loss | 100%; any mismatch is incident | Committed event set → broker/consumer reconciliation |
Production replay является maker-checker change-controlled восстановлением exact retained records; он сохраняет event identity, не создаёт новый business fact и не обходит normal consumer authorization.
Открыть replay request
Maker фиксирует incident/change ticket, reason, data classification, consumer owner и ожидаемый reconciliation result.
Собрать replay manifest
Зафиксировать immutable source cluster, topic, partitions, start/end offsets, event-count digest, schema snapshot и consumer build digest.
Получить независимый approval
Checker отличается от maker и подтверждает range, ACL, retention availability, side-effect mode, quota и rollback/stop conditions.
Запустить shadow consumer group
External effects disabled либо защищены тем же durable idempotency key; рабочие group offsets не изменяются.
Выполнять bounded replay
Throttle не превышает security-on load-test limit; per-partition checkpoints и new/duplicate/quarantined/failed counters сохраняются.
Остановиться на conflict
Digest mismatch, unknown schema, authorization drift или unexpected side effect немедленно останавливает affected partition.
Выполнить reconciliation
Сверить source event set, inbox outcomes, rebuilt projection, quarantine и external-effect journal до любого cutover.
Закрыть change
Подписать result manifest, сохранить evidence, удалить temporary ACL/group по approved cleanup и зафиксировать остаточный risk.
version: certarail.kafka-replay.v1
request_id: replay-<ticket-id>
reason: <approved incident or rebuild reason>
maker: <workload-or-operator-principal>
checker: <different approving principal>
source_cluster: <immutable cluster identity>
consumer:
name: <consumer-name>
build_digest: sha256:<image-digest>
shadow_group: <new-isolated-group>
range:
topic: <approved-topic>
partitions:
- partition: 0
start_offset: 1200
end_offset: 1750
schemas:
registry_snapshot_sha256: <sha256>
effects:
mode: disabled-or-idempotent
limits:
max_records_per_second: <load-tested-limit>
evidence:
expected_event_count: 551
expected_event_set_sha256: <sha256>
output_manifest_uri: <approved-evidence-location>Production topology использует минимум три brokers в независимых failure domains, RF=3, min.insync.replicas=2 и acks=all; DR считается проверенным только после measured restore/failover drill.
Зафиксировать baseline
Сохранить topic configs, ACL/schema snapshots, cluster IDs, offsets, outbox head, event-set digest, current lag и SLO burn.
Изолировать failure domain
Quarterly убрать broker/AZ; semiannual изолировать primary cluster/region. Test controller не должен зависеть от затронутого domain.
Проверить protected write behavior
Writes продолжаются только при required ISR; unclean leader election и silent downgrade durability запрещены.
Promote DR path
Переключить reviewed bootstrap endpoint, restore Schema Registry and consumer offsets, then rotate cluster authority epoch to prevent split-brain clients.
Drain authoritative backlog
Outbox publishes committed facts through the new cluster; duplicates converge through stable event_id and inbox.
Reconcile and measure
Compare source/outbox/broker/inbox event sets and record actual RPO, RTO, lag, duplicates, quarantine and error-budget impact.
Return without split brain
Old cluster rejoins only after fencing and re-seed; evidence owner signs findings and tracks every failed acceptance item.
| Failure domain | Target RPO | Target RTO | Frequency |
|---|---|---|---|
| Single broker or AZ | 0 acknowledged records | ≤ 5 min | Quarterly and after topology change |
| Primary Kafka region | ≤ 5 min replicated log; PostgreSQL facts remain authoritative | ≤ 60 min | Semiannual |
| Schema Registry | ≤ 5 min metadata | ≤ 60 min | Quarterly restore |
| Consumer offsets/checkpoints | ≤ 5 min | ≤ 60 min plus replay | Quarterly restore |
Каждая security, schema, replay и recovery операция оставляет privacy-minimised, tamper-evident manifest, позволяющий независимо восстановить кто, что, когда и по какому approval изменил.
- Release evidence включает broker/controller version and image digest, cluster ID, listener/TLS policy, topic configs, replication/ISR state, quotas и payload-free health result.
- Access evidence включает normalized principal map, ACL snapshot/diff, denied cross-tenant canary, group ownership, temporary elevation expiry и maker-checker approval.
- Schema evidence включает registry subject/schema ID, canonical $id, schema SHA-256, FULL_TRANSITIVE result, example validation, consumer matrix и promotion actor.
- Operational evidence включает lag/SLO report, alert delivery drill, replay manifest/result, DR test timeline, measured RPO/RTO и reconciliation event-set digest.
- Credentials, OAuth token, private key, truststore password, raw payload, tenant PII и unrestricted error text не входят в evidence или logs.
- Manifest canonicalized, hashed with SHA-256, signed by approved workload/operator identity, timestamped and anchored in bank-managed WORM/SIEM storage with retention/legal-hold policy.
- Evidence has an owner, classification, retention_until, verification status and immutable link to incident/change/release. Missing artifact blocks Production activation or closes drill as failed.
| Evidence class | Required artifacts | Fail gate |
|---|---|---|
| Transport and identity | TLS scan, CA/principal map, rotation and negative auth tests | Plaintext or fallback path exists |
| Authorization | ACL snapshot/diff, cross-tenant deny and admin separation | Wildcard or unowned grant |
| Schema | Registry IDs/digests and schema compatibility report | Unregistered or incompatible schema |
| Reliability | Lag dashboard, SLO burn and alert receiver drill | No consumer freshness signal |
| Recovery | Replay/DR manifests, event-set reconciliation and actual RPO/RTO | Loss, split brain or unresolved mismatch |
Статус в каталоге берётся из operation и x-certarail runtime/publication metadata. Поэтому наличие schema, topic или Kafka key не заявляет существование publisher, consumer либо доставки.
Versioned production design фиксирует обязательные controls, SLO targets, replay/DR procedures и evidence pack. Он не меняет Local server в canonical AsyncAPI и не включает отсутствующие runtime integrations.
Нашли неточность?
Участники private repository могут предложить правку через reviewed pull request. Остальные пользователи — отправить техническое сообщение без credentials и чувствительных данных.