fix: attribute pipelines to the pipeline user, guard zero project ids
Problem
Pipeline attribution derived Pipeline.UserID from glJobs[0].User.ID (the first job's user). That is wrong for retried jobs and bridge/downstream pipelines, where the first job's user differs from who triggered the pipeline, and it yields 0 for pipelines with no jobs or a null job user.
Separately, deriveProjectIDs did not guard against a null project_id (value 0) on a merge request (MR) or issue. Project 0 was added to the active set and drove a guaranteed GET /projects/0/pipelines 404 that burns the collection error budget.
Fix
-
Attribute to the pipeline's own user. Added a
Userfield to thegitlabPipelinestruct. The GitLab pipelines endpoint returns a top-leveluserobject identifying the trigger.UserIDnow comes frompipeline.user.id, falling back to the first job's user only when the pipeline user is absent (for example, system-triggered pipelines). Jobs are still fetched: artifactfile_typedrives security and infrastructure scoring downstream. Aslog.Debugline records pipelines with no attributable user (UserIDstays0). -
Guard
ProjectID == 0inderiveProjectIDs. A nullproject_idon an MR or issue is skipped, so project0is never requested. -
UserID == 0 inertness (verified, no analyzer change). A pipeline with no attributable user stays
UserID: 0. The analyzer's per-user scoring (cicd,infrastructure,security) readsPipelinesByUser[userID]keyed by roster ids from/users, which never include0, so the0bucket is never scored or surfaced there. One map-iteration site (scorer.gobucket build feedingteams.go'suserPrimaryProjects) does range overPipelinesByUserdirectly and is not provably inert; that is flagged in a code comment for a follow-up analyzer guard. This MR is collector-only by scope, so no analyzer file is touched.
Validation
make check(go vet + full test suite) passes on Go 1.24.2.go test -race ./...passes on Go 1.24.2.- New tests:
TestCollectPipelines_AttributesToPipelineUser: pipelineuser.id99 differs fromjobs[0].user.id7; asserts attribution to 99.TestCollectPipelines_FallsBackToJobUser: null pipeline user falls back to job user 7.TestCollectPipelines_NoAttributableUser: null user, no jobs, yieldsUserID0.TestDeriveProjectIDs_SkipsZero: project0excluded from the derived set.TestDeriveProjectIDs_NoZeroPipelineRequest: recording server confirms no/projects/0/path is ever requested.
Fails-on-old proof
Temporarily reverting attribution to jobUserID (the old jobs[0] behavior) made TestCollectPipelines_AttributesToPipelineUser fail:
pipelines_test.go:246: expected user_id=99 (pipeline user), got 7
--- FAIL: TestCollectPipelines_AttributesToPipelineUserRestoring glp.User.ID returns the test to PASS.