|
166 | 166 | { "name": "garbage-count-ignored", "body_attempts": 4, "approximate_receive_count": "not-a-number", "expected_attempts": 4 }, |
167 | 167 | { "name": "absent-count", "body_attempts": 3, "approximate_receive_count": null, "expected_attempts": 3 } |
168 | 168 | ] |
| 169 | + }, |
| 170 | + "schema_version_gate": { |
| 171 | + "description": "Consumer-side schema-version gate (broker-bindings.md §3.7). The consumer MUST read the SQS message attribute `bq-schema-version` BEFORE decoding the body. Blank means the empty string or only ASCII whitespace (space, \\t, \\n, \\v, \\f, \\r) - the same set in every SDK (GR-5): an absent or blank value falls through to the normal decode path (the body meta.schema_version check still applies afterwards). Otherwise the value is compared as-is, never trimmed: exactly \"1\" decodes, and anything else is rejected WITHOUT decoding (poison/DLQ path) - including \"2\", \"01\", \"x\", values padded with whitespace (\" 1\", \"1 \"), and values made of non-ASCII whitespace (NBSP U+00A0, U+0085, U+3000) or a BOM (U+FEFF), which are NOT blank. A unicode-aware blank check or a trim() before comparing fails these cases. Each case supplies the property 'value' (or 'absent': true) and the 'expect'ed verdict; the message body is always the valid envelope in 'fixture', so a 'reject' verdict can only come from the property gate.", |
| 172 | + "property": "bq-schema-version", |
| 173 | + "fixture": "fixtures/order-created.json", |
| 174 | + "cases": [ |
| 175 | + { "value": "1", "expect": "decode" }, |
| 176 | + { "value": "", "expect": "decode" }, |
| 177 | + { "value": " \t", "expect": "decode" }, |
| 178 | + { "value": " ", "expect": "decode" }, |
| 179 | + { "value": "\n\u000b\f\r", "expect": "decode" }, |
| 180 | + { "absent": true, "expect": "decode" }, |
| 181 | + { "value": "2", "expect": "reject" }, |
| 182 | + { "value": "01", "expect": "reject" }, |
| 183 | + { "value": "x", "expect": "reject" }, |
| 184 | + { "value": " 1", "expect": "reject" }, |
| 185 | + { "value": "1 ", "expect": "reject" }, |
| 186 | + { "value": "\t1\n", "expect": "reject" }, |
| 187 | + { "value": "\u00a0", "expect": "reject" }, |
| 188 | + { "value": "\u0085", "expect": "reject" }, |
| 189 | + { "value": "\u3000", "expect": "reject" }, |
| 190 | + { "value": "\ufeff", "expect": "reject" }, |
| 191 | + { "value": "\u00a01", "expect": "reject" }, |
| 192 | + { "value": "1\u3000", "expect": "reject" } |
| 193 | + ] |
169 | 194 | } |
170 | 195 | }, |
171 | 196 | "asb": { |
|
195 | 220 | { "name": "first-delivery-keeps-body", "body_attempts": 4, "delivery_count": 1, "expected_attempts": 4 }, |
196 | 221 | { "name": "zero-count-keeps-body", "body_attempts": 3, "delivery_count": 0, "expected_attempts": 3 } |
197 | 222 | ] |
| 223 | + }, |
| 224 | + "schema_version_gate": { |
| 225 | + "description": "Consumer-side schema-version gate (broker-bindings.md §4.7). The consumer MUST read the ASB ApplicationProperties entry `bq-schema-version` BEFORE decoding the body. Blank means the empty string or only ASCII whitespace (space, \\t, \\n, \\v, \\f, \\r) - the same set in every SDK (GR-5): an absent or blank value falls through to the normal decode path (the body meta.schema_version check still applies afterwards). Otherwise the value is compared as-is, never trimmed: exactly \"1\" decodes, and anything else is rejected WITHOUT decoding (poison/DLQ path) - including \"2\", \"01\", \"x\", values padded with whitespace (\" 1\", \"1 \"), and values made of non-ASCII whitespace (NBSP U+00A0, U+0085, U+3000) or a BOM (U+FEFF), which are NOT blank. A unicode-aware blank check or a trim() before comparing fails these cases. Each case supplies the property 'value' (or 'absent': true) and the 'expect'ed verdict; the message body is always the valid envelope in 'fixture', so a 'reject' verdict can only come from the property gate. ASB application properties are AMQP-typed, so the JSON-number cases (a number, not a string) stand for a native AMQP number: any AMQP integral type (byte/short/int/long, signed or unsigned) with value 1 decodes; any other numeric value (2, 0, non-integral 1.5) and any other type rejects. JSON numbers carry no width, so a runner MUST exercise integral 1 and 2 at both int32 and int64 width. A floating-point 1.0 is unspecified and not tested.", |
| 226 | + "property": "bq-schema-version", |
| 227 | + "fixture": "fixtures/order-created.json", |
| 228 | + "cases": [ |
| 229 | + { "value": "1", "expect": "decode" }, |
| 230 | + { "value": "", "expect": "decode" }, |
| 231 | + { "value": " \t", "expect": "decode" }, |
| 232 | + { "value": " ", "expect": "decode" }, |
| 233 | + { "value": "\n\u000b\f\r", "expect": "decode" }, |
| 234 | + { "absent": true, "expect": "decode" }, |
| 235 | + { "value": "2", "expect": "reject" }, |
| 236 | + { "value": "01", "expect": "reject" }, |
| 237 | + { "value": "x", "expect": "reject" }, |
| 238 | + { "value": " 1", "expect": "reject" }, |
| 239 | + { "value": "1 ", "expect": "reject" }, |
| 240 | + { "value": "\t1\n", "expect": "reject" }, |
| 241 | + { "value": "\u00a0", "expect": "reject" }, |
| 242 | + { "value": "\u0085", "expect": "reject" }, |
| 243 | + { "value": "\u3000", "expect": "reject" }, |
| 244 | + { "value": "\ufeff", "expect": "reject" }, |
| 245 | + { "value": "\u00a01", "expect": "reject" }, |
| 246 | + { "value": "1\u3000", "expect": "reject" }, |
| 247 | + { "value": 1, "expect": "decode" }, |
| 248 | + { "value": 2, "expect": "reject" }, |
| 249 | + { "value": 0, "expect": "reject" }, |
| 250 | + { "value": 1.5, "expect": "reject" } |
| 251 | + ] |
198 | 252 | } |
199 | 253 | }, |
200 | 254 | "pulsar": { |
|
221 | 275 | { "name": "first-delivery-keeps-body", "body_attempts": 4, "redelivery_count": 0, "expected_attempts": 4 }, |
222 | 276 | { "name": "native-equals-body", "body_attempts": 3, "redelivery_count": 3, "expected_attempts": 3 } |
223 | 277 | ] |
| 278 | + }, |
| 279 | + "schema_version_gate": { |
| 280 | + "description": "Consumer-side schema-version gate (broker-bindings.md §5.7). The consumer MUST read the Pulsar message property `bq-schema-version` BEFORE decoding the body. Blank means the empty string or only ASCII whitespace (space, \\t, \\n, \\v, \\f, \\r) - the same set in every SDK (GR-5): an absent or blank value falls through to the normal decode path (the body meta.schema_version check still applies afterwards). Otherwise the value is compared as-is, never trimmed: exactly \"1\" decodes, and anything else is rejected WITHOUT decoding (poison/DLQ path) - including \"2\", \"01\", \"x\", values padded with whitespace (\" 1\", \"1 \"), and values made of non-ASCII whitespace (NBSP U+00A0, U+0085, U+3000) or a BOM (U+FEFF), which are NOT blank. A unicode-aware blank check or a trim() before comparing fails these cases. Each case supplies the property 'value' (or 'absent': true) and the 'expect'ed verdict; the message body is always the valid envelope in 'fixture', so a 'reject' verdict can only come from the property gate.", |
| 281 | + "property": "bq-schema-version", |
| 282 | + "fixture": "fixtures/order-created.json", |
| 283 | + "cases": [ |
| 284 | + { "value": "1", "expect": "decode" }, |
| 285 | + { "value": "", "expect": "decode" }, |
| 286 | + { "value": " \t", "expect": "decode" }, |
| 287 | + { "value": " ", "expect": "decode" }, |
| 288 | + { "value": "\n\u000b\f\r", "expect": "decode" }, |
| 289 | + { "absent": true, "expect": "decode" }, |
| 290 | + { "value": "2", "expect": "reject" }, |
| 291 | + { "value": "01", "expect": "reject" }, |
| 292 | + { "value": "x", "expect": "reject" }, |
| 293 | + { "value": " 1", "expect": "reject" }, |
| 294 | + { "value": "1 ", "expect": "reject" }, |
| 295 | + { "value": "\t1\n", "expect": "reject" }, |
| 296 | + { "value": "\u00a0", "expect": "reject" }, |
| 297 | + { "value": "\u0085", "expect": "reject" }, |
| 298 | + { "value": "\u3000", "expect": "reject" }, |
| 299 | + { "value": "\ufeff", "expect": "reject" }, |
| 300 | + { "value": "\u00a01", "expect": "reject" }, |
| 301 | + { "value": "1\u3000", "expect": "reject" } |
| 302 | + ] |
224 | 303 | } |
225 | 304 | }, |
226 | 305 | "kafka": { |
|
246 | 325 | { "name": "header-authoritative-overrides", "body_attempts": 5, "header_attempts": 1, "expected_attempts": 1 }, |
247 | 326 | { "name": "header-higher-than-body", "body_attempts": 1, "header_attempts": 4, "expected_attempts": 4 } |
248 | 327 | ] |
| 328 | + }, |
| 329 | + "schema_version_gate": { |
| 330 | + "description": "Consumer-side schema-version gate (broker-bindings.md §6.7). The consumer MUST read the Kafka record header `bq-schema-version` BEFORE decoding the body. Blank means the empty string or only ASCII whitespace (space, \\t, \\n, \\v, \\f, \\r) - the same set in every SDK (GR-5): an absent or blank value falls through to the normal decode path (the body meta.schema_version check still applies afterwards). Otherwise the value is compared as-is, never trimmed: exactly \"1\" decodes, and anything else is rejected WITHOUT decoding (poison/DLQ path) - including \"2\", \"01\", \"x\", values padded with whitespace (\" 1\", \"1 \"), and values made of non-ASCII whitespace (NBSP U+00A0, U+0085, U+3000) or a BOM (U+FEFF), which are NOT blank. A unicode-aware blank check or a trim() before comparing fails these cases. Each case supplies the property 'value' (or 'absent': true) and the 'expect'ed verdict; the message body is always the valid envelope in 'fixture', so a 'reject' verdict can only come from the property gate.", |
| 331 | + "property": "bq-schema-version", |
| 332 | + "fixture": "fixtures/order-created.json", |
| 333 | + "cases": [ |
| 334 | + { "value": "1", "expect": "decode" }, |
| 335 | + { "value": "", "expect": "decode" }, |
| 336 | + { "value": " \t", "expect": "decode" }, |
| 337 | + { "value": " ", "expect": "decode" }, |
| 338 | + { "value": "\n\u000b\f\r", "expect": "decode" }, |
| 339 | + { "absent": true, "expect": "decode" }, |
| 340 | + { "value": "2", "expect": "reject" }, |
| 341 | + { "value": "01", "expect": "reject" }, |
| 342 | + { "value": "x", "expect": "reject" }, |
| 343 | + { "value": " 1", "expect": "reject" }, |
| 344 | + { "value": "1 ", "expect": "reject" }, |
| 345 | + { "value": "\t1\n", "expect": "reject" }, |
| 346 | + { "value": "\u00a0", "expect": "reject" }, |
| 347 | + { "value": "\u0085", "expect": "reject" }, |
| 348 | + { "value": "\u3000", "expect": "reject" }, |
| 349 | + { "value": "\ufeff", "expect": "reject" }, |
| 350 | + { "value": "\u00a01", "expect": "reject" }, |
| 351 | + { "value": "1\u3000", "expect": "reject" } |
| 352 | + ] |
249 | 353 | } |
250 | 354 | }, |
251 | 355 | "artemis": { |
|
273 | 377 | { "name": "first-delivery-keeps-body", "body_attempts": 4, "delivery_count": 0, "expected_attempts": 4 }, |
274 | 378 | { "name": "native-equals-body", "body_attempts": 3, "delivery_count": 3, "expected_attempts": 3 } |
275 | 379 | ] |
| 380 | + }, |
| 381 | + "schema_version_gate": { |
| 382 | + "description": "Consumer-side schema-version gate (broker-bindings.md §7.7). The consumer MUST read the Artemis application property `bq_schema_version` (underscores, ADR-0017) BEFORE decoding the body. Blank means the empty string or only ASCII whitespace (space, \\t, \\n, \\v, \\f, \\r) - the same set in every SDK (GR-5): an absent or blank value falls through to the normal decode path (the body meta.schema_version check still applies afterwards). Otherwise the value is compared as-is, never trimmed: exactly \"1\" decodes, and anything else is rejected WITHOUT decoding (poison/DLQ path) - including \"2\", \"01\", \"x\", values padded with whitespace (\" 1\", \"1 \"), and values made of non-ASCII whitespace (NBSP U+00A0, U+0085, U+3000) or a BOM (U+FEFF), which are NOT blank. A unicode-aware blank check or a trim() before comparing fails these cases. Each case supplies the property 'value' (or 'absent': true) and the 'expect'ed verdict; the message body is always the valid envelope in 'fixture', so a 'reject' verdict can only come from the property gate.", |
| 383 | + "property": "bq_schema_version", |
| 384 | + "fixture": "fixtures/order-created.json", |
| 385 | + "cases": [ |
| 386 | + { "value": "1", "expect": "decode" }, |
| 387 | + { "value": "", "expect": "decode" }, |
| 388 | + { "value": " \t", "expect": "decode" }, |
| 389 | + { "value": " ", "expect": "decode" }, |
| 390 | + { "value": "\n\u000b\f\r", "expect": "decode" }, |
| 391 | + { "absent": true, "expect": "decode" }, |
| 392 | + { "value": "2", "expect": "reject" }, |
| 393 | + { "value": "01", "expect": "reject" }, |
| 394 | + { "value": "x", "expect": "reject" }, |
| 395 | + { "value": " 1", "expect": "reject" }, |
| 396 | + { "value": "1 ", "expect": "reject" }, |
| 397 | + { "value": "\t1\n", "expect": "reject" }, |
| 398 | + { "value": "\u00a0", "expect": "reject" }, |
| 399 | + { "value": "\u0085", "expect": "reject" }, |
| 400 | + { "value": "\u3000", "expect": "reject" }, |
| 401 | + { "value": "\ufeff", "expect": "reject" }, |
| 402 | + { "value": "\u00a01", "expect": "reject" }, |
| 403 | + { "value": "1\u3000", "expect": "reject" } |
| 404 | + ] |
276 | 405 | } |
277 | 406 | }, |
278 | 407 | "redis": { |
|
0 commit comments