Skip to content

Commit cc4500d

Browse files
committed
feat(stovepipe): Add issue-tracking controller
1 parent 21015cf commit cc4500d

8 files changed

Lines changed: 1141 additions & 6 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
load("@rules_go//go:def.bzl", "go_library", "go_test")
2+
3+
go_library(
4+
name = "go_default_library",
5+
srcs = ["issuetrack.go"],
6+
importpath = "github.com/uber/submitqueue/stovepipe/controller/issuetrack",
7+
visibility = ["//visibility:public"],
8+
deps = [
9+
"//platform/base/id:go_default_library",
10+
"//platform/base/messagequeue:go_default_library",
11+
"//platform/consumer:go_default_library",
12+
"//platform/metrics:go_default_library",
13+
"//stovepipe/core/loader:go_default_library",
14+
"//stovepipe/core/messagequeue:go_default_library",
15+
"//stovepipe/entity:go_default_library",
16+
"//stovepipe/extension/storage:go_default_library",
17+
"@com_github_uber_go_tally//:go_default_library",
18+
"@org_uber_go_zap//:go_default_library",
19+
],
20+
)
21+
22+
go_test(
23+
name = "go_default_test",
24+
srcs = ["issuetrack_test.go"],
25+
embed = [":go_default_library"],
26+
deps = [
27+
"//platform/base/messagequeue:go_default_library",
28+
"//platform/consumer/mock:go_default_library",
29+
"//platform/publish:go_default_library",
30+
"//stovepipe/core/messagequeue:go_default_library",
31+
"//stovepipe/entity:go_default_library",
32+
"//stovepipe/extension/storage:go_default_library",
33+
"//stovepipe/extension/storage/mock:go_default_library",
34+
"@com_github_stretchr_testify//assert:go_default_library",
35+
"@com_github_stretchr_testify//require:go_default_library",
36+
"@com_github_uber_go_tally//:go_default_library",
37+
"@org_uber_go_mock//gomock:go_default_library",
38+
"@org_uber_go_zap//:go_default_library",
39+
],
40+
)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Issue tracking
2+
3+
`issuetrack.Controller` consumes `IssueTrack` notifications in a single queue partition. It alone maintains operational `OpenIssue` membership and records `IssueLog` transitions; build detection and immutable per-build observations remain in `buildanalysis`. The controller rejects messages whose partition key differs from the payload queue. Producers and consumers must preserve per-queue serialization; the progress row is not a distributed lock or a substitute for that delivery contract.
4+
5+
## Reconciliation
6+
7+
A refresh notification traverses its build's observations in bounded task/unit batches. A missing issue is opened using this build as its first-seen build. Repeated observations preserve existing openings and do not create another transition. Final reconciliation also traverses open issues and resolves those absent from the source build. Cancelled builds may add confirmed observations but never establish resolution. Final notifications must name the request's winning terminal build; duplicate-build losers are ignored.
8+
9+
The final-notification contract is an input requirement, not something terminal status alone proves: detection must have succeeded, its persisted observations must be complete and authoritative for the queue's relevant validation scope, and in-flight analysis must not add unaccounted results after the final handoff. Accumulated observations are not automatically a current failure snapshot. A producer that cannot satisfy those requirements must not request absence-based resolution. No producer or consumer is wired by this implementation; the production handoff and recovery policy remain follow-up work.
10+
11+
## Source ordering and interrupted passes
12+
13+
`IssueTrackingStore` retains one progress row per queue, including the source request, build, phase, and optimistic version. Request IDs are compared through `CompareRequestID`; provider build IDs and timestamps are not order keys. An older request cannot modify current issues. Later refresh notifications and duplicate finals for an already-finalized request are ignored. Same-request refresh notifications remain eligible until finalization. The progress row remains even when there are no open issues.
14+
15+
Before applying any issue writes, the controller conditionally records a refreshing or finalizing phase. It records the completed phase only after issue and history writes succeed. A subsequent delivery first finishes pending work, including when the original delivery was retried or dead-lettered; only then may it admit a newer source. A permanent pending failure therefore needs operational repair rather than silently skipping a partially applied transition. Controller code owns version arithmetic; storage performs guarded replacement only.
16+
17+
Opening history is repaired on replay if an open row was created before its log write failed. Resolution is retained before deleting the matching opening, so failed history persistence leaves the issue available for retry. Duplicate transition identities preserve their original timestamps. Replaying a whole pass avoids a multi-row transaction or a durable cursor over mutable membership. The operational progress and accepted writes are serialized, not an atomic database snapshot.
18+
19+
This controller does not publish fix requests, expose a user-facing issue read model, or materialize `IssueSummary`. Those are separate responsibilities. Lifecycle history is retained here; a log-derived summary/materializer can be added without changing the observation handoff or the source-order guard.

0 commit comments

Comments
 (0)