From 884bde9f81813f15660083636a3fc4d5189a2a4c Mon Sep 17 00:00:00 2001 From: mnoah1 Date: Fri, 23 Oct 2026 19:32:39 +0000 Subject: [PATCH 1/2] feat(stovepipe): define the repair executor extension Summary: Add RepairExecutor.Submit accepting one OpenIssue. Define queue-scoped factory resolution and submission acceptance/error semantics, with generated mocks and Bazel targets. Revert Plan: Revert this commit. --- Makefile | 1 + stovepipe/extension/fixer/BUILD.bazel | 9 ++ stovepipe/extension/fixer/README.md | 9 ++ stovepipe/extension/fixer/fixer.go | 45 +++++++++ stovepipe/extension/fixer/mock/BUILD.bazel | 13 +++ stovepipe/extension/fixer/mock/fixer_mock.go | 97 ++++++++++++++++++++ 6 files changed, 174 insertions(+) create mode 100644 stovepipe/extension/fixer/BUILD.bazel create mode 100644 stovepipe/extension/fixer/README.md create mode 100644 stovepipe/extension/fixer/fixer.go create mode 100644 stovepipe/extension/fixer/mock/BUILD.bazel create mode 100644 stovepipe/extension/fixer/mock/fixer_mock.go diff --git a/Makefile b/Makefile index 24597829b..de514a8fd 100644 --- a/Makefile +++ b/Makefile @@ -629,6 +629,7 @@ GO_GENERATE_PACKAGES := \ ./stovepipe/core/requestlog/... \ ./stovepipe/extension/buildissue/... \ ./stovepipe/extension/buildrunner/... \ + ./stovepipe/extension/fixer/... \ ./stovepipe/extension/projectstatus/... \ ./stovepipe/extension/queueconfig/... \ ./stovepipe/extension/sourcecontrol/... \ diff --git a/stovepipe/extension/fixer/BUILD.bazel b/stovepipe/extension/fixer/BUILD.bazel new file mode 100644 index 000000000..a6af87006 --- /dev/null +++ b/stovepipe/extension/fixer/BUILD.bazel @@ -0,0 +1,9 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["fixer.go"], + importpath = "github.com/uber/submitqueue/stovepipe/extension/fixer", + visibility = ["//visibility:public"], + deps = ["//stovepipe/entity:go_default_library"], +) diff --git a/stovepipe/extension/fixer/README.md b/stovepipe/extension/fixer/README.md new file mode 100644 index 000000000..a0a840e14 --- /dev/null +++ b/stovepipe/extension/fixer/README.md @@ -0,0 +1,9 @@ +# Repair execution + +[`RepairExecutor`](fixer.go) submits repair work for one [`OpenIssue`](../../entity/open_issue.go). The executor can invoke an in-process implementation or wrap an external provider. + +`Submit` returns promptly with a nonempty opaque provider attempt ID on acceptance, or an error. Acceptance does not establish recovery. + +Each call may start an attempt. The caller owns first-failure selection and duplicate suppression. An error, including a timeout or cancellation, can occur after the provider accepted the work, so it must not cause an automatic resubmission. Implementations return plain dependency errors and must support concurrent calls. + +`Factory.For` resolves an executor bound to `Config.QueueName`. Generated mocks are available in [`mock`](mock/fixer_mock.go). diff --git a/stovepipe/extension/fixer/fixer.go b/stovepipe/extension/fixer/fixer.go new file mode 100644 index 000000000..0622f0be9 --- /dev/null +++ b/stovepipe/extension/fixer/fixer.go @@ -0,0 +1,45 @@ +// 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 fixer defines repair submission for an open issue. +package fixer + +//go:generate mockgen -source=fixer.go -destination=mock/fixer_mock.go -package=mock + +import ( + "context" + + "github.com/uber/submitqueue/stovepipe/entity" +) + +// RepairExecutor submits repair work for one open issue within a configured queue. +// Implementations must be concurrency-safe and return promptly after submission. +type RepairExecutor interface { + // Submit returns a nonempty opaque provider attempt ID on acceptance. + // Acceptance does not establish recovery. + // Repeated calls may start additional attempts. A plain error can follow acceptance, + // including on context cancellation; callers must not automatically repeat Submit. + Submit(context.Context, entity.OpenIssue) (string, error) +} + +// Config identifies the queue whose repair executor is requested. +type Config struct { + // QueueName identifies the validation queue. + QueueName string +} + +// Factory resolves queue-bound executors; routing belongs to service wiring. +type Factory interface { + For(Config) (RepairExecutor, error) +} diff --git a/stovepipe/extension/fixer/mock/BUILD.bazel b/stovepipe/extension/fixer/mock/BUILD.bazel new file mode 100644 index 000000000..5727675f3 --- /dev/null +++ b/stovepipe/extension/fixer/mock/BUILD.bazel @@ -0,0 +1,13 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["fixer_mock.go"], + importpath = "github.com/uber/submitqueue/stovepipe/extension/fixer/mock", + visibility = ["//visibility:public"], + deps = [ + "//stovepipe/entity:go_default_library", + "//stovepipe/extension/fixer:go_default_library", + "@org_uber_go_mock//gomock:go_default_library", + ], +) diff --git a/stovepipe/extension/fixer/mock/fixer_mock.go b/stovepipe/extension/fixer/mock/fixer_mock.go new file mode 100644 index 000000000..25e4779f5 --- /dev/null +++ b/stovepipe/extension/fixer/mock/fixer_mock.go @@ -0,0 +1,97 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: fixer.go +// +// Generated by this command: +// +// mockgen -source=fixer.go -destination=mock/fixer_mock.go -package=mock +// + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + entity "github.com/uber/submitqueue/stovepipe/entity" + fixer "github.com/uber/submitqueue/stovepipe/extension/fixer" + gomock "go.uber.org/mock/gomock" +) + +// MockRepairExecutor is a mock of RepairExecutor interface. +type MockRepairExecutor struct { + ctrl *gomock.Controller + recorder *MockRepairExecutorMockRecorder + isgomock struct{} +} + +// MockRepairExecutorMockRecorder is the mock recorder for MockRepairExecutor. +type MockRepairExecutorMockRecorder struct { + mock *MockRepairExecutor +} + +// NewMockRepairExecutor creates a new mock instance. +func NewMockRepairExecutor(ctrl *gomock.Controller) *MockRepairExecutor { + mock := &MockRepairExecutor{ctrl: ctrl} + mock.recorder = &MockRepairExecutorMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockRepairExecutor) EXPECT() *MockRepairExecutorMockRecorder { + return m.recorder +} + +// Submit mocks base method. +func (m *MockRepairExecutor) Submit(arg0 context.Context, arg1 entity.OpenIssue) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Submit", arg0, arg1) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Submit indicates an expected call of Submit. +func (mr *MockRepairExecutorMockRecorder) Submit(arg0, arg1 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Submit", reflect.TypeOf((*MockRepairExecutor)(nil).Submit), arg0, arg1) +} + +// MockFactory is a mock of Factory interface. +type MockFactory struct { + ctrl *gomock.Controller + recorder *MockFactoryMockRecorder + isgomock struct{} +} + +// MockFactoryMockRecorder is the mock recorder for MockFactory. +type MockFactoryMockRecorder struct { + mock *MockFactory +} + +// NewMockFactory creates a new mock instance. +func NewMockFactory(ctrl *gomock.Controller) *MockFactory { + mock := &MockFactory{ctrl: ctrl} + mock.recorder = &MockFactoryMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockFactory) EXPECT() *MockFactoryMockRecorder { + return m.recorder +} + +// For mocks base method. +func (m *MockFactory) For(arg0 fixer.Config) (fixer.RepairExecutor, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "For", arg0) + ret0, _ := ret[0].(fixer.RepairExecutor) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// For indicates an expected call of For. +func (mr *MockFactoryMockRecorder) For(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "For", reflect.TypeOf((*MockFactory)(nil).For), arg0) +} From cd39a5ff9361954758a19e749ed25a065d33909c Mon Sep 17 00:00:00 2001 From: Michael Noah <92764374+mnoah1@users.noreply.github.com> Date: Fri, 23 Oct 2026 15:39:11 -0400 Subject: [PATCH 2/2] Update README.md --- stovepipe/extension/fixer/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stovepipe/extension/fixer/README.md b/stovepipe/extension/fixer/README.md index a0a840e14..4486c7c24 100644 --- a/stovepipe/extension/fixer/README.md +++ b/stovepipe/extension/fixer/README.md @@ -4,6 +4,6 @@ `Submit` returns promptly with a nonempty opaque provider attempt ID on acceptance, or an error. Acceptance does not establish recovery. -Each call may start an attempt. The caller owns first-failure selection and duplicate suppression. An error, including a timeout or cancellation, can occur after the provider accepted the work, so it must not cause an automatic resubmission. Implementations return plain dependency errors and must support concurrent calls. +An error, including a timeout or cancellation, can occur after the provider accepted the work, so it must not cause an automatic resubmission. Implementations return plain dependency errors and must support concurrent calls. `Factory.For` resolves an executor bound to `Config.QueueName`. Generated mocks are available in [`mock`](mock/fixer_mock.go).