Skip to content

Config family version re-pin: bring GTB and all 19 adapters to config head, and stop the drift recurring

Authors
Matt Cockayne, Claude Fable 5 (AI drafting assistant)
Date
2026-07-23
Status
DRAFT — pending review
Related
architectural review (§ ecosystem, both HIGH findings + the credentials-diamond MEDIUM), config Apply absent-source fix (must land in core first; this sweep carries it), config v0.3 migration (the last deliberate GTB config bump — where the pin froze)

1. Problem

Two HIGH ecosystem findings, one shared root cause: propagation across the config family stalls because every re-release is a human-gated Release MR and a config-core change fans out into ~20–23 of them.

GTB is five minors behind config core. GTB go.mod pins gitlab.com/phpboyscout/go/config v0.4.0; the latest remote tag is v0.9.0. The missed range is not cosmetic — it includes hardening shipped specifically for consumers like GTB: v0.5.0 exported codec conformance suite, v0.6.0 Sensitive enforcement at the write path (ErrSensitiveLeak), v0.7.0 sensitive read-only backend refusing routed-beneath writes, v0.8.0 ErrReadOnlyFS, v0.9.0 PollIntervalHinter. The flagship consumer — the reason config exists — runs without the sensitive-write enforcement. And because pre-1.0 policy licenses breaking minors, every skipped minor makes the eventual catch-up larger and riskier.

The 19 adapters pin core across five different minors (all verified against remote-HEAD go.mods):

core pin adapters
v0.3.0 afero — the one adapter GTB actually uses, furthest behind
v0.5.0 dotenv, hcl, ini, json, properties, toml, xml
v0.6.0 aws-ssm, azure-appconfig, consul, gcp-parameter
v0.7.0 vault (also: created but never released — zero remote tags)
v0.9.0 aws-s3, azure-blob, billy, gcp-gcs, iofs, sftp

Any consumer combining a new-pin adapter with an old-pin one has MVS force the old adapter to build and run against config v0.9.0 — a combination no adapter CI ever tested. GTB does exactly this today: it pins config-afero v0.1.0 (built against core v0.3.0) alongside core v0.4.0. The first genuinely breaking core minor turns every stale-pinned adapter into a latent compile failure that surfaces in consumers' builds, not in the adapter's own pipeline.

Underlying mechanism failure: the fleet convention says Renovate proposes these bumps, yet config v0.4.0→v0.9.0 sat unadopted in GTB across five releases, and the adapter pins correlate with each repo's creation date. Renovate is either not running, not configured for the go/* group, or its MRs are not being merged on the stale cohort.

2. Proposed change

Three parts, in order.

A. Sequencing with the Apply fix. Land the Apply absent-source fix in config core first and release it (v0.9.1 or v0.10.0). The sweep below then targets that tag, so one pass both closes the version spread and delivers the bug fix (plus its new conformance case) to every adapter and to GTB.

B. The re-pin sweep.

  1. Bump all 19 adapters' go/config require to the new core tag; run each adapter's full suite (now including the new absent-source conformance case); release each via its Release MR (fix(deps) or chore(deps) + fix where the conformance suite is newly inherited — whichever releaser-pleaser needs to actually cut a tag, see Q2).
  2. Release config-vault v0.1.0 as part of the sweep, or explicitly mark it WIP in its README (review LOW finding; it is the only adapter with CI but no tag).
  3. Bump GTB: go/config → new core tag, go/config-afero (and any other config-family requires) → the freshly-released adapter tags. Verify the newly-enforced behaviours (ErrSensitiveLeak on sensitive writes, read-only refusals) against GTB's config set/wizard flows and adjust error-presentation if any new sentinel needs a user-facing hint.

C. The durable mechanism (so this does not recur).

  1. Renovate audit on the stale cohort: confirm each config-family repo (and GTB) has Renovate actually running nightly with the gitlab.com/phpboyscout/go/ group matched, and that its MRs are landing. Fold findings into the shared preset rather than per-repo fixes where possible.
  2. Cross-repo canary in config core's CI: a job in go/config (MR pipelines, or at minimum pre-release) that checks out each adapter, applies a local replace to config HEAD, and runs go build ./... + go test ./.... This inverts the failure surface: a core change that breaks an adapter fails in config's pipeline before release, not in a consumer's build after. Advisory (allow_failure: true) initially, gating once stable.

