Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions stovepipe/entity/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ go_library(
"request_acceptance.go",
"request_history.go",
"request_id.go",
"request_issue_summary.go",
"request_log.go",
"request_summary.go",
"validation_fact.go",
Expand All @@ -45,6 +46,7 @@ go_test(
"project_fact_by_degree_test.go",
"queue_state_test.go",
"request_id_test.go",
"request_issue_summary_test.go",
"request_log_test.go",
"request_test.go",
"validation_fact_test.go",
Expand Down
61 changes: 61 additions & 0 deletions stovepipe/entity/request_issue_summary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// Copyright (c) 2026 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package entity

import "fmt"

// RequestIssueKind classifies an accepted issue update within one request.
type RequestIssueKind string

const (
RequestIssueKindNew RequestIssueKind = "new"
RequestIssueKindExisting RequestIssueKind = "existing"
RequestIssueKindClosed RequestIssueKind = "closed"
)

// RequestIssueSummary is a materialized result of one request's issue reconciliation.
// Its identity is request, task, and unit within a queue.
type RequestIssueSummary struct {
// RequestID identifies the request that processed the issue.
RequestID string `json:"request_id"`
// TaskID identifies the logical validation task.
TaskID string `json:"task_id"`
// UnitID identifies the validation unit; empty for a task-wide failure.
UnitID string `json:"unit_id"`
// Kind distinguishes newly opened, already open, and closed issues.
Kind RequestIssueKind `json:"kind"`
// BuildID identifies the build whose accepted update produced this result.
BuildID string `json:"build_id"`
// FirstSeenBuildID identifies the opening, including for an existing or closed issue.
FirstSeenBuildID string `json:"first_seen_build_id"`
// Version is the optimistic-lock version, initially zero.
Version int32 `json:"version"`
}

// Validate checks the identity and classification of a request issue.
func (i RequestIssueSummary) Validate() error {
if i.Version < 0 {
return fmt.Errorf("request issue summary version must not be negative")
}
if i.RequestID == "" || i.TaskID == "" || i.BuildID == "" || i.FirstSeenBuildID == "" {
return fmt.Errorf("request issue requires request, task, build, and first-seen build IDs")
}
switch i.Kind {
case RequestIssueKindNew, RequestIssueKindExisting, RequestIssueKindClosed:
return nil
default:
return fmt.Errorf("invalid request issue kind %q", i.Kind)
}
}
39 changes: 39 additions & 0 deletions stovepipe/entity/request_issue_summary_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) 2026 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package entity

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestRequestIssueValidate(t *testing.T) {
valid := RequestIssueSummary{RequestID: "12", TaskID: "test", BuildID: "build/12", FirstSeenBuildID: "build/10", Kind: RequestIssueKindExisting}
for _, kind := range []RequestIssueKind{RequestIssueKindNew, RequestIssueKindExisting, RequestIssueKindClosed} {
issue := valid
issue.Kind = kind
require.NoError(t, issue.Validate())
}
for _, change := range []func(*RequestIssueSummary){
func(i *RequestIssueSummary) { i.RequestID = "" }, func(i *RequestIssueSummary) { i.TaskID = "" },
func(i *RequestIssueSummary) { i.BuildID = "" }, func(i *RequestIssueSummary) { i.FirstSeenBuildID = "" },
func(i *RequestIssueSummary) { i.Kind = "open" },
} {
issue := valid
change(&issue)
require.Error(t, issue.Validate())
}
}
1 change: 1 addition & 0 deletions stovepipe/extension/storage/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ go_library(
"queue_store.go",
"queue_summary_store.go",
"request_acceptance_store.go",
"request_issue_summary_store.go",
"request_log_store.go",
"request_store.go",
"request_summary_store.go",
Expand Down
4 changes: 4 additions & 0 deletions stovepipe/extension/storage/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,7 @@ The issue-tracking table must include the notification, cursor, `finalize`, and
### Applied transition facts

`IssueState.LastProcessedBuildID` identifies the most recent build applied to an issue, including unchanged results. `LastChangedBuildID` and `ChangedAtMs` identify the actual opening or closing and supply its history identity and timestamp. They do not advance when only the processed build changes. `FirstSeenBuildID` remains the opening identity across closure and changes on reopening. These references must remain readable while used for source ordering. The current-issue cursor and the row's processed source provide the storage needed for retry-safe application without retaining a future write payload.

## Request issue results

The request-issue summary store retains one materialized result per request, task, and unit in its bound queue. New includes reopened issues; existing means this request reported an issue that was already open; closed means this request actually closed it. Issues merely left open because closure was suppressed are not existing results. Reads use a bounded request-prefix scan in task/unit order. These results describe the request's accepted updates, not the queue's full open-issue set.
1 change: 1 addition & 0 deletions stovepipe/extension/storage/mock/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ go_library(
"queue_store_mock.go",
"queue_summary_store_mock.go",
"request_acceptance_store_mock.go",
"request_issue_summary_store_mock.go",
"request_log_store_mock.go",
"request_store_mock.go",
"request_summary_store_mock.go",
Expand Down
101 changes: 101 additions & 0 deletions stovepipe/extension/storage/mock/request_issue_summary_store_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions stovepipe/extension/storage/mock/storage_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions stovepipe/extension/storage/mysql/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ go_library(
"queue_store.go",
"queue_summary_store.go",
"request_acceptance_store.go",
"request_issue_summary_store.go",
"request_log_store.go",
"request_store.go",
"request_summary_store.go",
Expand All @@ -25,6 +26,7 @@ go_library(
importpath = "github.com/uber/submitqueue/stovepipe/extension/storage/mysql",
visibility = ["//visibility:public"],
deps = [
"//platform/base/id:go_default_library",
"//platform/metrics:go_default_library",
"//stovepipe/entity:go_default_library",
"//stovepipe/extension/storage:go_default_library",
Expand All @@ -50,6 +52,7 @@ go_test(
"queue_store_test.go",
"queue_summary_store_test.go",
"request_acceptance_store_test.go",
"request_issue_summary_store_test.go",
"request_log_store_test.go",
"request_store_test.go",
"request_summary_store_test.go",
Expand Down
Loading
Loading