feat(stovepipe): add DLQ reconciliation for the process stage#347
Conversation
A request whose process-stage message exhausts retries would previously sit in a non-terminal state forever, indistinguishable from "not yet validated" to callers gating on greenness. Per the fail-closed policy in doc/rfc/stovepipe/workflow.md, such requests must be driven to a conservative not-green terminal state instead. - Add RequestStateFailed terminal state and IsRequestStateTerminal helper to the entity package. - Add stovepipe/controller/dlq: a consumer.Controller that drains the process_dlq topic and CAS-transitions the affected request to failed, releasing the queue's in_flight_count slot first when the request had already been admitted (processing), per the in_flight_count integrity rule in doc/rfc/stovepipe/steps/process.md. - Wire a second consumer in the stovepipe server using errs.AlwaysRetryableProcessor, register the process_dlq topic with DLQ disabled (no cascade) and raised retry attempts, and start/stop the two consumers in the safe order (DLQ up first, drained last). Reconciliation is idempotent: already-terminal requests are skipped, and optimistic locking lets a late legitimate pipeline transition win. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review feedback on the process DLQ PR: the DLQ subscription overrides (DLQ.Enabled=false, MaxAttempts=1000) were duplicated across wiring files, and two doc comments in the DLQ reconciler claimed behavior the code does not have (release-first ordering prevents double release; decode errors are non-retryable under AlwaysRetryableProcessor). - platform/extension/messagequeue/subscription_config.go: add DLQSubscriptionConfig helper owning the DLQ-consumer subscription policy - platform/extension/messagequeue/subscription_config_test.go: test the helper's contract - service/stovepipe/server/main.go: use the helper - service/submitqueue/orchestrator/server/main.go: use the helper - stovepipe/controller/dlq/dlq.go: correct failRequest comment to state the real crash trade-off (double release vs leaked slot) - stovepipe/controller/dlq/request.go: correct decode-error comment — retried deliberately to heal deployment skew, bounded by MaxAttempts Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
587410a to
78d3d91
Compare
|
Rebased onto main. Besides the review feedback already discussed (the The reconciler now forces
Reconciliation behavior is otherwise unchanged (idempotent terminal-skip, slot release before the terminal CAS, always-retryable pairing). 🤖 Generated with Claude Code |
Summary
A request whose
process-stage message exhausts retries would previously sit in a non-terminal state forever — indistinguishable from "not yet validated" to callers gating deployments on greenness. Per the fail-closed policy in doc/rfc/stovepipe/workflow.md, such requests must be driven to a conservative not-green terminal state instead.RequestStateFailedterminal state andIsRequestStateTerminalhelper.stovepipe/controller/dlqpackage: aconsumer.Controllerthat drains theprocess_dlqtopic and CAS-transitions the affected request tofailed. If the request had already been admitted (processing), the queue'sin_flight_countslot is released first, per the integrity rule in doc/rfc/stovepipe/steps/process.md.errs.AlwaysRetryableProcessor(a reconciliation failure retries in place — the DLQ subscription is a final destination withDLQ.Enabled = false, so nothing can cascade to a_dlq_dlq);process_dlqregistered in the topic registry with raised retry attempts; DLQ consumer starts first and stops last.Reconciliation is idempotent: already-terminal requests are skipped, and optimistic locking lets a late legitimate pipeline transition win. The design mirrors SubmitQueue's proven
submitqueue/orchestrator/controller/dlqpattern, simplified for stovepipe's single payload shape.Only
processis a queue-consuming step today (ingestis synchronous RPC); future stages (build,buildsignal,record) can reuse the same pattern.Test plan
stovepipe/controller/dlq/dlq_test.go: accepted→failed, processing→failed with slot release, already-terminal no-ops, not-found no-op, CAS version-mismatch paths, malformed/empty payloadsgo build ./...andgo test ./stovepipe/... ./service/stovepipe/...passmake gazelle,make fmt,make lint-licenseclean🤖 Generated with Claude Code