Secondary item — forge-family credentials diamond (in scope). The same review found forge core requiring credentials v0.1.0 while forge-bitbucket and GTB require v0.2.0 — MVS silently runs forge core against a credentials version it was never built against. This is the identical failure mode at one-repo scale, and forge core just released v0.2.0 in the forge-aware-setup wave, so the fix is one require bump in go/forge at its next release. Folded in as a checklist item here rather than a separate spec; it does not gate the config sweep.

3. Scope & release plan

Repos that change: go/config (canary CI job; the Apply fix ships via its own spec), all 19 go/config-* adapters (dep bump + release; vault additionally first release), go/forge (credentials require bump, next release), GTB (dep bumps), plus Renovate preset/config repos as the audit dictates.

All releases are human-gated releaser-pleaser Release MRs. Sequencing:

  1. Wave 0: config core — Apply fix released (companion spec). Canary CI job can land in the same or a following MR (CI-only, non-releasing).
  2. Wave 1: all 19 adapters re-pinned to the wave-0 tag, released. These are mutually independent — parallelisable, one Release MR each.
  3. Wave 2: GTB bumps core + adapter pins in one MR; e2e/integration run validates the newly-enforced core behaviours end-to-end.
  4. Independent: forge core credentials bump rides forge's next scheduled release; Renovate audit proceeds in parallel with all waves.

4. Acceptance criteria

  • Every go/config-* adapter's remote-HEAD go.mod requires the same config core version, and that version is the latest core tag.
  • GTB go.mod requires the latest config core tag and the latest config-afero tag; go mod graph shows no config-family module resolved above its own tested pin.
  • GTB exercises ErrSensitiveLeak (sensitive write-path enforcement) in at least one test, proving the v0.6.0 hardening is now active.
  • The Apply absent-source regression test runs green in every adapter that consumes the conformance suite.
  • config-vault either has a v0.1.0 remote tag or a README WIP notice.
  • go/forge HEAD requires credentials v0.2.0 (released at forge's next tag); go mod graph in GTB shows no credentials version skew.
  • The config-core canary job exists, runs on config MR pipelines, and demonstrably fails when a deliberate breaking change is introduced against a stale adapter (verified once by a scratch branch).
  • Renovate audit written up: for each config-family repo + GTB, evidence that a go/* dep bump MR was opened by Renovate within its schedule window after wave 0/1 tags appeared.

5. Open questions

  • Q1 — 19 repos vs one multi-module repo. The review's deeper question: does the config adapter family need 19 independent repos, or would one multi-module repo with synchronised tagging (Go multi-module monorepo, per-adapter tags config-afero/v0.x.y) collapse the propagation problem structurally? This spec deliberately does not decide it — the sweep and canary are worth having either way — but the answer determines whether the canary is a stopgap or the permanent mechanism. Maintainer decision; candidate for its own spec if the answer is "consolidate".
  • Q2 — releasing commit types for pure dep bumps. chore(deps)/ refactor do not cut a releaser-pleaser release. Do the adapter re-pins ship as fix(deps) (a tag per adapter, honest changelog entry), or as chore + rp-next-version::* label per Release MR? Precedent from previous cut-overs suggests fix(deps).
  • Q3 — canary trigger scope. Run the 19-adapter canary on every config MR (slow, ~19 checkouts + test runs) or only on a pre-release schedule / Release-MR pipelines? A tag-filtered subset (fs adapters + one KV + one codec) on MRs with the full matrix pre-release may be the right balance.
  • Q4 — who owns the fleet Renovate preset. The CI-component-pin drift (MEDIUM, 16 repos on cicd v0.22.0) has the same shape; should the Renovate audit here expand to cover component pins in the same pass, or stay config-scoped with the component sweep as a separate ops task?