FEAT: Generated transport conformance tests should cover new reject/dlq/requeue paths - #4297
FEAT: Generated transport conformance tests should cover new reject/dlq/requeue paths#4297iancooper wants to merge 85 commits into
Conversation
…or, the point is not to test the details of these and foucs on the tests use of the channel and producer.
…e retirement, and rollout governance ADR 0066 extends the generated messaging-gateway provider interfaces (DLQ + invalid-message routing keys, invalid-channel read, in-memory and spy scheduler-backed producers, strongly-typed RejectionMetadataKeys), retires the HasSupportToDelayedMessages/HasSupportToDeadLetterQueue/HasSupportToRequeue opt-in gates, and deletes the broken with_delay requeue template. ADR 0067 sequences the rollout as fix-then-flip (Kafka reference, then the DLQ-ADR transports, then GCP/Azure gaps, with the 0066 flip merged last) and governs deferrals via a conformance ledger cross-checked against a mandatory linked-issue Skip convention. Design phase for spec 0036 (issue #4240). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Re-review of the design phase raised six findings. Amendments: - 0066: FR-3 scheduler moved from a standalone producer factory to channel-level members (CreateChannelWithInMemoryScheduler / CreateChannelWithSpyScheduler -> SpyScheduledChannel), since the runtime seam is the consumer that backs the channel (ADR 0039); a standalone producer is not on the channel.Requeue path. - 0066: specify what CreateChannelWithInMemoryScheduler actually requires (CommandProcessor + FireSchedulerMessage handler + external bus/producer registry + TimeProvider/id funcs/conflict policy) and record that cost under Consequences -> Negative. - 0066: note the deliberate departure from FR-1(4)/AC-1's literal wording; fix stale producer-level phrasing in the RDD role bullets; name sibling ADR 0067; broaden the mis-declared-gate inventory (Kafka mis-declares all three). - 0067: correct RMQ - it has no per-transport DLQ ADR (native DLX + universal 0047/0045), so its fix may be larger; ledger rows are now per gateway configuration (~20) rather than per test project (9); CI audit scoped to in-tree artifacts, issue-state/sign-off left to the maintainer gate. - requirements.md: amend FR-1(4), AC-1, AC-3 and the FR-3 example so the producer-vs-channel scheduler wording matches the design. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ic FR-2 Design review round 3 established that the scheduler seam (IAmAChannelFactoryWithScheduler) is implemented by only six gateways - within the generator's target set, 6 of ~20 gateway configurations. The other 14 (AWS x4, AWS.V4 x4, GCP x4, PostgreSQL, RocketMQ) take no scheduler and delay natively. A scheduler-delegation assertion would therefore fail by design on conformant transports, and giving them the seam would need a public runtime API change that C-1 forbids. It is also a mechanism assertion, which NFR-3/OOS-1 forbid. - requirements.md: FR-2 restated as mechanism-agnostic (delayed requeue redelivers after the delay, regardless of native/producer/scheduler); FR-3 withdrawn and folded into FR-2; FR-1(4) and NFR-4 withdrawn as moot; AC-2 broadened, AC-3 withdrawn; corrected the false claim that RocketMQ is the only configuration declaring HasSupportToDelayedMessages true (AWS SqsStandard declares it too, in both V3 and V4). - 0066: removed the scheduler-carrying provider members and spy types; added a "Why there is no scheduler member" section; recorded the InMemoryScheduler harness cost for the OOS-2 follow-up; specified the MT_NONE-on-empty read contract for the DLQ/invalid-channel reads so the AC-5/AC-18 negative assertions are writable; rewrote Alternative 4 against the seam-coverage evidence. - 0067: FR-3 removed as a ledger column, with the rationale that an N/A(native) cell would have reintroduced the native/non-native distinction OOS-1 rejects; gate inventory corrected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Review round 4 found two High issues, both introduced by the previous round. 1. The claim that all 14 non-scheduler configurations "delay natively" was generalized from the two that were verified (SQS, PostgreSQL) and is false for five of them. GcpPullMessageConsumer.Requeue and GcpPubSubStreamMessageConsumer.Requeue ignore the delay argument outright (the XML doc states it is "not used by Pub/Sub"; redelivery timing comes from the subscription RetryPolicy), and RocketMessageConsumer.Requeue is a no-op returning true with its ChangeInvisibleDuration call commented out pending an upstream RocketMQ C# client fix. Corrected in 0066 and requirements.md, and 0067 now seeds GCP x4 and RocketMQ into the ledger as known FR-2 non-conformances (RocketMQ flagged as a likely signed-off Deferred row, being blocked on a third-party dependency) rather than discovering them at the flip. 2. The FR-3 withdrawal had not reached Consequences, Risks, Alternative 3, References or several spots in requirements.md. Two of those were live instructions: 0066's Negative bullet told implementers a provider must supply "a scheduler-backed channel", and its 0067 reference told the ledger to track "the in-memory scheduler arm" - neither exists. Swept all stale FR-2/FR-3 pairings, corrected OOS-3's transitive-proof justification (scheduler forwarding is no longer proven transitively), and corrected the Kafka coverage-gap paragraph. Also: AC-1 reworded to match the single-CreateSubscription-with-nullable-keys shape rather than demanding "separate members"; read-member contract extended to cover reading a channel the subscription does not configure; MSSQL's HasSupportToDeadLetterQueue:false added to the mis-declared inventory; and Alternative 4 no longer conflates six gateways with six configurations. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…n scope Rewrite requirements.md on the principle that requirements assert WHAT must be true and how it is verified, while the ADRs carry WHY and HOW. The document had accumulated four rounds of inline amendment scar tissue -- withdrawal markers, "originally worded" notes, ADR rationale and a line-number evidence list -- and was carrying a running argument instead of a specification. Identifiers are not renumbered. FR-1(4), FR-3, FR-18, NFR-4, AC-3, AC-19 and OOS-6 are retired as permanent gaps, preserving ~130 cross-references in ADRs 0066/0067. decision-log.md records why each was withdrawn, keeping that deliberation out of the spec itself. Scope: all twelve src/Paramore.Brighter.MessagingGateway.* transports are in scope, not the nine that happen to have generator wiring. FR-20 onboards AzureServiceBus, MQTT and RMQ.Sync (config + provider + CI infrastructure); OOS-6, which had excluded them, is withdrawn. A missing test-configuration.json describes what the generator covers, not what a transport owes -- the same error in kind as gating a universal obligation behind a capability flag. Substantive corrections from adversarial review rounds 4-6: - FR-12/AC-12: the blanket "no template may call Requeue without a non-null TimeSpan" was self-contradictory -- FR-10 preserves the plain-requeue template and FR-15 requires Requeue(M, null). Narrowed to delayed-requeue templates. - FR-19/AC-22: delete the requeue-count-exhaustion template. Exhaustion is enforced by the message pump (Message.HandledCountReached has two callers, both in Reactor/Proactor) or by native redrive (AWS pairs requeueCount: 3 with a RedrivePolicy). Channel.Requeue counts nothing. A pump test (OOS-5) or a native-mechanism test (NFR-3/OOS-1), so not a channel obligation. - FR-1(6)/AC-1: remove bool setupDeadLetterQueue from CreateSubscription; a boolean cannot express the DLQ-only/invalid-only/neither combinations FR-1(2) needs. Breaking change across all 20 providers and both interface templates. - AC-12/AC-22 also require the 38 checked-in generated copies to go; deleting a .liquid template does not delete its generated output. - AC-20/AC-21 give NFR-2 and NFR-3 acceptance criteria. AC-20's exemption is per assertion, not per AC, so negative assertions stay unretried while the positive arrival half of the same criteria keeps its bounded retry loop. - FR-13 defines the target set; C-1 widened to cover FR-20's test-side work. Also corrects the false claim that Azure/ASB was "currently partial" (it had no test-configuration.json at all), and ADR 0066's scheduler-seam coverage, which counted four targeted gateways where all six are now targeted transports. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ign r5 The gates are no longer retired up front. Canonical templates become ungated by construction, the four legacy gated templates stay suppressed until they are deleted, and the gates and config keys retire last as a terminal cleanup. Design review round 5 found that the old sequencing could not execute: the flip gate required every ledger cell resolved before the gates were removed, but while the gates are live SkipTest suppresses any template whose filename contains requeuing, with_delay, delayed_message or dead_letter_queue -- and Kafka, the reference transport, declares all three gates false. Its canonical FR-2 and FR-9 tests could not generate until the very change the ledger was meant to authorise. The spec owner's ruling dissolved it at the root: the old tests are never wanted, before or after the canonical set exists. A gate suppressing a legacy template is doing useful work until that template is deleted, so retiring the gates first would generate precisely the tests this spec replaces, against transports not yet fixed. Requirements: - FR-10 rewritten as a four-part gating lifecycle; SkipTest consults the gates only for a closed list of four legacy template filenames, so a canonical template cannot be suppressed however it is named (naming cannot be relied on -- NFR-1 means a canonical delayed-requeue template contains both requeuing and with_delay). - FR-9 now requires a canonical delayed-send template rather than ungating the legacy one; FR-22 + AC-25 added for canonical plain requeue, the behaviour old FR-10 supplied by ungating. - FR-11 resequenced (removing a key early would ungate its legacy template); FR-12/FR-19 deletions folded into the legacy sweep; AC-10 became three ordered checkpoints; AC-9, AC-12, AC-13, AC-22 updated. - Deletion scope is 80 generated copies across four templates, not 38 across two, plus 40 generated provider-interface copies broken by FR-1(6). Requirements review round 7 (10 findings at threshold, 0 critical): - restored FR-13's truncated definition of "targeted gateway configuration" - FR-21 + AC-24: the conformance ledger now has a requirement - bounded FR-20(3) to execution against a broker, not compilation - twelve-row gateway->test-project mapping table (five pairs differ by name) - ADR 0066: stale "~20 target" phrasings, FR-20 coverage, narration removed - ADR 0067: C-1 widened to permit FR-20, DLQ ADRs cited by slug not number Design review round 5 (12 findings, 9 at threshold, 0 critical): nine remediated here, four still owed -- the ledger cannot represent a deferred FR-20 onboarding, Scope ownership of FR-19/20/21, RejectionMetadataKeys has no emitting template or namespace, and Reactor/Proactor parity needs a cell form the vocabulary lacks. Both ADR frontmatter summaries changed, so docs/adr/index.md is regenerated. Rationale for the reversal is in the spec's decision-log.md. No code changes; no phase approved. Requirements round 8 and design round 6 are owed on this text. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Urq9JbwARi9eqT2fhXsoFV
Runs the two owed review rounds on the gating-lifecycle reversal and remediates every finding from each. Requirements round 8 (8 findings, 4 at threshold, no Criticals): the reversal had been written as if "never ungated" meant "never generated". It does not — a gate suppresses a template only where declared false, and most configurations declare these gates true, so the legacy templates keep generating until deleted. Corrected in FR-9, FR-10(3), FR-22, AC-9, AC-10, AC-22, AC-25, the terminology list, both ADRs, the README and the decision log. The conflation had a hidden consequence. FR-1(6) removes setupDeadLetterQueue while the exhaustion template is still live for sixteen configurations, and that template passes the flag positionally as a bare `true` — so none of its 32 generated copies contains the parameter name, and migrating "every generated caller" by searching for it misses every broken call site. FR-1(6) now carries an interim obligation to edit the template in the same change; AC-1 records that positional call sites are not name-searchable. Spec-owner ruling: FR-15 narrows to the explicit TimeSpan.Zero argument; FR-22 owns the no-delay call in both spellings. Requeue's delay parameter is optional and null-defaulted, so Requeue(m) and Requeue(m, null) were one behaviour specified twice with two ledger columns. Design round 6 (6 findings, 3 at threshold, no Criticals): all four owed round-5 findings confirmed resolved — placeholder ledger rows per un-onboarded transport (F3), Scope ownership of FR-20/FR-21/AC-24 (F4), a Shared/ template giving RejectionMetadataKeys a home plus string.Empty for unstamped fields (F5), and partial parity as a single Deferred cell (F9). Round 6 also caught an arithmetic error introduced by the round-8 remediation: "three of the four legacy templates generate today" is four of four (6 + 36 + 6 + 32 = 80). Corrected in both ADRs, requirements.md, the README and the decision log, and annotated in the round-8 record. Also fixed: the "three gate branches" off-by-one (SkipTest has four, keyed on three gates); 0067's References entry still describing the superseded flip-then-fix sequencing; the placeholder-row vs seeding-Unknown disagreement; the AWS-family undercount in 0066's Context; and the FR-19 attribution for the last setupDeadLetterQueue caller. Neither phase is approved. Both carry unreviewed remediation. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Urq9JbwARi9eqT2fhXsoFV
…d arm Remediate requirements review round 9 (9 findings, 1 Critical) and round 10 (PASS, 0 at threshold); requirements now approved. The Critical: FR-2 required "redelivered after delay D" but AC-2 asserted only that a later receive yields the body, so a gateway that ignores the delay and redelivers immediately would pass. AC-2 gains a two-sided assertion — an immediate receive must yield MT_NONE before D and the message must arrive after it — added to AC-20's exemption list. This makes GCP x4 and RocketMQ fail as generated, matching the ledger the ADRs seed. NFR-2's bound is quantified once (500ms poll / 30s ceiling / 5s delay); FR-15 reworded to an assertable first-iteration/elapsed check; FR-21 now names the five known non-conformances. Also: provider parity tightened to both interfaces (FR-20(2)/AC-23/AC-14); FR-19 draft-narration removed; hand-written test counts corrected to 31/19/15; PostgresSQL ledger token normalised; AC-24/AC-25 reordered; Out of Scope reframed around Brighter-universal vs implementer-owned; ADRs 0066/0067 swept for the two-sided FR-2 and two-transports/five-configurations wording. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Urq9JbwARi9eqT2fhXsoFV
…esign Design review round 7 (1 finding) and round 8 (PASS, 0 findings). The round-7 finding: this session's two-sided-FR-2 edits mis-attributed RocketMQ to FR-2's before-D (immediate-MT_NONE) arm. Verified against source, RocketMQ's Requeue is a no-op leaving the message held by a 30s invisibility timeout, so it passes the before-D arm; only GCP x4 (immediate redelivery) fails it. Corrected across both ADRs, requirements (FR-2/AC-2/FR-21, AC-2 had self-contradicted), and the decision log. Approve design: ADRs 0066 and 0067 flipped Proposed -> Accepted in both frontmatter and body; docs/adr/index.md regenerated; .design-approved marker added. .requirements-approved stands (factual correction only). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Urq9JbwARi9eqT2fhXsoFV
Add the 59-task unattended (ralph) implementation plan for the universal
transport conformance tests, its adversarial review record, and the tasks
approval marker.
The plan was reviewed across four rounds; the final round PASSed with zero
findings at or above threshold. The last remediation fixed:
- Brighter.sln -> Brighter.slnx in the solution-build gates
- Phase 1 "generate everywhere" now runs a structural test, not just a build
- broker startup decoupled ({ docker compose up -d || true; }) so an infra
block still reaches the flag-and-move-on deferral gate
- Phase 3/4 multi-config tasks scoped per configuration namespace
(AWS x4, AWS.V4 x4, GCP x4, RMQ.Async x2) so a sibling cannot fail the row
- Phase 0 exhaustion-template edit made unambiguous (pass deadLetterRoutingKey)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Urq9JbwARi9eqT2fhXsoFV
- Test: conformance-status.md artifact; RALPH-VERIFY grep gate passes - Implementation: 23-row × 11-behaviour ledger, all cells Unknown; 3 placeholder rows; 5 known-FR-2-gap cells annotated - Ralph task: 1/59 Co-Authored-By: Claude Opus <noreply@anthropic.com> Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
- Test: When_gate_flags_are_false_should_skip_only_legacy_templates - Implementation: LegacyGatedTemplates allow-list gates the four legacy branches only; canonical templates ungated by construction - Ralph task: 2/59 Co-Authored-By: Claude Opus <noreply@anthropic.com> Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
…Q bool - Test: When_generating_provider_interface_should_expose_canonical_surface - Implementation: interface templates gain canonical surface (routing-key params, GetMessageFromInvalidChannel, RejectionMetadataKeys, MT_NONE contract); exhaustion templates pass deadLetterRoutingKey explicitly - Ralph task: 3/59 Co-Authored-By: Claude Opus <noreply@anthropic.com> Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
- Test: When_generating_gateway_should_emit_rejection_metadata_keys_once_per_config - Implementation: new Shared/RejectionMetadataKeys.cs.liquid + once-per-config emit in MessagingGatewayGenerator; csproj copies Shared templates; disable assembly test parallelization to prevent shared-template-dir race - Ralph task: 4/59 Co-Authored-By: Claude Opus <noreply@anthropic.com> Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
- Test: compilation gate (AC-1); dotnet build tests/Paramore.Brighter.Kafka.Tests succeeds - Implementation: both Kafka providers implement routing-key CreateSubscription, GetMessageFromInvalidChannel, RejectionMetadataKeys (PascalCase keys); regenerated Generated tree (interface copies + Shared record); no setupDeadLetterQueue remains - Ralph task: 5/59 Co-Authored-By: Claude Opus <noreply@anthropic.com> Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
- Test: compilation gate (AC-1); dotnet build tests/Paramore.Brighter.AWS.Tests succeeds - Implementation: four AWS providers (Sns/Sqs × Standard/Fifo) implement routing-key CreateSubscription, GetMessageFromInvalidChannel(+Async), RejectionMetadataKeys (SQS camelCase keys); regenerated Generated tree; no setupDeadLetterQueue remains - Ralph task: 6/59 Co-Authored-By: Claude Opus <noreply@anthropic.com> Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
- Test: compilation gate (AC-1); dotnet build tests/Paramore.Brighter.AWS.V4.Tests succeeds - Implementation: four AWS.V4 providers implement routing-key CreateSubscription, GetMessageFromInvalidChannel(+Async), RejectionMetadataKeys (SQS camelCase keys); regenerated Generated tree; no setupDeadLetterQueue remains - Ralph task: 7/59 Co-Authored-By: Claude Opus <noreply@anthropic.com> Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
- Test: compilation gate (AC-1); dotnet build tests/Paramore.Brighter.Gcp.Tests succeeds - Implementation: four GCP providers (Pull/PullOrdering/Stream/StreamOrdering) implement routing-key CreateSubscription, GetMessageFromInvalidChannel(+Async), RejectionMetadataKeys (string.Empty where unstamped); regenerated Generated tree; FR-2 gap deferred to Phase 4 - Ralph task: 8/59 Co-Authored-By: Claude Opus <noreply@anthropic.com> Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
- Test: compilation gate (AC-1); dotnet build tests/Paramore.Brighter.MSSQL.Tests succeeds - Implementation: MsSqlMessageGatewayProvider implements routing-key CreateSubscription (explicit deadLetterRoutingKey), GetMessageFromInvalidChannel(+Async), RejectionMetadataKeys; regenerated Generated tree; no setupDeadLetterQueue remains - Ralph task: 9/59 Co-Authored-By: Claude Opus <noreply@anthropic.com> Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
…tgresSQL - Test: compilation gate (AC-1); dotnet build tests/Paramore.Brighter.PostgresSQL.Tests succeeds - Implementation: PostgresMessageGatewayProvider implements routing-key CreateSubscription, GetMessageFromInvalidChannel(+Async), RejectionMetadataKeys; regenerated Generated tree; no setupDeadLetterQueue remains - Ralph task: 10/59 Co-Authored-By: Claude Opus <noreply@anthropic.com> Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
- Test: compilation gate (AC-1); dotnet build tests/Paramore.Brighter.Redis.Tests succeeds - Implementation: RedisMessageGatewayProvider implements routing-key CreateSubscription, GetMessageFromInvalidChannel(+Async), RejectionMetadataKeys (Redis camelCase keys); regenerated Generated tree; no setupDeadLetterQueue remains - Ralph task: 11/59 Co-Authored-By: Claude Opus <noreply@anthropic.com> Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
…Async - Test: compilation gate (AC-1); dotnet build tests/Paramore.Brighter.RMQ.Async.Tests succeeds - Implementation: RmqClassic/Quorum providers implement routing-key CreateSubscription, GetMessageFromInvalidChannel(+Async), RejectionMetadataKeys (string.Empty; RMQ uses native DLX); regenerated Generated tree; no setupDeadLetterQueue remains - Ralph task: 12/59 Co-Authored-By: Claude Opus <noreply@anthropic.com> Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
- Test: compilation gate (AC-1); dotnet build tests/Paramore.Brighter.RocketMQ.Tests succeeds
- Implementation: RocketMqMessageGatewayProvider implements routing-key CreateSubscription, GetMessageFromInvalidChannel(+Async), RejectionMetadataKeys; regenerated Generated tree. Last of the 20 provider migrations — no actual setupDeadLetterQueue parameter remains in the repo.
- Note: the task RALPH-VERIFY's repo-wide 'grep -rn setupDeadLetterQueue tests tools' has a benign false-positive — the only two matches are Assert.DoesNotContain("setupDeadLetterQueue", ...) absence-assertions in the FR-1 interface meta-test (task 3). RocketMQ builds clean and no real parameter/usage remains. Flagged for owner (candidate Phase 6 grep scoping).
- Ralph task: 13/59
Co-Authored-By: Claude Opus <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
… Phase 6 grep - ADR 0066: add 'Implementation notes (learned during Phase 0 rollout)' under Implementation Approach (status unchanged/Accepted) — regen needs --framework net10.0 (generator multi-targets); generator-test project must disable xUnit parallelization (shared Templates output dir race); repo-wide absence greps collide with meta-tests that name the token. - ralph-tasks.md: add --framework net10.0 to all 35 regenerate RALPH-VERIFY commands and the two Execution Notes describing them (the bare dotnet run aborts on a multi-targeted generator). - ralph-tasks.md Phase 6: harden the #NNNN reconciliation grep to exclude **/ConformanceAudit/** so audit negative-fixtures naming #NNNN don't false-positive (same class as the Phase 0 setupDeadLetterQueue grep vs the FR-1 interface meta-test's Assert.DoesNotContain). Co-Authored-By: Claude Opus <noreply@anthropic.com>
…l templates - Test: When_ledger_marks_a_cell_should_emit_skip_only_when_not_proven - Implementation: load+parse conformance ledger (repo-root resolution via walk-up), canonical-template->FR-column map, per-cell Skip value into render context; empty Skip suppressed via Liquid empty-string equality; canonical templates only - Ralph task: 14/59 (Phase 1 task 1) Co-Authored-By: Claude Opus <noreply@anthropic.com> Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
…livery and ledger-driven Skip - Test: When_generating_plain_requeue_should_emit_bounded_redelivery_both_variants - Implementation: Reactor + Proactor canonical templates emitting Requeue/RequeueAsync no-delay, asserting true and bounded 500ms-poll/30s-ceiling redelivery loop (AC-20); conditional ledger-driven Skip, no hard-coded marker. Also fixed task-1 test isolation to restore (not delete) the canonical template in Dispose - Ralph task: 15/59 (Phase 1 task 2) Co-Authored-By: Claude Opus <noreply@anthropic.com> Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
…r-D arms) - Test: When_generating_requeue_with_delay_should_emit_before_and_after_arms_both_variants - Implementation: Reactor + Proactor canonical templates passing a positive 5s TimeSpan to Requeue/RequeueAsync; before-D arm single immediate receive asserts MT_NONE (AC-20 exemption), after-D arm asserts arrival inside the bounded 500ms/30s retry loop; no mechanism assertion (AC-21); conditional ledger-driven Skip, no hard-coded marker - Ralph task: 16/59 (Phase 1 task 3) Co-Authored-By: Claude Opus <noreply@anthropic.com> Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
- Test: When_generating_zero_delay_requeue_should_emit_first_iteration_receipt_both_variants - Implementation: Reactor + Proactor canonical templates calling Requeue(M, TimeSpan.Zero) explicitly; asserts true, first-iteration receipt inside the bounded retry loop, and elapsed-under-5s (proves zero is neither special-cased nor a positive delay); conditional ledger-driven Skip, no hard-coded marker - Ralph task: 17/59 (Phase 1 task 4) Co-Authored-By: Claude Opus <noreply@anthropic.com> Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
- Test: When_generating_delivery_error_reject_should_emit_dlq_routing_both_variants - Implementation: Reactor + Proactor canonical templates proving Reject(M, DeliveryError) on a channel with a deadLetterRoutingKey routes M to the DLQ; asserts original-topic (== data topic) and rejection-reason via bounded GetMessageFromDeadLetterQueue; conditional ledger-driven Skip, no hard-coded marker - Ralph task: 18/59 (Phase 1 task 5) Co-Authored-By: Claude Opus <noreply@anthropic.com> Co-Authored-By: Claude Sonnet <noreply@anthropic.com>
|
Review: Universal transport conformance tests (1 of 2) Reviewed the hand-written surface — the six The shape of this is good: replacing three coarse capability booleans with a per-(configuration x behaviour) ledger that drives 1. GCP:
The comment on Returning private static CallSettings? BuildPullCallSettings(TimeSpan? timeOut) =>
timeOut is { } window && window > TimeSpan.Zero
? CallSettings.FromExpiration(Expiration.FromTimeout(window))
: null;Two smaller points on the same change:
2. MQTT:
Suggest signalling rather than polling — a Minor, same method: 3. FR-5 templates skip the FR-8 relaxation guard
For a native-dead-letter provider (RMQ returns 4. FR-7 assertion can be satisfied by the failure it is meant to catch In Separately, the 5. Retry bounds are split between template and provider, and the comment is wrong Every DLQ / invalid-channel template says
Consequences:
Suggest making the contract explicit: |
|
Review: Universal transport conformance tests (2 of 2) Should fix MQTT producer hardcodes The ledger fails open. All 24 Docker resource and image changes. RocketMQ baggage guard is symptom-scoped. The fix guards No unit-level regression tests for the three Nits and maintainability
PR description is stale relative to the ledger The description says MQTT / RMQ.Sync / AzureServiceBus are the only Nice work overall. Item 5 in part 1 is the one I would most want resolved before the terminal cleanup lands, since a lot of test wall-clock and a lot of the credibility of the suite rest on those bounds. |
…d copies (#4240) - Test: When_legacy_templates_deleted_should_leave_no_template_or_generated_copy - Implementation: deleted the four legacy templates in both Reactor and Proactor variants (8 .liquid files) and swept all 72 checked-in generated copies; the generator never deletes stale files, so the sweep is manual. Added the Cleanup/ gate test asserting no legacy template or generated copy remains anywhere under tests/**/Generated/, and that every delayed-requeue template passes a non-null TimeSpan to Requeue/RequeueAsync (AC-10(b), AC-12, AC-22). Dropped two now-moot assertions from the FR-1 provider-interface test that read the deleted exhaustion template's generated output. - Ralph task: 56/62 (Phase 5 step 1 — templates and copies first, gate keys last) Co-Authored-By: Claude Opus <noreply@anthropic.com> Co-Authored-By: Claude Sonnet <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RQSGAs6ugbAnFsAQ5caL3
…SkipTest (#4240) - Test: When_gates_retired_should_leave_no_branch_keyed_on_the_three_gates - Implementation: deleted the four gate branches keyed on the three retired capability gates (HasSupportToDelayedMessages twice, HasSupportToDeadLetterQueue, HasSupportToRequeue), their enclosing Array.Exists(LegacyGatedTemplates, …) block, and the now-unused LegacyGatedTemplates array. The legacy templates they gated were deleted in the previous task, so they gated nothing. The retained gates (confirming_posting, no_broker_created, assume_channel/validate_channel) are asserted to still skip their templates, with a positive control proving the absence assertions cannot pass on an empty generation run. - Ralph task: 57/62 (Phase 5 step 2 — gate keys still come last) Co-Authored-By: Claude Opus <noreply@anthropic.com> Co-Authored-By: Claude Sonnet <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RQSGAs6ugbAnFsAQ5caL3
…onfiguration (#4240) - Test: When_gates_retired_should_absent_config_properties - Implementation: deleted HasSupportToDelayedMessages, HasSupportToDeadLetterQueue and HasSupportToRequeue from MessagingGatewayConfiguration. Nothing read them after the previous task removed the SkipTest branches. Removed the now-dead initialiser assignments from two generator tests, and corrected four assertion messages in the gate-flags test that still cited the deleted flags as the reason the legacy templates are absent — they now guard against a template restore. The test asserts absence by reflection over the compiled type, with the three retained flag properties as positive controls. - Ralph task: 58/62 (Phase 5 step 3 — the JSON gate keys are next and come last) Co-Authored-By: Claude Opus <noreply@anthropic.com> Co-Authored-By: Claude Sonnet <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RQSGAs6ugbAnFsAQ5caL3
…n.json (#4240) - Test: verification is grep + full regenerate + solution build (no new xUnit test, per the task definition) - Implementation: removed HasSupportToDelayedMessages, HasSupportToDeadLetterQueue and HasSupportToRequeue from all 9 test-configuration.json files still carrying them (63 occurrences across 24 configurations, both the singular MessagingGateway and plural MessagingGateways shapes). Done LAST, after the templates, the SkipTest branches and the config properties, because removing a key before its template is deleted would have ungated the legacy template (AC-11 is ordered). The retained gates — HasSupportToPublishConfirmation, HasSupportToValidateBrokerExistence, HasSupportToValidateInfrastructure (including Kafka/Consumer's false) — are untouched. A full ./generate-test.sh after the change is a content no-op: no generated file differs, confirming nothing read the keys. Also dropped the three retired flags from .agent_instructions/generated_tests.md, which still instructed contributors to set keys that no longer exist. - Ralph task: 59/62 — Phase 5 (terminal cleanup) is COMPLETE Co-Authored-By: Claude Opus <noreply@anthropic.com> Co-Authored-By: Claude Sonnet <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RQSGAs6ugbAnFsAQ5caL3
…lace (#4240) Every Deferred ledger cell already carries `Deferred -> #4240 (sign-off: @maintainer)` and every generated Skip marker already carries real digits, per the standing maintainer ruling that the umbrella issue #4240 covers every Deferred cell. No follow-up issues raised; no source changed. Also widens task 60's RALPH-VERIFY, which false-positived on four legitimate *format placeholders* that are notation rather than unreconciled cells: - conformance-status.md lines 16/21 (cell-vocabulary row, placeholder-row rule) - tests/**/CanonicalTemplates/** fixtures asserting the placeholder behaviour - tools/.../ConformanceLedger.cs, the emitter that produces `#NNNN` for an Unknown cell and must keep the literal An adversarial re-audit opened every excluded occurrence and confirmed none is a real placeholder, and an unexcluded repo-wide sweep found no others. - Ralph task: 60/62 Co-Authored-By: Claude Opus <noreply@anthropic.com> Co-Authored-By: Claude Sonnet <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RQSGAs6ugbAnFsAQ5caL3
Review — #4297 Generated transport conformance tests (1/2: blocking items)Reviewed by category: the 6 Genuinely good work — replacing four boolean capability gates with a per-(configuration × behaviour) ledger is a real improvement in honesty, and the ledger's prose annotations (why SQS FIFO can't do per-message delay, why Redis/MSSQL can't Nack) are the kind of thing that usually gets lost. The I was unable to build or run anything in this environment, so everything below is from reading the diff. 1.
|
Review — #4297 (2/2: non-blocking findings)5. MQTT producer hardcodes
|
…e audit (#4240) A read-only, network-free audit that fails any messaging-gateway Skip whose value does not match `Deferred: #<n>`. A bare or reasonless Skip is now a CI failure (AC-13, "no silent skip"). - Test: When_a_gateway_skip_is_not_a_deferred_marker_should_fail_audit - Implementation: GatewaySkipConventionAudit — a pure `IsConformingSkipValue` predicate plus a `ScanTree` walk over the two in-tree artifact roots (Templates/MessagingGateway/**/*.cs.liquid and tests/**/Generated/**/*.cs). No network, no subprocess, no issue-tracker query — ADR 0067 step 7. Non-vacuity is asserted, not assumed: the live-tree fact fails if the scan visits zero files or finds zero conforming markers (it visits 1337 files and finds 122 real `Deferred: #4240` markers), and a scan-level canary test plants three violations into a synthetic tree and asserts the scan reports all three. The manual planted-canary check was also run against a real generated file and the live-tree fact failed as intended. The Skip extractor tolerates `Skip="…"` without spaces and captures the empty value, so a reasonless `Skip = ""` is reported rather than slipping past the regex — the predicate already rejected it, but the scan could not see it. Verified this surfaces nothing new across the 1337 scanned files. Generator suite: 203 passed / 0 failed on net9.0 and net10.0 (196 + 7 new). - Ralph task: 61/62 Co-Authored-By: Claude Opus <noreply@anthropic.com> Co-Authored-By: Claude Sonnet <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RQSGAs6ugbAnFsAQ5caL3
Extends the conformance audit with a two-direction, offline cross-check of the in-tree deferral trail (FR-13, FR-21, AC-13, AC-24; ADR 0067 step 7): - Skip -> ledger: every distinct issue number in a `Skip = "Deferred: #<n> …"` must appear in at least one ledger cell as `Deferred -> #<n>`. - Ledger -> trail: every `Deferred` ledger cell must carry BOTH a real issue link and a `sign-off: @<name>` token. The audit reads only conformance-status.md and the in-tree artifacts — it never queries the tracker for issue open/closed state and never re-verifies sign-off provenance, which remain the maintainer review gate's job. - Test: When_a_skip_has_no_matching_deferred_ledger_row_should_fail_audit - Implementation: LedgerSkipCrossCheckAudit — a ledger matrix parser anchored on the `| Configuration |` header (so the Cell-Vocabulary table and the Rules prose, which both contain the literal notation, are never read as cells) plus the two-direction check. Non-vacuity again asserted rather than assumed: the live-tree fact fails if the parse finds zero data rows or zero Deferred cells (it finds 24 rows and 29 Deferred cells) or if the scan finds zero Skip markers, and three synthetic-tree canaries prove each specified failure mode is actually reported — a Skip whose issue has no ledger row, a Deferred cell with no issue link, and a Deferred cell with no sign-off. Refactor: the artifact walk and the Skip extraction now live once, on GatewaySkipConventionAudit (`EnumerateGatewayArtifacts`/`ExtractSkipValues`), and both audits share them. They were duplicated, which would have let a future change to the artifact layout silently desync the two audits. Generator suite: 210 passed / 0 failed on net9.0 and net10.0 (203 + 7 new). `dotnet build Brighter.slnx`: 0 errors. - Ralph task: 62/62 — PHASE 6 COMPLETE, all 62 ralph tasks done Co-Authored-By: Claude Opus <noreply@anthropic.com> Co-Authored-By: Claude Sonnet <noreply@anthropic.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RQSGAs6ugbAnFsAQ5caL3
Review — Generated transport conformance tests (#4297) — part 1/2Reviewed the hand-written surface: Overall: big, coherent work and the direction is right — replacing four boolean capability gates with a per-cell ledger is a real improvement, and the audit suite (non-vacuity assertions, synthetic-repo canaries, I verified the wiring end to end: 24 Correctness1.
2. FR-5 template asserts metadata without the Every other reject template gates its metadata assertions on 3. GCP: The comment says a null timeout "leaves the call unbounded, preserving the prior default behaviour", but the two differ. Secondary: 4. Missing ledger row / typo'd
The obvious guard does not catch it either: Suggest throwing on an unknown row/column at generation time, plus a test asserting the 5. Missing ledger file disables the whole suite —
6. The Skip→Ledger check compares sets of issue numbers. Every Deferred cell uses 7. Coverage regression: requeue-count-exceeded → DLQ deleted with no replacement
8. Nack two-message test does not assert what its comment claims —
Performance9. DLQ/invalid-channel poll loops spin with no sleep, and nest inside a longer inner retry
For AWS the numbers invert: the provider internally retries 10 × (5 s receive + 1 s delay) ≈ 60 s ( Suggest giving 10. RocketMQ compose memory bumped 16× — 128m→2g broker and 64m→1g proxy. Plausibly necessary, but 3g+ of JVM heap alongside the nameserver is a real constraint on 7 GB GitHub-hosted runners — worth confirming the smallest value that actually works rather than landing on round numbers. |
Review — #4297 — part 2/2Consistency / design11. MQTT tracing hardcodes Every other gateway threads a configured value ( 12. PR description omits the MQTT "Transport 13. The XML doc acknowledges the Channel throws 14. Duplicated
15. Model mutation leaks across renders —
16. FR-8 When 17. Ledger parsing is brittle — Header detection is Minor
Worth calling out
On the descriptionThe status section reads as stale relative to the checked-in ledger: it says MQTT, RMQ.Sync and AzureServiceBus are "the only |
Follow-up to task 62. The cross-check shipped there matches a Skip to the ledger on issue number alone. Because every deferral resolves to the single umbrella issue #4240, that reduces to "#4240 appears somewhere in the ledger" and cannot see the drift that actually happens. This adds `LedgerSkipCrossCheckAudit.CheckCellAgreement`, which checks each canonical generated test against the exact (LedgerKey × FR column) cell it is judged by, in both directions: - a cell that defers must have a Skip — catches a cell flipped to Deferred without regenerating, which leaves the test running; - a cell that passes must not — catches a cell flipped green without regenerating, which leaves the test skipped; - a Skip that exists must be character-for-character what the generator would emit for that cell — catches a wrong issue number or a stale behaviour label. The expected value comes from `ConformanceLedger.GetSkip`, the generator's own emitter, so the audit cannot drift from what generation produces. In effect the audit is now a regeneration-drift detector for Skip attributes. - Test: When_a_skip_disagrees_with_its_own_ledger_cell_should_fail_audit - Structural: the two mappings that make the cell addressable — canonical template to FR column, and FR column to behaviour label — move out of `MessagingGatewayGenerator`'s private state into a shared public `CanonicalBehaviours`. The generator and the audit now read one copy; two copies could drift and the audit would then check the wrong cell. This is behaviour-preserving: `./generate-test.sh` after the move is a content no-op, not one generated file differs. Proven on the real tree, not just synthetically. Flipping MSSQL's FR-16 cell to `Pass` without regenerating fails the audit naming both affected files; stripping the Skip from one of those files fails it in the reverse direction. Both canaries were reverted. Four synthetic-tree tests cover each drift shape plus the agreeing case, and the live-tree fact asserts non-vacuity on four axes — configurations resolved, files checked, and that BOTH branches were exercised (cells expecting a Skip and cells expecting none), so it cannot pass by checking nothing. Coverage: 24 ledger rows, 55 Deferred cells, 110 generated files carrying a Deferred Skip — 55 x 2 variants, which reconciles exactly. Generator suite: 215 passed / 0 failed on net9.0 and net10.0 (210 + 5 new). `dotnet build Brighter.slnx`: 0 errors. `./generate-test.sh`: content no-op. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RQSGAs6ugbAnFsAQ5caL3
…mplates (#4240) The file's name no longer described live behaviour — the gate flags it is named for were deleted in Phase 5, so its legacy assertions had become restore-guards rather than gate tests. Most of it was also genuinely redundant: - "the four legacy templates are not generated" is covered by the Cleanup gate test, which scans for both templates and generated copies repo-wide; - "the retained gates still skip their templates" is covered by When_retained_gate_flags_are_false_should_still_skip_retained_gate_templates, which asserts the same four templates AND carries a positive control. One assertion was NOT redundant and is carried over rather than dropped: the substring-matching hazard guard (ADR 0066). It plants a canonical template whose name contains both "requeuing" and "with_delay" — as the real FR-2 template does — and proves nothing gates it. The Cleanup test only mentions the hazard in a comment about its own matching; it never asserts the positive case. That assertion now lives in the gates-retired test as its own fact, When_a_canonical_template_name_contains_legacy_substrings_should_still_be_generated, alongside the other retained-gate facts it belongs with. Structural only, no behaviour change. Generator suite: 215 passed / 0 failed on net9.0 and net10.0 — unchanged, one fact removed and one added. `dotnet build Brighter.slnx`: 0 errors. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RQSGAs6ugbAnFsAQ5caL3
Review — PR #4297 (generated transport conformance suite) — part 1/2Reviewed at Overall this is strong work. Replacing four coarse Findings roughly in priority order. 1. MQTT
|
Review — PR #4297 — part 2/26. CI wall-clock679 generated files carry a 7. Smaller items
8. Docs / housekeeping
Nothing here challenges the design — the ledger-plus-audit approach is the right call. Items 1, 2 and 3 from part 1 are the ones I'd want fixed before merge. Happy to look again once this is out of draft. |
Review — PR #4297 (WIP): Generated transport conformance tests (1/2)Reviewed the hand-written surface: the three Overall this is strong work. The ledger + cross-check audit design is genuinely good — in particular the non-vacuity assertions in 1. The two-message canonical templates cannot distinguish M1 from M2 — the assertion that matters is vacuous
var message1 = _messageBuilder.SetTopic(_publication.Topic!).Build();
var message2 = _messageBuilder.SetTopic(_publication.Topic!).Build();so M1 and M2 have an identical
Both are marked 2.
|
Review — PR #4297 (WIP) (2/2)5. The ledger fails open, silently, in several places
Given ADR 0067's "no silent skips" stance, the symmetric guarantee ("no silently ungoverned cell") seems worth having: throw on an unrecognised cell value, and log at Warning/Error when the ledger cannot be located, so ledger drift is a build failure rather than a quiet no-op. Related, 6. DLQ polling loops hot-spinEvery DLQ / invalid-channel template does: while (stopwatch.Elapsed < TimeSpan.FromSeconds(30))
{
dlqMessage = _messageGatewayProvider.GetMessageFromDeadLetterQueue(_subscription);
if (dlqMessage.Header.MessageType != MessageType.MT_NONE) break;
}with no delay between iterations. Whether that is a tight loop depends on the provider: 7. Harness schedulers: silent swallow, and a disposal race
8. Smaller things
On the TDD workflowCLAUDE.md makes Answering the specific questions
Nice work overall — the ledger-as-source-of-truth approach is a much better answer than per-transport capability flags. |
…a passing test (#4240) Kafka/Consumer was the only Kafka configuration declaring `HasSupportToValidateInfrastructure: false`, so Classic and PartitionKey each ran `assume_channel` and `validate_channel` while Consumer ran neither. That inconsistency is the kind of mis-declared capability flag this spec exists to remove, so it is fixed here rather than left. Measured against a live broker rather than reasoned about. With the flag removed and the tree regenerated, Consumer's four tests split cleanly: - `validate_channel` (Reactor + Proactor) PASS - `assume_channel` (Reactor + Proactor) FAIL, deterministically over repeated runs — "We are expected to throw an exception" So the flag was too coarse: it was suppressing two tests that work in order to suppress two that do not. The failure is real and specific. `EnsureTopic()` returns immediately on `OnMissingChannel.Assume` without any admin call, and the compose sets `KAFKA_AUTO_CREATE_TOPICS_ENABLE: "false"`, so the topic genuinely does not exist; the classic consumer surfaces that, while the KIP-848 consumer-group protocol completes silently. `Validate` goes through `AdminClient.GetMetadata` and works on both. This reproduces what #4233's author hit — their commit trail records it as "async completion for non-existent topics" — but shows it applies to only half of what they gated off. The fix is a narrower, truthful declaration: `HasSupportToDetectMissingInfrastructureOnAssume` (default `true`) skips `assume_channel` alone. `HasSupportToValidateInfrastructure` keeps its existing meaning and still gates both, so MQTT and Redis — the other two configurations that set it false — are unaffected. Kafka/Consumer now declares the narrow flag and gains the two `validate_channel` tests. - Test: When_assume_detection_is_unsupported_should_skip_assume_channel_but_still_emit_validate_channel, proven non-vacuous with a planted canary (disable the gate branch -> fails; restore -> passes) - Kafka suite with a live broker: 190 passed / 0 failed, up from the 188 baseline by exactly the two tests gained - Generator suite: 216 passed / 0 failed on net9.0 and net10.0 (215 + 1) - `dotnet build Brighter.slnx`: 0 errors; `./generate-test.sh` stable across repeated runs Note the two stale `assume_channel` copies generated during the investigation had to be deleted by hand: the generator does not remove what a now-skipped template previously produced, exactly the orphan problem ADR 0066 calls out. The KIP-848 consumer swallowing a missing topic under `Assume` is a genuine gateway behaviour difference, not a harness gap. It is declared here, not fixed — fixing error surfacing in the Kafka consumer is outside this PR's localized src boundary and belongs with the KIP-848 author. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RQSGAs6ugbAnFsAQ5caL3
Review — #4297 (generated transport conformance tests) — part 1/3:
|
Review — #4297 — part 2/3: ledger, audits, ledger accuracyThe load-bearing partThe stated invariant is "no silent skips". The audits enforce that direction well. The inverse — silently losing a skip, or silently gating nothing — has three open paths: 7.
8. Two silent "no ledger at all" paths —
9. An unknown
10. Two ledger parsers with different header heuristics
11. Line-based
Ledger accuracy12. The ledger's own vocabulary says
These read as |
Review — #4297 — part 3/3: test coverage, housekeeping, nitsTest coverage13. No direct regression test for any of the four CLAUDE.md makes The SNS one is the clearest gap: 14. Doubly-nested retry inflates failure cost — The provider already retries internally for up to ~60 s (10 x (5 s receive + 1 s sleep)), and the canonical template wraps that in a further 30 s stopwatch loop that constructs a fresh 15. Culture-sensitive ISO-8601 assertion —
Housekeeping16. 17. ADR 0070 is deleted rather than superseded — 18. 19. 20. 21. 22. Stale comment and duplicated ledger state — Nits
Nothing here blocks the direction. #7/#8/#9 and #12 are the ones I would want addressed before merge, since they undercut the guarantee the whole ledger mechanism is built to provide, and #1/#2/#3 are shipping behaviour rather than test scaffolding. Verified while reviewing: no |
Generated transport conformance tests - should cover new reject/dlq/requeue paths
Closes #4240.
While generating the transport conformance tests, I added new functionality around reject/dlq/requeue. This functionality can now be universal; where the message-oriented middleware doesn't support it, we can fall back to a Brighter substitute.
This replaces the four legacy per-transport gate keys, which controlled generation of functionality for these features, dependent on native support, with a generated canonical conformance suite that runs
against every gateway.
As some gateways have not yet implemented Brighter support for missing features, we also added a checked-in conformance ledger (
specs/0036-universal-transport-conformance-tests/conformance-status.md) recording per-configuration conformance for 11 canonical behaviors (FR-2, 4, 5, 6, 7, 8, 9, 15, 16, 17, 22).Every canonical test's
Skipis driven by this ledger, so a behavior is either proven against a real broker or carries a signed-offDeferred -> #4240marker. No silent skips.Status — 62 of 62 tasks; phases 0–6 complete
All 24 wired configuration rows are resolved — zero
Unknowncells, across all twelve targetedtransports:
SqsFifoFR-9Deferred(SQS FIFO rejects per-message delay) andSns*FR-9FixedDeferred(destructive read)Deferred(invalid-channel routing)Fixed+ FR-5Deferred, mirroring RMQ.AsyncFixed+ FR-16DeferredFixed+ FR-2 / FR-15Deferred(upstream no-opRequeue)Fixed+ 6Deferred(emulator-only verification)Deferred— streaming pull hangs on the emulator, excluded in CI, no real credsDeferred— no compose file, no credentials, no emulator; deferral of verification, not a finding of non-conformance55 of the 264 cells are
Deferred, each carrying a signed-off#4240marker.Transport
srcchanges (deliberately localized)Paramore.Brighter.MessagingGateway.RocketMQ— guard theBaggageproperty; empty baggage crashed every send.Paramore.Brighter.MessagingGateway.GcpPubSub—GcpPullMessageConsumer.Receive/ReceiveAsyncignoredtimeOutand long-polled, blocking the pump; now bound viaCallSettingsexpiration.Paramore.Brighter.MessagingGateway.AWSSQS(V3 + V4) — syncSendWithDelaypassedTimeSpan.Zero.Everything else is test-harness, generator template, or ledger work.
Terminal cleanup — done
Gated on the zero-
Unknownledger, and performed in the order the acceptance criteria require (thetest-configuration.jsonkeys last, so each earlier step was provably a no-op):SkipTestgate branches and theLegacyGatedTemplatesclosed list stripped;MessagingGatewayConfiguration;test-configuration.json(9 files, 63 occurrences).Regeneration after each step was a content no-op — not one generated file differs — which is the
proof that nothing was still reading those keys. The three retained capability gates
(
HasSupportToPublishConfirmation,HasSupportToValidateBrokerExistence,HasSupportToValidateInfrastructure) keep their meaning and are still tested.One retained gate was found to be mis-declared in the same way the retired ones were, and is
corrected here rather than left.
Kafka / Consumerwas the only Kafka configuration settingHasSupportToValidateInfrastructure: false, soClassicandPartitionKeyranassume_channelandvalidate_channelwhileConsumerran neither. Measured against a live broker, that configurationsplits cleanly:
validate_channelpasses both variants,assume_channelfails both,deterministically — the flag was suppressing two working tests in order to suppress two that do not
work. A narrower gate,
HasSupportToDetectMissingInfrastructureOnAssume, now skipsassume_channelalone;
HasSupportToValidateInfrastructureis unchanged in meaning, so MQTT and Redis are unaffected.The Kafka suite goes 188 -> 190 pass / 0 fail, by exactly the two tests recovered.
The underlying cause is a real behavioural difference, declared rather than papered over:
EnsureTopic()returns immediately on
OnMissingChannel.Assumewithout an admin call, and the test compose disablestopic auto-creation, so the topic genuinely does not exist — the classic consumer surfaces that, while
the KIP-848 consumer-group protocol completes silently. Fixing that error surfacing in
KafkaMessageConsumeris deliberately not attempted here; it sits outside this PR's localizedsrcboundary and belongs with the KIP-848 author.
CI audit — no silent skips
A read-only, network-free audit (it never queries the issue tracker) enforces the deferral trail in
three ways:
Skipmust matchDeferred: #<n>— a bare or reasonlessSkipfails thebuild;
Deferredledger cell must carry both an issue link and a recorded sign-off;Skipmust agree with its own (configuration × behaviour) ledger cell, inboth directions — a cell flipped without regenerating fails, whether that leaves a test wrongly
skipped or wrongly running. The expected value comes from the generator's own emitter, so the audit
cannot drift from what generation produces.
The audits assert their own non-vacuity (files scanned, markers found, and that both branches were
exercised), and each failure mode is covered by a synthetic-tree test, so they cannot pass by checking
nothing.
Out of scope
Fixing all brokers that don't currently implement Brighter support when there is no native support is out of scope for this work.
Every
Deferredcell resolves to #4240, this PR's own tracking issue, by maintainer ruling — noper-deferral follow-up issues are raised. The
#NNNNpre-audit placeholders are fully reconciled: noneremains in the ledger or in any generated
Skipmarker.🤖 Generated with Claude Code