-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmanifest.json
More file actions
253 lines (253 loc) · 19.6 KB
/
Copy pathmanifest.json
File metadata and controls
253 lines (253 loc) · 19.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
{
"schema_version": 1,
"description": "Cross-SDK conformance cases. Every BabelQueue SDK core must satisfy these against the canonical wire envelope. Per-message fields (meta.id, trace_id, meta.created_at) are intrinsically unique and are NOT asserted by value.",
"cases": [
{
"name": "order-created",
"file": "fixtures/order-created.json",
"valid": true,
"description": "A normal produced envelope.",
"expect": {
"urn": "urn:babel:orders:created",
"data": { "order_id": 1042 },
"attempts": 0,
"lang": "php",
"schema_version": 1
}
},
{
"name": "urn-alias",
"file": "fixtures/urn-alias.json",
"valid": true,
"description": "Consumers MUST accept 'urn' as an inbound alias for 'job'.",
"expect": {
"urn": "urn:babel:orders:created",
"data": { "order_id": 1042 },
"attempts": 0,
"lang": "go",
"schema_version": 1
}
},
{
"name": "dead-lettered",
"file": "fixtures/dead-lettered.json",
"valid": true,
"description": "A dead-lettered message: original preserved + additive dead_letter block.",
"expect": {
"urn": "urn:babel:orders:created",
"data": { "order_id": 1042 },
"attempts": 3,
"lang": "php",
"schema_version": 1,
"dead_letter": { "reason": "failed", "original_queue": "orders" }
}
},
{
"name": "unicode-and-numbers",
"file": "fixtures/unicode-and-numbers.json",
"valid": true,
"description": "UTF-8 strings, integers, an exact float, boolean and null round-trip identically.",
"expect": {
"urn": "urn:babel:catalog:item.indexed",
"data": { "title": "Café — naïve ☕", "qty": 7, "price_cents": 1299, "ratio": 0.5, "active": true, "note": null },
"attempts": 2,
"lang": "python",
"schema_version": 1
}
},
{
"name": "invalid-unknown-schema-version",
"file": "fixtures/invalid-unknown-schema-version.json",
"valid": false,
"reason": "meta.schema_version is not a version this SDK supports"
},
{
"name": "invalid-missing-urn",
"file": "fixtures/invalid-missing-urn.json",
"valid": false,
"reason": "no 'job' or 'urn' — the message has no identity"
}
],
"sqs": {
"description": "Amazon SQS binding conformance (broker-bindings.md §3). Every SDK that ships an SQS transport must satisfy these. The envelope body stays byte-identical (the 'cases' above); these lock the native projection + reconciliation the binding adds. Per-message values reuse fixtures/order-created.json so the expected attributes are deterministic.",
"attribute_projection": {
"description": "On produce, the transport MUST project these native MessageAttributes from the envelope (a redundant, routable view of the body; ids/strings are DataType String, counters are DataType Number). Applies to every SQS-producing SDK.",
"envelope_file": "fixtures/order-created.json",
"message_attributes": {
"bq-job": { "DataType": "String", "StringValue": "urn:babel:orders:created" },
"bq-trace-id": { "DataType": "String", "StringValue": "7b3f9c2a-e41d-4f88-9b2a-1c0d5e6f7a8b" },
"bq-message-id": { "DataType": "String", "StringValue": "f1e2d3c4-b5a6-4789-90ab-cdef01234567" },
"bq-schema-version": { "DataType": "Number", "StringValue": "1" },
"bq-source-lang": { "DataType": "String", "StringValue": "php" },
"bq-created-at": { "DataType": "Number", "StringValue": "1749132727000" }
}
},
"attempts_reconciliation": {
"description": "On consume, attempts = max(body.attempts, ApproximateReceiveCount - 1): a first delivery reads 0, an absent/garbage count is ignored, a runtime-incremented count is never lowered. Applies to SDKs that reconcile the envelope body on consume (the framework-less/runtime transports). A drop-in driver that surfaces the broker's native delivery count instead (e.g. Laravel's SqsJob.attempts() = ApproximateReceiveCount) is exempt — it documents that divergence.",
"cases": [
{ "name": "first-delivery", "body_attempts": 0, "approximate_receive_count": "1", "expected_attempts": 0 },
{ "name": "third-delivery", "body_attempts": 0, "approximate_receive_count": "3", "expected_attempts": 2 },
{ "name": "native-exceeds-body", "body_attempts": 2, "approximate_receive_count": "5", "expected_attempts": 4 },
{ "name": "never-lower-runtime", "body_attempts": 5, "approximate_receive_count": "1", "expected_attempts": 5 },
{ "name": "garbage-count-ignored", "body_attempts": 4, "approximate_receive_count": "not-a-number", "expected_attempts": 4 },
{ "name": "absent-count", "body_attempts": 3, "approximate_receive_count": null, "expected_attempts": 3 }
]
}
},
"asb": {
"description": "Azure Service Bus binding conformance (broker-bindings.md §4). Every SDK that ships an ASB transport must satisfy these. The envelope body stays byte-identical (the 'cases' above); these lock the native projection + reconciliation the binding adds. Per-message values reuse fixtures/order-created.json so the expected projection is deterministic.",
"property_projection": {
"description": "On produce, the transport MUST project these native Service Bus message fields from the envelope: Subject = job (the URN), CorrelationId = trace_id, MessageId = meta.id, ContentType = application/json, plus the bq- ApplicationProperties as native AMQP-typed values (numbers stay numbers, not strings). Applies to every ASB-producing SDK.",
"envelope_file": "fixtures/order-created.json",
"message": {
"subject": "urn:babel:orders:created",
"correlation_id": "7b3f9c2a-e41d-4f88-9b2a-1c0d5e6f7a8b",
"message_id": "f1e2d3c4-b5a6-4789-90ab-cdef01234567",
"content_type": "application/json"
},
"application_properties": {
"bq-schema-version": 1,
"bq-source-lang": "php",
"bq-created-at": 1749132727000
}
},
"attempts_reconciliation": {
"description": "On consume, attempts = max(body.attempts, DeliveryCount - 1): a first delivery (DeliveryCount 1) reads 0, a runtime-incremented body count is never lowered, and DeliveryCount <= 1 leaves the body's own count untouched (the runtime retries by republishing with attempts+1). DeliveryCount is the native 1-based ASB redelivery counter; the rule is identical across the native-consumer SDKs (.NET/Java/Node) and the Transport+App SDKs (Python/Go).",
"cases": [
{ "name": "first-delivery", "body_attempts": 0, "delivery_count": 1, "expected_attempts": 0 },
{ "name": "third-delivery", "body_attempts": 0, "delivery_count": 3, "expected_attempts": 2 },
{ "name": "native-exceeds-body", "body_attempts": 2, "delivery_count": 5, "expected_attempts": 4 },
{ "name": "never-lower-runtime", "body_attempts": 5, "delivery_count": 2, "expected_attempts": 5 },
{ "name": "first-delivery-keeps-body", "body_attempts": 4, "delivery_count": 1, "expected_attempts": 4 },
{ "name": "zero-count-keeps-body", "body_attempts": 3, "delivery_count": 0, "expected_attempts": 3 }
]
}
},
"pulsar": {
"description": "Apache Pulsar binding conformance (broker-bindings.md §5). Every SDK that ships a Pulsar transport must satisfy these. The envelope body stays byte-identical (the 'cases' above); these lock the native projection + reconciliation the binding adds. Per-message values reuse fixtures/order-created.json so the expected projection is deterministic.",
"property_projection": {
"description": "On produce, the transport MUST project these native Pulsar message properties from the envelope, all string->string (Pulsar properties are string-typed, so numbers are stringified): bq-job = job (the URN), bq-trace-id = trace_id, bq-message-id = meta.id, bq-schema-version = str(meta.schema_version), bq-source-lang = meta.lang, bq-attempts = str(attempts). The payload is the byte-identical envelope; the native publishTime mirrors meta.created_at (broker-set, body authoritative). Applies to every Pulsar-producing SDK.",
"envelope_file": "fixtures/order-created.json",
"properties": {
"bq-job": "urn:babel:orders:created",
"bq-trace-id": "7b3f9c2a-e41d-4f88-9b2a-1c0d5e6f7a8b",
"bq-message-id": "f1e2d3c4-b5a6-4789-90ab-cdef01234567",
"bq-schema-version": "1",
"bq-source-lang": "php",
"bq-attempts": "0"
}
},
"attempts_reconciliation": {
"description": "On consume, attempts = max(body.attempts, RedeliveryCount): Pulsar's RedeliveryCount is 0-based (0 on first delivery) so it maps directly with NO -1, a runtime-incremented body count is never lowered, and RedeliveryCount 0 leaves the body's own count untouched (the runtime retries by republishing with attempts+1, which resets the broker's redelivery count to 0). The rule is identical across the native-consumer SDKs (.NET/Java/Node) and the Transport+App SDKs (Python/Go).",
"cases": [
{ "name": "first-delivery", "body_attempts": 0, "redelivery_count": 0, "expected_attempts": 0 },
{ "name": "third-delivery", "body_attempts": 0, "redelivery_count": 2, "expected_attempts": 2 },
{ "name": "native-exceeds-body", "body_attempts": 2, "redelivery_count": 5, "expected_attempts": 5 },
{ "name": "never-lower-runtime", "body_attempts": 5, "redelivery_count": 1, "expected_attempts": 5 },
{ "name": "first-delivery-keeps-body", "body_attempts": 4, "redelivery_count": 0, "expected_attempts": 4 },
{ "name": "native-equals-body", "body_attempts": 3, "redelivery_count": 3, "expected_attempts": 3 }
]
}
},
"kafka": {
"description": "Apache Kafka binding conformance (broker-bindings.md §6). Every SDK that ships a Kafka transport must satisfy these. The record value is the byte-identical envelope (the 'cases' above); these lock the bq- header projection + the bq-attempts-authoritative reconciliation the binding adds. Per-message values reuse fixtures/order-created.json so the expected projection is deterministic.",
"property_projection": {
"description": "On produce, the transport MUST project these native Kafka record headers from the envelope, all UTF-8 byte strings (Kafka headers are bytes, so integers are stringified): bq-job = job (the URN), bq-trace-id = trace_id, bq-message-id = meta.id, bq-schema-version = str(meta.schema_version), bq-source-lang = meta.lang, bq-attempts = str(attempts). The record value is the byte-identical envelope and the record timestamp mirrors meta.created_at (Unix ms). Applies to every Kafka-producing SDK.",
"envelope_file": "fixtures/order-created.json",
"headers": {
"bq-job": "urn:babel:orders:created",
"bq-trace-id": "7b3f9c2a-e41d-4f88-9b2a-1c0d5e6f7a8b",
"bq-message-id": "f1e2d3c4-b5a6-4789-90ab-cdef01234567",
"bq-schema-version": "1",
"bq-source-lang": "php",
"bq-attempts": "0"
}
},
"attempts_reconciliation": {
"description": "On consume, attempts = the bq-attempts header when present (AUTHORITATIVE — Kafka has no native delivery count), else the body's own attempts (the fallback for a non-BabelQueue producer). This is NOT a max: the header overrides the body even when lower. A null header_attempts means the header is absent. The rule is identical across all five Kafka SDKs.",
"cases": [
{ "name": "header-matches-body", "body_attempts": 0, "header_attempts": 0, "expected_attempts": 0 },
{ "name": "header-present", "body_attempts": 0, "header_attempts": 2, "expected_attempts": 2 },
{ "name": "header-absent-falls-back", "body_attempts": 3, "header_attempts": null, "expected_attempts": 3 },
{ "name": "header-authoritative-overrides", "body_attempts": 5, "header_attempts": 1, "expected_attempts": 1 },
{ "name": "header-higher-than-body", "body_attempts": 1, "header_attempts": 4, "expected_attempts": 4 }
]
}
},
"artemis": {
"description": "Apache ActiveMQ Artemis binding conformance (broker-bindings.md §7). Every SDK that ships an Artemis transport must satisfy these. The message body stays byte-identical (the 'cases' above); these lock the AMQP-1.0 projection + reconciliation the binding adds. Per-message values reuse fixtures/order-created.json so the expected projection is deterministic.",
"property_projection": {
"description": "On produce, the transport MUST project the envelope onto the AMQP a JMS peer reads: the x-opt-jms-type message annotation = job (the URN, the AMQP-JMS mapping of JMSType), correlation-id = trace_id (JMSCorrelationID), creation-time = meta.created_at (JMSTimestamp, Unix ms), plus the bq_ application properties, all string-valued. The names use UNDERSCORES (bq_schema_version, not bq-schema-version): JMS property names must be valid Java identifiers (no hyphens), and the Java binding consumes/produces over JMS, so every Artemis SDK uses the JMS-legal underscore form for cross-protocol parity. bq_schema_version = str(meta.schema_version), bq_source_lang = meta.lang, bq_attempts = str(attempts), bq_app_id = 'babelqueue'. The body is the byte-identical envelope. Unlike Kafka/Pulsar, the URN / trace_id / message-id are NOT bq_ properties — they ride the JMS-native slots. Applies to every Artemis-producing SDK.",
"envelope_file": "fixtures/order-created.json",
"jms_type_annotation": "x-opt-jms-type",
"jms_type": "urn:babel:orders:created",
"correlation_id": "7b3f9c2a-e41d-4f88-9b2a-1c0d5e6f7a8b",
"properties": {
"bq_schema_version": "1",
"bq_source_lang": "php",
"bq_attempts": "0",
"bq_app_id": "babelqueue"
}
},
"attempts_reconciliation": {
"description": "On consume, attempts = max(body.attempts, delivery-count): the AMQP delivery-count header is 0-based (0 on first delivery) so it maps directly with NO -1, a runtime-incremented body count is never lowered, and delivery-count 0 leaves the body's own count untouched (the runtime/App retries by republishing with attempts+1, which resets the broker's delivery-count to 0). The Java JMS binding reads the 1-based JMSXDeliveryCount and subtracts 1, arriving at the same 0-based attempts. The rule is identical across the native-consumer SDKs (.NET/Java/Node) and the Transport+App SDKs (Python/Go).",
"cases": [
{ "name": "first-delivery", "body_attempts": 0, "delivery_count": 0, "expected_attempts": 0 },
{ "name": "third-delivery", "body_attempts": 0, "delivery_count": 2, "expected_attempts": 2 },
{ "name": "native-exceeds-body", "body_attempts": 2, "delivery_count": 5, "expected_attempts": 5 },
{ "name": "never-lower-runtime", "body_attempts": 5, "delivery_count": 1, "expected_attempts": 5 },
{ "name": "first-delivery-keeps-body", "body_attempts": 4, "delivery_count": 0, "expected_attempts": 4 },
{ "name": "native-equals-body", "body_attempts": 3, "delivery_count": 3, "expected_attempts": 3 }
]
}
},
"redis": {
"description": "Redis binding conformance (broker-bindings.md §1 — the reliable-queue list pattern). Redis lists carry no native metadata, so there is NO property projection: the queue element IS the canonical envelope JSON, byte-for-byte, with NO wrapping (unlike BullMQ's job structure) and NO added fields. The reserve/ack reliability (Laravel's reserved-sorted-set vs the App runtimes' processing-list) and attempts handling are SDK-runtime-specific and proven by the live interop round-trip, not a static fixture. The one cross-SDK invariant locked here is payload identity.",
"payload_identity": {
"description": "On produce, the transport MUST store the envelope JSON byte-for-byte as the queue element (RPUSH / equivalent); on reserve it MUST return that element unchanged — no wrapping, no envelope mutation, no added metadata. A §1 SDK round-trips fixtures/order-created.json through produce→reserve and gets back the identical bytes. Applies to every Redis-producing SDK.",
"envelope_file": "fixtures/order-created.json"
}
},
"rabbitmq": {
"description": "RabbitMQ binding conformance (broker-bindings.md §2 — AMQP 0-9-1). The envelope body stays byte-identical (the 'cases' above); this locks the native AMQP-0-9-1 property projection every producer emits. NOTE: consume-side attempts handling is NOT in scope here because it is runtime-specific — the Laravel driver reads the authoritative x-attempts header (§2.5), while the App-runtime SDKs (Go/Python) keep attempts in the body and republish with attempts+1 (the x-attempts header is then a redundant produce-side mirror). Both stay consistent because Laravel increments the body's attempts too. Per-message values reuse fixtures/order-created.json.",
"property_projection": {
"description": "On produce, the transport MUST project these native AMQP 0-9-1 properties + headers from the envelope. Header values are NATIVE-typed (AMQP 0-9-1 field-tables carry typed values — integers stay integers, NOT stringified like Kafka/Pulsar): type = job (the URN, route on it without reading the body), correlation_id = trace_id, message_id = meta.id, app_id = 'babelqueue', content_type = 'application/json', header x-schema-version = meta.schema_version (number), x-source-lang = meta.lang, x-attempts = attempts (number). The body is the byte-identical envelope. Applies to every RabbitMQ-producing SDK.",
"envelope_file": "fixtures/order-created.json",
"properties": {
"type": "urn:babel:orders:created",
"correlation_id": "7b3f9c2a-e41d-4f88-9b2a-1c0d5e6f7a8b",
"message_id": "f1e2d3c4-b5a6-4789-90ab-cdef01234567",
"app_id": "babelqueue",
"content_type": "application/json"
},
"headers": {
"x-schema-version": 1,
"x-source-lang": "php",
"x-attempts": 0
}
}
},
"payload_schema": {
"description": "Per-URN data schema validation (ADR-0024). Each case validates `data` against `schema`; every SDK's optional payload validator (Go schema, PHP BabelQueue\\Schema, Python babelqueue.schema) MUST agree on `valid`. The wire envelope stays frozen — this governs the data block only, and is opt-in (consumers/producers without a registered schema skip it).",
"schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["order_id"],
"properties": {
"order_id": { "type": "integer", "minimum": 1 },
"amount": { "type": "number", "minimum": 0 },
"currency": { "enum": ["USD", "EUR", "TRY"] }
},
"additionalProperties": false
},
"cases": [
{ "name": "valid-minimal", "valid": true, "data": { "order_id": 1042 } },
{ "name": "valid-full", "valid": true, "data": { "order_id": 1042, "amount": 99.9, "currency": "USD" } },
{ "name": "invalid-missing-required", "valid": false, "data": { "amount": 10 } },
{ "name": "invalid-wrong-type", "valid": false, "data": { "order_id": "x" } },
{ "name": "invalid-additional-property", "valid": false, "data": { "order_id": 1, "extra": true } },
{ "name": "invalid-enum", "valid": false, "data": { "order_id": 1, "currency": "GBP" } },
{ "name": "invalid-below-minimum", "valid": false, "data": { "order_id": 0 } }
]
}
}