From 2b015262ed077afbd8d761ca83e4a082caa6a36a Mon Sep 17 00:00:00 2001 From: manjari Date: Tue, 13 Oct 2026 17:59:51 +0000 Subject: [PATCH 1/2] feat(storage): Add target signature and dictionary store contracts --- doc/rfc/service-scoped-extensions.md | 8 +-- submitqueue/entity/BUILD.bazel | 2 + submitqueue/entity/target_label.go | 33 +++++++++ submitqueue/entity/target_signature.go | 45 ++++++++++++ submitqueue/extension/storage/BUILD.bazel | 2 + .../extension/storage/mock/BUILD.bazel | 2 + .../mock/target_dictionary_store_mock.go | 72 +++++++++++++++++++ .../mock/target_signature_store_mock.go | 72 +++++++++++++++++++ .../storage/target_dictionary_store.go | 38 ++++++++++ .../storage/target_signature_store.go | 38 ++++++++++ 10 files changed, 308 insertions(+), 4 deletions(-) create mode 100644 submitqueue/entity/target_label.go create mode 100644 submitqueue/entity/target_signature.go create mode 100644 submitqueue/extension/storage/mock/target_dictionary_store_mock.go create mode 100644 submitqueue/extension/storage/mock/target_signature_store_mock.go create mode 100644 submitqueue/extension/storage/target_dictionary_store.go create mode 100644 submitqueue/extension/storage/target_signature_store.go diff --git a/doc/rfc/service-scoped-extensions.md b/doc/rfc/service-scoped-extensions.md index 555447091..a0ba16b36 100644 --- a/doc/rfc/service-scoped-extensions.md +++ b/doc/rfc/service-scoped-extensions.md @@ -4,7 +4,7 @@ Deciding which service of a multi-service domain an extension belongs to, and mo ## Status -Implemented for SubmitQueue storage and for the core packages that served one service. `submitqueue/gateway/extension/storage` and `submitqueue/orchestrator/extension/storage` each declare a `Factory`, a `Storage` aggregate, and a MySQL schema. The thirteen store contracts, their mocks, and the error vocabulary remain at `submitqueue/extension/storage`. There is no `submitqueue/extension/storage/mysql` package. `submitqueue/extension/storage/BUILD.bazel` filegroups the two service schemas for deployments that colocate them. +Implemented for SubmitQueue storage and for the core packages that served one service. `submitqueue/gateway/extension/storage` and `submitqueue/orchestrator/extension/storage` each declare a `Factory`, a `Storage` aggregate, and a MySQL schema. The fifteen store contracts, their mocks, and the error vocabulary remain at `submitqueue/extension/storage`. There is no `submitqueue/extension/storage/mysql` package. `submitqueue/extension/storage/BUILD.bazel` filegroups the two service schemas for deployments that colocate them. `submitqueue/core/batch` and `submitqueue/core/request` are not on disk. Batch helpers live in `submitqueue/orchestrator/core/batch`. Request-log publish and terminate live in `submitqueue/orchestrator/core/request`. The gateway materializer lives in `submitqueue/gateway/core/request`. `submitqueue/core` holds `changeset`, `messagequeue`, and `topickey`. `changeset` still declares `Stores` and `Resolve`, because `buildrunner`, `conflict`, and `speculation` are still domain-level packages that depend on it. Stovepipe keeps `stovepipe/extension/storage/mysql` because that domain is the service; that package is not a leftover SubmitQueue backend. @@ -26,7 +26,7 @@ Separating the services onto their own databases already worked — the orchestr 2. An extension shared between a domain's services stays wholly at `{domain}/extension/{ext}/` — including one used by a single service today but expected in both. `submitqueue/extension/queueconfig/` is gateway-only now and stays where it is on that basis. A single-service domain is unaffected: its domain root is its service root, so nothing moves. 3. Storage is the extension this split applied to. `changeprovider`, `validator`, `conflict`, `buildrunner` and `speculation` are orchestrator-only and would qualify under Decision 1, but none has a schema and none was the reason for this change; they stay at `{domain}/extension/` until a later RFC takes them. The trigger for moving storage was a schema that a separate-database deployment must not over-provision. 4. SubmitQueue's storage aggregate splits in two: `submitqueue/gateway/extension/storage/` declares a `Factory` and a four-accessor `Storage`, `submitqueue/orchestrator/extension/storage/` a `Factory` and a nine-accessor `Storage`. Neither can resolve the other's stores. -5. `submitqueue/extension/storage/` keeps the thirteen store contracts, their mocks, the error vocabulary, `Config`, and the package README, which documents the contracts. Its import path does not change, so callers that name only a contract or an error are untouched. +5. `submitqueue/extension/storage/` keeps the fifteen store contracts, their mocks, the error vocabulary, `Config`, and the package README, which documents the contracts. Its import path does not change, so callers that name only a contract or an error are untouched. 6. The contract package is not promoted to `platform/`. It is not cross-domain — `stovepipe/extension/storage/storage.go` already declares its own verbatim copy of the same symbols, so a per-domain error vocabulary is the existing pattern rather than something this RFC introduces. 7. Each half owns its own MySQL schema directory, so the tables a service creates are the tables it reads. A union target over both stays available for deployments that colocate the services — the end-to-end tests and the local compose stack take it and name no service directory. Split schemas are the opt-in, not the default. @@ -60,11 +60,11 @@ Every store, verified against actual usage rather than intent. ``` submitqueue/ ├── extension/storage/ # contracts, shared -│ ├── {13 stores}_store.go # BatchStore, RequestLogStore, … +│ ├── {15 stores}_store.go # BatchStore, RequestLogStore, … │ ├── storage.go # ErrNotFound, ErrAlreadyExists, ErrVersionMismatch, │ │ # IsNotFound, WrapNotFound, Config │ ├── README.md -│ └── mock/ # the 13 store mocks +│ └── mock/ # the 15 store mocks ├── gateway/extension/storage/ │ ├── storage.go # Factory + Storage, 4 accessors │ ├── mock/ # the aggregate's mock diff --git a/submitqueue/entity/BUILD.bazel b/submitqueue/entity/BUILD.bazel index 6e1561ab1..0e7f3b135 100644 --- a/submitqueue/entity/BUILD.bazel +++ b/submitqueue/entity/BUILD.bazel @@ -25,6 +25,8 @@ go_library( "request_summary.go", "speculation.go", "subject.go", + "target_label.go", + "target_signature.go", ], importpath = "github.com/uber/submitqueue/submitqueue/entity", visibility = ["//visibility:public"], diff --git a/submitqueue/entity/target_label.go b/submitqueue/entity/target_label.go new file mode 100644 index 000000000..1bb4b6025 --- /dev/null +++ b/submitqueue/entity/target_label.go @@ -0,0 +1,33 @@ +// 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 + +// TargetLabel is one build target label and the 64-bit ID it hashes to. The +// whole record is immutable: (Queue, ID) is its identity and there is no update +// path. Two labels can share an ID on a hash collision; the first one recorded +// stays. +type TargetLabel struct { + // Queue is the queue the label was seen in. Unique together with ID. + Queue string + + // ID is the 64-bit hash of Label. + ID uint64 + + // Label is the target label exactly as the build graph reported it. + Label string + + // CreatedAt is the Unix milliseconds timestamp when the record was created. + CreatedAt int64 +} diff --git a/submitqueue/entity/target_signature.go b/submitqueue/entity/target_signature.go new file mode 100644 index 000000000..16483ebec --- /dev/null +++ b/submitqueue/entity/target_signature.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 entity + +// TargetSignature is the set of build targets a batch changes, as 64-bit IDs. +// The whole record is immutable: (Queue, BatchID) is its identity and there is +// no update path. +type TargetSignature struct { + // Queue is the queue the batch belongs to. Unique together with BatchID. + Queue string + + // BatchID is the canonical decimal batch identifier, unique within Queue. + BatchID string + + // BaseRevision is the commit SHA the changed targets were computed against. + BaseRevision string + + // Targets is sorted ascending by ID, with no duplicate IDs. + Targets []SignatureTarget + + // CreatedAt is the Unix milliseconds timestamp when the record was created. + CreatedAt int64 +} + +// SignatureTarget is one changed build target in a TargetSignature. +type SignatureTarget struct { + // ID is the 64-bit hash of the target's label. + ID uint64 + + // Distance is the distance from the nearest directly changed source in the + // reverse dependency graph; 0 means directly changed. + Distance int32 +} diff --git a/submitqueue/extension/storage/BUILD.bazel b/submitqueue/extension/storage/BUILD.bazel index e75af25dd..32290350c 100644 --- a/submitqueue/extension/storage/BUILD.bazel +++ b/submitqueue/extension/storage/BUILD.bazel @@ -17,6 +17,8 @@ go_library( "request_uri_store.go", "speculation_path_set_store.go", "storage.go", + "target_dictionary_store.go", + "target_signature_store.go", ], importpath = "github.com/uber/submitqueue/submitqueue/extension/storage", visibility = ["//visibility:public"], diff --git a/submitqueue/extension/storage/mock/BUILD.bazel b/submitqueue/extension/storage/mock/BUILD.bazel index 49f2907c6..a714ed273 100644 --- a/submitqueue/extension/storage/mock/BUILD.bazel +++ b/submitqueue/extension/storage/mock/BUILD.bazel @@ -16,6 +16,8 @@ go_library( "request_summary_store_mock.go", "request_uri_store_mock.go", "speculation_path_set_store_mock.go", + "target_dictionary_store_mock.go", + "target_signature_store_mock.go", ], importpath = "github.com/uber/submitqueue/submitqueue/extension/storage/mock", visibility = ["//visibility:public"], diff --git a/submitqueue/extension/storage/mock/target_dictionary_store_mock.go b/submitqueue/extension/storage/mock/target_dictionary_store_mock.go new file mode 100644 index 000000000..ddc3846f3 --- /dev/null +++ b/submitqueue/extension/storage/mock/target_dictionary_store_mock.go @@ -0,0 +1,72 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: target_dictionary_store.go +// +// Generated by this command: +// +// mockgen -source=target_dictionary_store.go -destination=mock/target_dictionary_store_mock.go -package=mock +// + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + entity "github.com/uber/submitqueue/submitqueue/entity" + gomock "go.uber.org/mock/gomock" +) + +// MockTargetDictionaryStore is a mock of TargetDictionaryStore interface. +type MockTargetDictionaryStore struct { + ctrl *gomock.Controller + recorder *MockTargetDictionaryStoreMockRecorder + isgomock struct{} +} + +// MockTargetDictionaryStoreMockRecorder is the mock recorder for MockTargetDictionaryStore. +type MockTargetDictionaryStoreMockRecorder struct { + mock *MockTargetDictionaryStore +} + +// NewMockTargetDictionaryStore creates a new mock instance. +func NewMockTargetDictionaryStore(ctrl *gomock.Controller) *MockTargetDictionaryStore { + mock := &MockTargetDictionaryStore{ctrl: ctrl} + mock.recorder = &MockTargetDictionaryStoreMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockTargetDictionaryStore) EXPECT() *MockTargetDictionaryStoreMockRecorder { + return m.recorder +} + +// Create mocks base method. +func (m *MockTargetDictionaryStore) Create(ctx context.Context, label entity.TargetLabel) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", ctx, label) + ret0, _ := ret[0].(error) + return ret0 +} + +// Create indicates an expected call of Create. +func (mr *MockTargetDictionaryStoreMockRecorder) Create(ctx, label any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockTargetDictionaryStore)(nil).Create), ctx, label) +} + +// Get mocks base method. +func (m *MockTargetDictionaryStore) Get(ctx context.Context, id uint64) (entity.TargetLabel, bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", ctx, id) + ret0, _ := ret[0].(entity.TargetLabel) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// Get indicates an expected call of Get. +func (mr *MockTargetDictionaryStoreMockRecorder) Get(ctx, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockTargetDictionaryStore)(nil).Get), ctx, id) +} diff --git a/submitqueue/extension/storage/mock/target_signature_store_mock.go b/submitqueue/extension/storage/mock/target_signature_store_mock.go new file mode 100644 index 000000000..0256b3b99 --- /dev/null +++ b/submitqueue/extension/storage/mock/target_signature_store_mock.go @@ -0,0 +1,72 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: target_signature_store.go +// +// Generated by this command: +// +// mockgen -source=target_signature_store.go -destination=mock/target_signature_store_mock.go -package=mock +// + +// Package mock is a generated GoMock package. +package mock + +import ( + context "context" + reflect "reflect" + + entity "github.com/uber/submitqueue/submitqueue/entity" + gomock "go.uber.org/mock/gomock" +) + +// MockTargetSignatureStore is a mock of TargetSignatureStore interface. +type MockTargetSignatureStore struct { + ctrl *gomock.Controller + recorder *MockTargetSignatureStoreMockRecorder + isgomock struct{} +} + +// MockTargetSignatureStoreMockRecorder is the mock recorder for MockTargetSignatureStore. +type MockTargetSignatureStoreMockRecorder struct { + mock *MockTargetSignatureStore +} + +// NewMockTargetSignatureStore creates a new mock instance. +func NewMockTargetSignatureStore(ctrl *gomock.Controller) *MockTargetSignatureStore { + mock := &MockTargetSignatureStore{ctrl: ctrl} + mock.recorder = &MockTargetSignatureStoreMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockTargetSignatureStore) EXPECT() *MockTargetSignatureStoreMockRecorder { + return m.recorder +} + +// Create mocks base method. +func (m *MockTargetSignatureStore) Create(ctx context.Context, signature entity.TargetSignature) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", ctx, signature) + ret0, _ := ret[0].(error) + return ret0 +} + +// Create indicates an expected call of Create. +func (mr *MockTargetSignatureStoreMockRecorder) Create(ctx, signature any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockTargetSignatureStore)(nil).Create), ctx, signature) +} + +// Get mocks base method. +func (m *MockTargetSignatureStore) Get(ctx context.Context, batchID string) (entity.TargetSignature, bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", ctx, batchID) + ret0, _ := ret[0].(entity.TargetSignature) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// Get indicates an expected call of Get. +func (mr *MockTargetSignatureStoreMockRecorder) Get(ctx, batchID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockTargetSignatureStore)(nil).Get), ctx, batchID) +} diff --git a/submitqueue/extension/storage/target_dictionary_store.go b/submitqueue/extension/storage/target_dictionary_store.go new file mode 100644 index 000000000..b3b9fde0c --- /dev/null +++ b/submitqueue/extension/storage/target_dictionary_store.go @@ -0,0 +1,38 @@ +// 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 storage + +//go:generate mockgen -source=target_dictionary_store.go -destination=mock/target_dictionary_store_mock.go -package=mock + +import ( + "context" + + "github.com/uber/submitqueue/submitqueue/entity" +) + +// TargetDictionaryStore persists the target dictionary: one TargetLabel record +// for each 64-bit target ID. A record is immutable: it is created once under +// (Queue, ID) and never updated. Two labels can share an ID, so the first label +// recorded stays and the caller can read it back with Get to detect a collision. +type TargetDictionaryStore interface { + // Create persists label in one write. Returns ErrAlreadyExists if the + // bound queue already holds a label for label.ID, whether or not it is the + // same label, so repeating a write is harmless. + Create(ctx context.Context, label entity.TargetLabel) error + + // Get returns the label recorded for id in the bound queue. The bool is + // false when none has been recorded. + Get(ctx context.Context, id uint64) (entity.TargetLabel, bool, error) +} diff --git a/submitqueue/extension/storage/target_signature_store.go b/submitqueue/extension/storage/target_signature_store.go new file mode 100644 index 000000000..c2ca1c655 --- /dev/null +++ b/submitqueue/extension/storage/target_signature_store.go @@ -0,0 +1,38 @@ +// 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 storage + +//go:generate mockgen -source=target_signature_store.go -destination=mock/target_signature_store_mock.go -package=mock + +import ( + "context" + + "github.com/uber/submitqueue/submitqueue/entity" +) + +// TargetSignatureStore persists the target signature of each batch. A +// signature is immutable: it is created once under (Queue, BatchID) and never +// updated. Reads and writes are per-batch point operations, so any backend can +// implement it. +type TargetSignatureStore interface { + // Create persists signature in one write. Returns ErrAlreadyExists if the + // bound queue already holds a signature for signature.BatchID, so the first + // signature written stays and the caller can read it back with Get. + Create(ctx context.Context, signature entity.TargetSignature) error + + // Get returns the signature of batchID in the bound queue. The bool is + // false when no signature has been created for the batch. + Get(ctx context.Context, batchID string) (entity.TargetSignature, bool, error) +} From 5d2bc1eb42aee54846ac7946fe6abfc038cb5a5f Mon Sep 17 00:00:00 2001 From: manjari Date: Thu, 15 Oct 2026 21:29:06 +0000 Subject: [PATCH 2/2] refactor(conflict): Move target signature and dictionary contracts under tango --- doc/rfc/service-scoped-extensions.md | 8 +- submitqueue/entity/BUILD.bazel | 2 - .../extension/conflict/tango/BUILD.bazel | 14 ++ .../extension/conflict/tango/mock_store.go | 205 ++++++++++++++++++ submitqueue/extension/conflict/tango/store.go | 60 +++++ .../conflict/tango}/target_label.go | 2 +- .../conflict/tango}/target_signature.go | 2 +- submitqueue/extension/storage/BUILD.bazel | 2 - .../extension/storage/mock/BUILD.bazel | 2 - .../mock/target_dictionary_store_mock.go | 72 ------ .../mock/target_signature_store_mock.go | 72 ------ .../storage/target_dictionary_store.go | 38 ---- .../storage/target_signature_store.go | 38 ---- 13 files changed, 285 insertions(+), 232 deletions(-) create mode 100644 submitqueue/extension/conflict/tango/BUILD.bazel create mode 100644 submitqueue/extension/conflict/tango/mock_store.go create mode 100644 submitqueue/extension/conflict/tango/store.go rename submitqueue/{entity => extension/conflict/tango}/target_label.go (98%) rename submitqueue/{entity => extension/conflict/tango}/target_signature.go (99%) delete mode 100644 submitqueue/extension/storage/mock/target_dictionary_store_mock.go delete mode 100644 submitqueue/extension/storage/mock/target_signature_store_mock.go delete mode 100644 submitqueue/extension/storage/target_dictionary_store.go delete mode 100644 submitqueue/extension/storage/target_signature_store.go diff --git a/doc/rfc/service-scoped-extensions.md b/doc/rfc/service-scoped-extensions.md index a0ba16b36..555447091 100644 --- a/doc/rfc/service-scoped-extensions.md +++ b/doc/rfc/service-scoped-extensions.md @@ -4,7 +4,7 @@ Deciding which service of a multi-service domain an extension belongs to, and mo ## Status -Implemented for SubmitQueue storage and for the core packages that served one service. `submitqueue/gateway/extension/storage` and `submitqueue/orchestrator/extension/storage` each declare a `Factory`, a `Storage` aggregate, and a MySQL schema. The fifteen store contracts, their mocks, and the error vocabulary remain at `submitqueue/extension/storage`. There is no `submitqueue/extension/storage/mysql` package. `submitqueue/extension/storage/BUILD.bazel` filegroups the two service schemas for deployments that colocate them. +Implemented for SubmitQueue storage and for the core packages that served one service. `submitqueue/gateway/extension/storage` and `submitqueue/orchestrator/extension/storage` each declare a `Factory`, a `Storage` aggregate, and a MySQL schema. The thirteen store contracts, their mocks, and the error vocabulary remain at `submitqueue/extension/storage`. There is no `submitqueue/extension/storage/mysql` package. `submitqueue/extension/storage/BUILD.bazel` filegroups the two service schemas for deployments that colocate them. `submitqueue/core/batch` and `submitqueue/core/request` are not on disk. Batch helpers live in `submitqueue/orchestrator/core/batch`. Request-log publish and terminate live in `submitqueue/orchestrator/core/request`. The gateway materializer lives in `submitqueue/gateway/core/request`. `submitqueue/core` holds `changeset`, `messagequeue`, and `topickey`. `changeset` still declares `Stores` and `Resolve`, because `buildrunner`, `conflict`, and `speculation` are still domain-level packages that depend on it. Stovepipe keeps `stovepipe/extension/storage/mysql` because that domain is the service; that package is not a leftover SubmitQueue backend. @@ -26,7 +26,7 @@ Separating the services onto their own databases already worked — the orchestr 2. An extension shared between a domain's services stays wholly at `{domain}/extension/{ext}/` — including one used by a single service today but expected in both. `submitqueue/extension/queueconfig/` is gateway-only now and stays where it is on that basis. A single-service domain is unaffected: its domain root is its service root, so nothing moves. 3. Storage is the extension this split applied to. `changeprovider`, `validator`, `conflict`, `buildrunner` and `speculation` are orchestrator-only and would qualify under Decision 1, but none has a schema and none was the reason for this change; they stay at `{domain}/extension/` until a later RFC takes them. The trigger for moving storage was a schema that a separate-database deployment must not over-provision. 4. SubmitQueue's storage aggregate splits in two: `submitqueue/gateway/extension/storage/` declares a `Factory` and a four-accessor `Storage`, `submitqueue/orchestrator/extension/storage/` a `Factory` and a nine-accessor `Storage`. Neither can resolve the other's stores. -5. `submitqueue/extension/storage/` keeps the fifteen store contracts, their mocks, the error vocabulary, `Config`, and the package README, which documents the contracts. Its import path does not change, so callers that name only a contract or an error are untouched. +5. `submitqueue/extension/storage/` keeps the thirteen store contracts, their mocks, the error vocabulary, `Config`, and the package README, which documents the contracts. Its import path does not change, so callers that name only a contract or an error are untouched. 6. The contract package is not promoted to `platform/`. It is not cross-domain — `stovepipe/extension/storage/storage.go` already declares its own verbatim copy of the same symbols, so a per-domain error vocabulary is the existing pattern rather than something this RFC introduces. 7. Each half owns its own MySQL schema directory, so the tables a service creates are the tables it reads. A union target over both stays available for deployments that colocate the services — the end-to-end tests and the local compose stack take it and name no service directory. Split schemas are the opt-in, not the default. @@ -60,11 +60,11 @@ Every store, verified against actual usage rather than intent. ``` submitqueue/ ├── extension/storage/ # contracts, shared -│ ├── {15 stores}_store.go # BatchStore, RequestLogStore, … +│ ├── {13 stores}_store.go # BatchStore, RequestLogStore, … │ ├── storage.go # ErrNotFound, ErrAlreadyExists, ErrVersionMismatch, │ │ # IsNotFound, WrapNotFound, Config │ ├── README.md -│ └── mock/ # the 15 store mocks +│ └── mock/ # the 13 store mocks ├── gateway/extension/storage/ │ ├── storage.go # Factory + Storage, 4 accessors │ ├── mock/ # the aggregate's mock diff --git a/submitqueue/entity/BUILD.bazel b/submitqueue/entity/BUILD.bazel index 0e7f3b135..6e1561ab1 100644 --- a/submitqueue/entity/BUILD.bazel +++ b/submitqueue/entity/BUILD.bazel @@ -25,8 +25,6 @@ go_library( "request_summary.go", "speculation.go", "subject.go", - "target_label.go", - "target_signature.go", ], importpath = "github.com/uber/submitqueue/submitqueue/entity", visibility = ["//visibility:public"], diff --git a/submitqueue/extension/conflict/tango/BUILD.bazel b/submitqueue/extension/conflict/tango/BUILD.bazel new file mode 100644 index 000000000..10f193dd1 --- /dev/null +++ b/submitqueue/extension/conflict/tango/BUILD.bazel @@ -0,0 +1,14 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "mock_store.go", + "store.go", + "target_label.go", + "target_signature.go", + ], + importpath = "github.com/uber/submitqueue/submitqueue/extension/conflict/tango", + visibility = ["//visibility:public"], + deps = ["@org_uber_go_mock//gomock:go_default_library"], +) diff --git a/submitqueue/extension/conflict/tango/mock_store.go b/submitqueue/extension/conflict/tango/mock_store.go new file mode 100644 index 000000000..8293d8f0d --- /dev/null +++ b/submitqueue/extension/conflict/tango/mock_store.go @@ -0,0 +1,205 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: store.go +// +// Generated by this command: +// +// mockgen -source=store.go -destination=mock_store.go -package=tango +// + +// Package tango is a generated GoMock package. +package tango + +import ( + context "context" + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockSignatureReader is a mock of SignatureReader interface. +type MockSignatureReader struct { + ctrl *gomock.Controller + recorder *MockSignatureReaderMockRecorder + isgomock struct{} +} + +// MockSignatureReaderMockRecorder is the mock recorder for MockSignatureReader. +type MockSignatureReaderMockRecorder struct { + mock *MockSignatureReader +} + +// NewMockSignatureReader creates a new mock instance. +func NewMockSignatureReader(ctrl *gomock.Controller) *MockSignatureReader { + mock := &MockSignatureReader{ctrl: ctrl} + mock.recorder = &MockSignatureReaderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSignatureReader) EXPECT() *MockSignatureReaderMockRecorder { + return m.recorder +} + +// Get mocks base method. +func (m *MockSignatureReader) Get(ctx context.Context, batchID string) (TargetSignature, bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", ctx, batchID) + ret0, _ := ret[0].(TargetSignature) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// Get indicates an expected call of Get. +func (mr *MockSignatureReaderMockRecorder) Get(ctx, batchID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockSignatureReader)(nil).Get), ctx, batchID) +} + +// MockSignatureStore is a mock of SignatureStore interface. +type MockSignatureStore struct { + ctrl *gomock.Controller + recorder *MockSignatureStoreMockRecorder + isgomock struct{} +} + +// MockSignatureStoreMockRecorder is the mock recorder for MockSignatureStore. +type MockSignatureStoreMockRecorder struct { + mock *MockSignatureStore +} + +// NewMockSignatureStore creates a new mock instance. +func NewMockSignatureStore(ctrl *gomock.Controller) *MockSignatureStore { + mock := &MockSignatureStore{ctrl: ctrl} + mock.recorder = &MockSignatureStoreMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockSignatureStore) EXPECT() *MockSignatureStoreMockRecorder { + return m.recorder +} + +// Create mocks base method. +func (m *MockSignatureStore) Create(ctx context.Context, signature TargetSignature) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", ctx, signature) + ret0, _ := ret[0].(error) + return ret0 +} + +// Create indicates an expected call of Create. +func (mr *MockSignatureStoreMockRecorder) Create(ctx, signature any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockSignatureStore)(nil).Create), ctx, signature) +} + +// Get mocks base method. +func (m *MockSignatureStore) Get(ctx context.Context, batchID string) (TargetSignature, bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", ctx, batchID) + ret0, _ := ret[0].(TargetSignature) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// Get indicates an expected call of Get. +func (mr *MockSignatureStoreMockRecorder) Get(ctx, batchID any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockSignatureStore)(nil).Get), ctx, batchID) +} + +// MockDictionaryReader is a mock of DictionaryReader interface. +type MockDictionaryReader struct { + ctrl *gomock.Controller + recorder *MockDictionaryReaderMockRecorder + isgomock struct{} +} + +// MockDictionaryReaderMockRecorder is the mock recorder for MockDictionaryReader. +type MockDictionaryReaderMockRecorder struct { + mock *MockDictionaryReader +} + +// NewMockDictionaryReader creates a new mock instance. +func NewMockDictionaryReader(ctrl *gomock.Controller) *MockDictionaryReader { + mock := &MockDictionaryReader{ctrl: ctrl} + mock.recorder = &MockDictionaryReaderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDictionaryReader) EXPECT() *MockDictionaryReaderMockRecorder { + return m.recorder +} + +// Get mocks base method. +func (m *MockDictionaryReader) Get(ctx context.Context, id uint64) (TargetLabel, bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", ctx, id) + ret0, _ := ret[0].(TargetLabel) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// Get indicates an expected call of Get. +func (mr *MockDictionaryReaderMockRecorder) Get(ctx, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockDictionaryReader)(nil).Get), ctx, id) +} + +// MockDictionaryStore is a mock of DictionaryStore interface. +type MockDictionaryStore struct { + ctrl *gomock.Controller + recorder *MockDictionaryStoreMockRecorder + isgomock struct{} +} + +// MockDictionaryStoreMockRecorder is the mock recorder for MockDictionaryStore. +type MockDictionaryStoreMockRecorder struct { + mock *MockDictionaryStore +} + +// NewMockDictionaryStore creates a new mock instance. +func NewMockDictionaryStore(ctrl *gomock.Controller) *MockDictionaryStore { + mock := &MockDictionaryStore{ctrl: ctrl} + mock.recorder = &MockDictionaryStoreMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDictionaryStore) EXPECT() *MockDictionaryStoreMockRecorder { + return m.recorder +} + +// Create mocks base method. +func (m *MockDictionaryStore) Create(ctx context.Context, label TargetLabel) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Create", ctx, label) + ret0, _ := ret[0].(error) + return ret0 +} + +// Create indicates an expected call of Create. +func (mr *MockDictionaryStoreMockRecorder) Create(ctx, label any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockDictionaryStore)(nil).Create), ctx, label) +} + +// Get mocks base method. +func (m *MockDictionaryStore) Get(ctx context.Context, id uint64) (TargetLabel, bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Get", ctx, id) + ret0, _ := ret[0].(TargetLabel) + ret1, _ := ret[1].(bool) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// Get indicates an expected call of Get. +func (mr *MockDictionaryStoreMockRecorder) Get(ctx, id any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockDictionaryStore)(nil).Get), ctx, id) +} diff --git a/submitqueue/extension/conflict/tango/store.go b/submitqueue/extension/conflict/tango/store.go new file mode 100644 index 000000000..0061bc5d7 --- /dev/null +++ b/submitqueue/extension/conflict/tango/store.go @@ -0,0 +1,60 @@ +// 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 tango + +//go:generate mockgen -source=store.go -destination=mock_store.go -package=tango + +import "context" + +// SignatureReader reads the target signature of a batch. It is the read-only +// view of SignatureStore for consumers that must not write. +type SignatureReader interface { + // Get returns the signature of batchID in the bound queue. The bool is + // false when no signature has been created for the batch. + Get(ctx context.Context, batchID string) (TargetSignature, bool, error) +} + +// SignatureStore persists target signatures. A signature is immutable: it is +// created once under (Queue, BatchID) and never updated. Reads and writes are +// per-batch point operations, so any backend can implement it. +type SignatureStore interface { + SignatureReader + + // Create persists signature in one write. Returns an already-exists error + // if the bound queue already holds a signature for signature.BatchID, so the + // first signature written stays and the caller can read it back with Get. + Create(ctx context.Context, signature TargetSignature) error +} + +// DictionaryReader reads the label recorded for a target ID. It is the +// read-only view of DictionaryStore. +type DictionaryReader interface { + // Get returns the label recorded for id in the bound queue. The bool is + // false when none has been recorded. + Get(ctx context.Context, id uint64) (TargetLabel, bool, error) +} + +// DictionaryStore persists the target dictionary: one TargetLabel for each +// 64-bit target ID. A record is immutable: it is created once under +// (Queue, ID) and never updated. Two labels can share an ID, so the first label +// recorded stays and the caller can read it back with Get to detect a collision. +type DictionaryStore interface { + DictionaryReader + + // Create persists label in one write. Returns an already-exists error if + // the bound queue holds a label for label.ID, whether or not it is the same + // label, so repeating a write is harmless. + Create(ctx context.Context, label TargetLabel) error +} diff --git a/submitqueue/entity/target_label.go b/submitqueue/extension/conflict/tango/target_label.go similarity index 98% rename from submitqueue/entity/target_label.go rename to submitqueue/extension/conflict/tango/target_label.go index 1bb4b6025..2bfa70bd2 100644 --- a/submitqueue/entity/target_label.go +++ b/submitqueue/extension/conflict/tango/target_label.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package entity +package tango // TargetLabel is one build target label and the 64-bit ID it hashes to. The // whole record is immutable: (Queue, ID) is its identity and there is no update diff --git a/submitqueue/entity/target_signature.go b/submitqueue/extension/conflict/tango/target_signature.go similarity index 99% rename from submitqueue/entity/target_signature.go rename to submitqueue/extension/conflict/tango/target_signature.go index 16483ebec..fe6793c51 100644 --- a/submitqueue/entity/target_signature.go +++ b/submitqueue/extension/conflict/tango/target_signature.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package entity +package tango // TargetSignature is the set of build targets a batch changes, as 64-bit IDs. // The whole record is immutable: (Queue, BatchID) is its identity and there is diff --git a/submitqueue/extension/storage/BUILD.bazel b/submitqueue/extension/storage/BUILD.bazel index 32290350c..e75af25dd 100644 --- a/submitqueue/extension/storage/BUILD.bazel +++ b/submitqueue/extension/storage/BUILD.bazel @@ -17,8 +17,6 @@ go_library( "request_uri_store.go", "speculation_path_set_store.go", "storage.go", - "target_dictionary_store.go", - "target_signature_store.go", ], importpath = "github.com/uber/submitqueue/submitqueue/extension/storage", visibility = ["//visibility:public"], diff --git a/submitqueue/extension/storage/mock/BUILD.bazel b/submitqueue/extension/storage/mock/BUILD.bazel index a714ed273..49f2907c6 100644 --- a/submitqueue/extension/storage/mock/BUILD.bazel +++ b/submitqueue/extension/storage/mock/BUILD.bazel @@ -16,8 +16,6 @@ go_library( "request_summary_store_mock.go", "request_uri_store_mock.go", "speculation_path_set_store_mock.go", - "target_dictionary_store_mock.go", - "target_signature_store_mock.go", ], importpath = "github.com/uber/submitqueue/submitqueue/extension/storage/mock", visibility = ["//visibility:public"], diff --git a/submitqueue/extension/storage/mock/target_dictionary_store_mock.go b/submitqueue/extension/storage/mock/target_dictionary_store_mock.go deleted file mode 100644 index ddc3846f3..000000000 --- a/submitqueue/extension/storage/mock/target_dictionary_store_mock.go +++ /dev/null @@ -1,72 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: target_dictionary_store.go -// -// Generated by this command: -// -// mockgen -source=target_dictionary_store.go -destination=mock/target_dictionary_store_mock.go -package=mock -// - -// Package mock is a generated GoMock package. -package mock - -import ( - context "context" - reflect "reflect" - - entity "github.com/uber/submitqueue/submitqueue/entity" - gomock "go.uber.org/mock/gomock" -) - -// MockTargetDictionaryStore is a mock of TargetDictionaryStore interface. -type MockTargetDictionaryStore struct { - ctrl *gomock.Controller - recorder *MockTargetDictionaryStoreMockRecorder - isgomock struct{} -} - -// MockTargetDictionaryStoreMockRecorder is the mock recorder for MockTargetDictionaryStore. -type MockTargetDictionaryStoreMockRecorder struct { - mock *MockTargetDictionaryStore -} - -// NewMockTargetDictionaryStore creates a new mock instance. -func NewMockTargetDictionaryStore(ctrl *gomock.Controller) *MockTargetDictionaryStore { - mock := &MockTargetDictionaryStore{ctrl: ctrl} - mock.recorder = &MockTargetDictionaryStoreMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockTargetDictionaryStore) EXPECT() *MockTargetDictionaryStoreMockRecorder { - return m.recorder -} - -// Create mocks base method. -func (m *MockTargetDictionaryStore) Create(ctx context.Context, label entity.TargetLabel) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Create", ctx, label) - ret0, _ := ret[0].(error) - return ret0 -} - -// Create indicates an expected call of Create. -func (mr *MockTargetDictionaryStoreMockRecorder) Create(ctx, label any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockTargetDictionaryStore)(nil).Create), ctx, label) -} - -// Get mocks base method. -func (m *MockTargetDictionaryStore) Get(ctx context.Context, id uint64) (entity.TargetLabel, bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Get", ctx, id) - ret0, _ := ret[0].(entity.TargetLabel) - ret1, _ := ret[1].(bool) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 -} - -// Get indicates an expected call of Get. -func (mr *MockTargetDictionaryStoreMockRecorder) Get(ctx, id any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockTargetDictionaryStore)(nil).Get), ctx, id) -} diff --git a/submitqueue/extension/storage/mock/target_signature_store_mock.go b/submitqueue/extension/storage/mock/target_signature_store_mock.go deleted file mode 100644 index 0256b3b99..000000000 --- a/submitqueue/extension/storage/mock/target_signature_store_mock.go +++ /dev/null @@ -1,72 +0,0 @@ -// Code generated by MockGen. DO NOT EDIT. -// Source: target_signature_store.go -// -// Generated by this command: -// -// mockgen -source=target_signature_store.go -destination=mock/target_signature_store_mock.go -package=mock -// - -// Package mock is a generated GoMock package. -package mock - -import ( - context "context" - reflect "reflect" - - entity "github.com/uber/submitqueue/submitqueue/entity" - gomock "go.uber.org/mock/gomock" -) - -// MockTargetSignatureStore is a mock of TargetSignatureStore interface. -type MockTargetSignatureStore struct { - ctrl *gomock.Controller - recorder *MockTargetSignatureStoreMockRecorder - isgomock struct{} -} - -// MockTargetSignatureStoreMockRecorder is the mock recorder for MockTargetSignatureStore. -type MockTargetSignatureStoreMockRecorder struct { - mock *MockTargetSignatureStore -} - -// NewMockTargetSignatureStore creates a new mock instance. -func NewMockTargetSignatureStore(ctrl *gomock.Controller) *MockTargetSignatureStore { - mock := &MockTargetSignatureStore{ctrl: ctrl} - mock.recorder = &MockTargetSignatureStoreMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockTargetSignatureStore) EXPECT() *MockTargetSignatureStoreMockRecorder { - return m.recorder -} - -// Create mocks base method. -func (m *MockTargetSignatureStore) Create(ctx context.Context, signature entity.TargetSignature) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Create", ctx, signature) - ret0, _ := ret[0].(error) - return ret0 -} - -// Create indicates an expected call of Create. -func (mr *MockTargetSignatureStoreMockRecorder) Create(ctx, signature any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockTargetSignatureStore)(nil).Create), ctx, signature) -} - -// Get mocks base method. -func (m *MockTargetSignatureStore) Get(ctx context.Context, batchID string) (entity.TargetSignature, bool, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Get", ctx, batchID) - ret0, _ := ret[0].(entity.TargetSignature) - ret1, _ := ret[1].(bool) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 -} - -// Get indicates an expected call of Get. -func (mr *MockTargetSignatureStoreMockRecorder) Get(ctx, batchID any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockTargetSignatureStore)(nil).Get), ctx, batchID) -} diff --git a/submitqueue/extension/storage/target_dictionary_store.go b/submitqueue/extension/storage/target_dictionary_store.go deleted file mode 100644 index b3b9fde0c..000000000 --- a/submitqueue/extension/storage/target_dictionary_store.go +++ /dev/null @@ -1,38 +0,0 @@ -// 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 storage - -//go:generate mockgen -source=target_dictionary_store.go -destination=mock/target_dictionary_store_mock.go -package=mock - -import ( - "context" - - "github.com/uber/submitqueue/submitqueue/entity" -) - -// TargetDictionaryStore persists the target dictionary: one TargetLabel record -// for each 64-bit target ID. A record is immutable: it is created once under -// (Queue, ID) and never updated. Two labels can share an ID, so the first label -// recorded stays and the caller can read it back with Get to detect a collision. -type TargetDictionaryStore interface { - // Create persists label in one write. Returns ErrAlreadyExists if the - // bound queue already holds a label for label.ID, whether or not it is the - // same label, so repeating a write is harmless. - Create(ctx context.Context, label entity.TargetLabel) error - - // Get returns the label recorded for id in the bound queue. The bool is - // false when none has been recorded. - Get(ctx context.Context, id uint64) (entity.TargetLabel, bool, error) -} diff --git a/submitqueue/extension/storage/target_signature_store.go b/submitqueue/extension/storage/target_signature_store.go deleted file mode 100644 index c2ca1c655..000000000 --- a/submitqueue/extension/storage/target_signature_store.go +++ /dev/null @@ -1,38 +0,0 @@ -// 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 storage - -//go:generate mockgen -source=target_signature_store.go -destination=mock/target_signature_store_mock.go -package=mock - -import ( - "context" - - "github.com/uber/submitqueue/submitqueue/entity" -) - -// TargetSignatureStore persists the target signature of each batch. A -// signature is immutable: it is created once under (Queue, BatchID) and never -// updated. Reads and writes are per-batch point operations, so any backend can -// implement it. -type TargetSignatureStore interface { - // Create persists signature in one write. Returns ErrAlreadyExists if the - // bound queue already holds a signature for signature.BatchID, so the first - // signature written stays and the caller can read it back with Get. - Create(ctx context.Context, signature entity.TargetSignature) error - - // Get returns the signature of batchID in the bound queue. The bool is - // false when no signature has been created for the batch. - Get(ctx context.Context, batchID string) (entity.TargetSignature, bool, error) -}