Draft: ci: weekly upstream divergence mining
What this adds
A weekly scheduled job that watches the two upstreams Artifact Registry reimplements and files issues for the fixes that apply to us.
AR reimplements behavior owned by the
Container Registry
(Go: OCI handlers, storage drivers, metadata DB) and the
monolith's package registry
(Ruby: Maven/npm semantics). Those upstreams keep fixing bugs their users
hit in production, and nothing in our process notices — no MR fires, no
pipeline runs against our copy, so we can silently re-ship a bug they
already paid to find. The mapping is semantic (different files, names,
architecture), so rules:changes cannot express it.
Each new upstream commit gets classified against our code:
| Class | Meaning | Where it goes |
|---|---|---|
shared |
our code has the same problem | an issue in this project, with evidence |
immune |
our design sidesteps it | the run report |
future |
we have not built that subsystem yet | the run report |
not-mapped |
outside the subsystem map | the run report |
A quiet week means no new divergence.
Scope
- Periodic or manual, nothing else. The job runs on the weekly
schedule (which sets
DIVERGENCE_MINE=true), or on demand: Build → Pipelines → Run pipeline (or an API/glabtrigger), then press play ondivergence-mine. MR and branch pipelines never run it, soAR_DIVERGENCE_TOKENcannot reach a fork pipeline. - Issues only. Findings become issues here, deduplicated on an
upstream-sha:<sha>label, so re-runs never open a second issue for the same upstream commit — including one already closed. Low-confidence findings stay in the run report. Security-shaped findings route to the security mirror as confidential issues. - No code changes, no MRs. The two fix-proposing modes are disabled.
- Defined locally. The job lives in this repository rather than being included from the tool's project, so the rules that decide when the token enters a pipeline are reviewable here. The tool enters as a pinned container image.
- No new services or storage. State rides in the previous run's
report.jsonartifact.
| Path | Purpose |
|---|---|
.divergence/miner.yml |
Upstreams, lookback, mode, budgets |
.divergence/map.yml |
Which upstream area maps to which of our packages |
.divergence/ignore.yml |
Muted upstream commits |
.gitlab/ci/divergence-miner.gitlab-ci.yml |
The job |
docs/dev/divergence-mining.md |
How to operate and read it |
Required before merge
- Create
AR_DIVERGENCE_TOKEN— masked, and protected once the schedule targets a protected branch. Needs theapiandai_featuresscopes, and its owner needs a GitLab Duo seat: model calls route through Duo, so no vendor API key and no per-token bill. - Decide the token identity. A
gitlab-org/opsservice account with a Duo seat is durable; a personal token ties the schedule to one person's credential and attributes every run to them. Requested in #393 — this MR can merge before that lands, but the weekly schedule should wait for it. - Create the schedule after merge: cron
0 6 * * 1, targetmain, with the variableDIVERGENCE_MINE=true(the job's rules require it). Nothing runs until the schedule exists. - Review
.divergence/map.yml— it claims which upstream areas correspond to which of our packages, including thestatus: futureentries that say "not built yet, do not look closer." This is the file that needs human judgment. - Weigh the image source. The tool's image and project sit in a
personal namespace today
(divergence-miner);
transfer to
gitlab-org/opsplus a versioned catalog release are planned. AppSec flagged the original external include as the main risk here — vendoring the job locally addressed it, and reviewers should decide whether the pinned image is acceptable in the interim. - Agree who triages. Issues are assigned to a random project
maintainer by default;
assignment_strategyalso supportsround-robinandnone, andassigneestakes an explicit list.
Evidence
A six-month run of this configuration (both upstreams, budgets raised to
cover the window in one pass) processed 195 commits into 102 findings:
5 shared, 25 future, 56 immune, 15 not-mapped, nothing lost. The
shared findings — filed to a scratch project during testing — included
an S3 Stat() panic fix, a FIPS MD5-check fix from the monolith (a
Ruby → Go finding), and a storage-driver conformance-test gap our suite
is missing. Two look like real bugs in AR today. Findings the classifier
could not fully verify were withheld rather than filed.
Cost: a handful of model calls per week (merges, dependency bumps, release and CI churn, and anything unmapped are dropped before any model call), one ~10–20 minute job, one upstream clone on disk at a time.
Related to #115 (closed)