Skip to content

feat(stovepipe): add DLQ reconciliation for the process stage#347

Open
gsturges wants to merge 1 commit into
mainfrom
gsturges/stovepipe-process-dlq
Open

feat(stovepipe): add DLQ reconciliation for the process stage#347
gsturges wants to merge 1 commit into
mainfrom
gsturges/stovepipe-process-dlq

Conversation

@gsturges

Copy link
Copy Markdown
Contributor

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.

  • Entity: add RequestStateFailed terminal state and IsRequestStateTerminal helper.
  • New stovepipe/controller/dlq package: a consumer.Controller that drains the process_dlq topic and CAS-transitions the affected request to failed. If the request had already been admitted (processing), the queue's in_flight_count slot is released first, per the integrity rule in doc/rfc/stovepipe/steps/process.md.
  • Server wiring: second consumer using errs.AlwaysRetryableProcessor (a reconciliation failure retries in place — the DLQ subscription is a final destination with DLQ.Enabled = false, so nothing can cascade to a _dlq_dlq); process_dlq registered 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/dlq pattern, simplified for stovepipe's single payload shape.

Only process is a queue-consuming step today (ingest is synchronous RPC); future stages (build, buildsignal, record) can reuse the same pattern.

Test plan

  • New table-driven unit tests in 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 payloads
  • go build ./... and go test ./stovepipe/... ./service/stovepipe/... pass
  • make gazelle, make fmt, make lint-license clean

🤖 Generated with Claude Code

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>
@gsturges gsturges marked this pull request as ready for review July 13, 2026 23:02
@gsturges gsturges requested review from a team, behinddwalls and sbalabanov as code owners July 13, 2026 23:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant