From 26c66b843590b269955617d4cd061e2aca6a6b58 Mon Sep 17 00:00:00 2001 From: mnoah1 Date: Thu, 17 Sep 2026 20:21:01 +0000 Subject: [PATCH] feat(stovepipe): pass request ID to build runners MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Intent: - Let build runners associate provider builds with the authoritative Stovepipe request without querying storage. Changes: - Define a stable optional BuildMetadata key for the Stovepipe request ID. - Pass the request ID from the build controller when triggering full and incremental builds. Issues: LINEAR-CODEM-511 Refs: CODEM-511 --- Generated by the 🪄 [pr-create](https://sg.uberinternal.com/code.uber.internal/uber-code/devexp-agent-marketplace/-/blob/claude-code/plugins/dev/uber-dev/skills/pr-create/SKILL.md) skill in devexp-agent-marketplace --- stovepipe/controller/build/build.go | 4 +++- stovepipe/controller/build/build_test.go | 18 +++++++++++------- stovepipe/entity/build.go | 10 ++++++---- 3 files changed, 20 insertions(+), 12 deletions(-) diff --git a/stovepipe/controller/build/build.go b/stovepipe/controller/build/build.go index 8891f23dd..3f6c9e0c0 100644 --- a/stovepipe/controller/build/build.go +++ b/stovepipe/controller/build/build.go @@ -137,7 +137,9 @@ func (c *Controller) Process(ctx context.Context, delivery consumer.Delivery) er baseURI = request.BaseURI } - buildID, err := buildRunner.Trigger(ctx, baseURI, request.URI, nil) + buildID, err := buildRunner.Trigger(ctx, baseURI, request.URI, entity.BuildMetadata{ + entity.BuildMetadataKeyRequestID: request.ID, + }) if err != nil { return fmt.Errorf("failed to trigger build for request %s: %w", request.ID, err) } diff --git a/stovepipe/controller/build/build_test.go b/stovepipe/controller/build/build_test.go index 905f41dd8..b7e6657f9 100644 --- a/stovepipe/controller/build/build_test.go +++ b/stovepipe/controller/build/build_test.go @@ -199,6 +199,10 @@ func processingRequest(strategy entity.BuildStrategy, baseURI string) entity.Req } } +func requestBuildMetadata() entity.BuildMetadata { + return entity.BuildMetadata{entity.BuildMetadataKeyRequestID: testID} +} + func TestProcess(t *testing.T) { tests := []struct { name string @@ -213,7 +217,7 @@ func TestProcess(t *testing.T) { req := processingRequest(entity.BuildStrategyIncrementalSinceGreen, testBaseURI) m.reqStore.EXPECT().Get(gomock.Any(), testID).Return(req, nil) m.runnerFactory.EXPECT().For(buildrunner.Config{QueueName: testQueue}).Return(m.runner, nil) - m.runner.EXPECT().Trigger(gomock.Any(), testBaseURI, testHeadURI, entity.BuildMetadata(nil)).Return(entity.BuildID{ID: testBuildID}, nil) + m.runner.EXPECT().Trigger(gomock.Any(), testBaseURI, testHeadURI, requestBuildMetadata()).Return(entity.BuildID{ID: testBuildID}, nil) build := entity.Build{ ID: testBuildID, RequestID: testID, @@ -231,7 +235,7 @@ func TestProcess(t *testing.T) { req := processingRequest(entity.BuildStrategyFull, testBaseURI) m.reqStore.EXPECT().Get(gomock.Any(), testID).Return(req, nil) m.runnerFactory.EXPECT().For(buildrunner.Config{QueueName: testQueue}).Return(m.runner, nil) - m.runner.EXPECT().Trigger(gomock.Any(), "", testHeadURI, entity.BuildMetadata(nil)).Return(entity.BuildID{ID: testBuildID}, nil) + m.runner.EXPECT().Trigger(gomock.Any(), "", testHeadURI, requestBuildMetadata()).Return(entity.BuildID{ID: testBuildID}, nil) build := entity.Build{ ID: testBuildID, RequestID: testID, @@ -319,7 +323,7 @@ func TestProcess(t *testing.T) { req := processingRequest(entity.BuildStrategyFull, "") m.reqStore.EXPECT().Get(gomock.Any(), testID).Return(req, nil) m.runnerFactory.EXPECT().For(buildrunner.Config{QueueName: testQueue}).Return(m.runner, nil) - m.runner.EXPECT().Trigger(gomock.Any(), "", testHeadURI, entity.BuildMetadata(nil)).Return(entity.BuildID{}, errors.New("runner down")) + m.runner.EXPECT().Trigger(gomock.Any(), "", testHeadURI, requestBuildMetadata()).Return(entity.BuildID{}, errors.New("runner down")) }, }, { @@ -329,7 +333,7 @@ func TestProcess(t *testing.T) { req := processingRequest(entity.BuildStrategyFull, "") m.reqStore.EXPECT().Get(gomock.Any(), testID).Return(req, nil) m.runnerFactory.EXPECT().For(buildrunner.Config{QueueName: testQueue}).Return(m.runner, nil) - m.runner.EXPECT().Trigger(gomock.Any(), "", testHeadURI, entity.BuildMetadata(nil)).Return(entity.BuildID{ID: testBuildID}, nil) + m.runner.EXPECT().Trigger(gomock.Any(), "", testHeadURI, requestBuildMetadata()).Return(entity.BuildID{ID: testBuildID}, nil) m.buildStore.EXPECT().Create(gomock.Any(), gomock.Any()).Return(storage.ErrAlreadyExists) }, }, @@ -341,7 +345,7 @@ func TestProcess(t *testing.T) { req := processingRequest(entity.BuildStrategyFull, "") m.reqStore.EXPECT().Get(gomock.Any(), testID).Return(req, nil) m.runnerFactory.EXPECT().For(buildrunner.Config{QueueName: testQueue}).Return(m.runner, nil) - m.runner.EXPECT().Trigger(gomock.Any(), "", testHeadURI, entity.BuildMetadata(nil)).Return(entity.BuildID{ID: testBuildID}, nil) + m.runner.EXPECT().Trigger(gomock.Any(), "", testHeadURI, requestBuildMetadata()).Return(entity.BuildID{ID: testBuildID}, nil) m.buildStore.EXPECT().Create(gomock.Any(), gomock.Any()).Return(errors.New("db down")) }, }, @@ -352,7 +356,7 @@ func TestProcess(t *testing.T) { req := processingRequest(entity.BuildStrategyFull, "") m.reqStore.EXPECT().Get(gomock.Any(), testID).Return(req, nil) m.runnerFactory.EXPECT().For(buildrunner.Config{QueueName: testQueue}).Return(m.runner, nil) - m.runner.EXPECT().Trigger(gomock.Any(), "", testHeadURI, entity.BuildMetadata(nil)).Return(entity.BuildID{ID: testBuildID}, nil) + m.runner.EXPECT().Trigger(gomock.Any(), "", testHeadURI, requestBuildMetadata()).Return(entity.BuildID{ID: testBuildID}, nil) createCall := m.buildStore.EXPECT().Create(gomock.Any(), gomock.Any()).Return(nil) request := entity.Request{ID: testID, Queue: testQueue} m.materializer.EXPECT().PersistLog( @@ -375,7 +379,7 @@ func TestProcess(t *testing.T) { req := processingRequest(entity.BuildStrategyFull, "") m.reqStore.EXPECT().Get(gomock.Any(), testID).Return(req, nil) m.runnerFactory.EXPECT().For(buildrunner.Config{QueueName: testQueue}).Return(m.runner, nil) - m.runner.EXPECT().Trigger(gomock.Any(), "", testHeadURI, entity.BuildMetadata(nil)).Return(entity.BuildID{ID: testBuildID}, nil) + m.runner.EXPECT().Trigger(gomock.Any(), "", testHeadURI, requestBuildMetadata()).Return(entity.BuildID{ID: testBuildID}, nil) createCall := m.buildStore.EXPECT().Create(gomock.Any(), gomock.Any()).Return(nil) logCall := expectBuildTriggered(m).After(createCall) m.publisher.EXPECT().Publish(gomock.Any(), "buildsignal", gomock.Any()).Return(errors.New("queue down")).After(logCall) diff --git a/stovepipe/entity/build.go b/stovepipe/entity/build.go index e278bf904..43f0aefcf 100644 --- a/stovepipe/entity/build.go +++ b/stovepipe/entity/build.go @@ -14,6 +14,11 @@ package entity +const ( + // BuildMetadataKeyRequestID identifies the Stovepipe request associated with a build. + BuildMetadataKeyRequestID = "request_id" +) + // BuildStatus defines the possible states of a build. Shaped the same as // SubmitQueue's own BuildStatus (submitqueue/entity/build.go), but defined // locally rather than shared — see build.md's "Alternatives considered for @@ -80,8 +85,5 @@ type BuildID struct { } // BuildMetadata carries caller-supplied, provider-echoed free-form metadata -// about a build. The runner must not depend on its contents. Empty today; -// expected to carry real data eventually (e.g. conflict-graph info, or other -// upstream decisions relevant to the build) once a concrete need lands in -// either domain — the shape is deferred until then, not decided here. +// about a build. The runner must not require any key to trigger a build. type BuildMetadata map[string]string