Repository navigation
Expand file tree
/
Copy pathdefault.json
More file actions
187 lines (187 loc) · 52.3 KB
/
Copy pathdefault.json
File metadata and controls
187 lines (187 loc) · 52.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"description": [
"FS-GG org-shared Renovate preset (FS-GG/.github#22, epic #16 Pillar 4). Custom managers for EVERY embedded cross-repo pin that the standard managers miss — the fs-gg-ui template pin (FsGgUiVersion MSBuild property), the FS.GG.Governance gate-set pin, and any literal annotated with a `renovate:` comment — resolved from the org GitHub Packages NuGet feed. The standard `nuget` manager covers <PackageReference>/<PackageVersion> (FS.GG.Contracts and friends) ONLY IN THE FILES IT MATCHES, which is not the same thing and this sentence used to say it was. Its shipped managerFilePatterns are four, a bare `.proj` is in none of them, and this unqualified clause is what stopped anyone looking for six kit releases — see the `nuget` block below, which extends them (.github#1564). Consume from a product repo with: \"extends\": [\"github>FS-GG/.github\"]. DORMANT until org-admin provisions the GitHub Packages feed + the Renovate app install (FS-GG/.github#21): the feed URL is deterministic from the org login, but private-feed auth comes from that install.",
"gitIgnoredAuthors — THE ORG'S OWN AUTOMATION WAS SILENTLY ABANDONING EVERY RENOVATE BRANCH IT TOUCHED (.github#1533). Two shared reusable workflows push a follow-up commit onto the bot's OWN branch: `kit-materialize.yml` (re-materializes FS.GG.Kit from the bumped pin, ADR-0062/#1262) and `lockfile-sync.yml` (regenerates packages.lock.json, #145). Both push under the fs-gg-cross-repo-dispatch GitHub App, deliberately — an App-token push re-runs `on: pull_request`, a GITHUB_TOKEN push does not, and that re-run is what re-evaluates the freshened tree. Renovate reads a foreign-authored commit on its branch as `somebody else may have edited the PR`, and TWO independent passes then act on it. (1) The branch worker returns `pr-edited`, stops updating the branch, lists it under **PR Edited (Blocked)** on the Dependency Dashboard, and comments `Edited/Blocked Notification` on the PR. (2) The branch-pruning pass refuses to autoclose a branch it thinks was edited, so it appends ` - abandoned` to the PR title and comments `Autoclosing Skipped`. Both artifacts are on FS.GG.SDD#671 today, in Renovate's own words: *\"Renovate will not automatically rebase this PR, because it does not recognize the last commit author... You can manually request rebase by checking the rebase/retry box above.\"* Because `renovate/<dep>-<major>.x` branches are LONG-LIVED, the FIRST bump of a dependency lands and every subsequent one freezes forever, silently — a non-proposal is not an error. The #576 class, one mechanism over.",
"MEASURED, not inferred (.github#1533, 2026-07-27). Every one of the 19 branches then parked under `PR Edited (Blocked)` across FS.GG.SDD, FS.GG.Rendering, FS.GG.Governance, FS.GG.Game and FS.GG.Templates was blocked by this ONE identity and by nothing else — not a single human edit among them. 8 carried only a `chore: materialize FS.GG.Kit` commit, 8 carried only a `chore: sync packages.lock.json` commit, 3 carried both. So lockfile-sync is INDEPENDENTLY sufficient to park a branch, and the blast radius is not the kit: SkiaSharp, Expecto/FsCheck, actions/checkout, actions/upload-artifact, fs.gg.audio.*, fs.gg.game.core and the FS.GG.UI coherent set were all frozen by it. FS.GG.Kit 0.3.0 through 0.7.0 published with zero Renovate PRs; receivers were hand-advanced instead (FS.GG.Governance#315, #331, #332).",
"THE VALUE IS AN EXACT COMMIT-AUTHOR EMAIL, AND NOTHING ELSE WORKS. Renovate's isBranchModified builds a Set of `%ae` AND `%ce` over `origin/<base>..origin/<branch>`, deletes `gitAuthorEmail`, then deletes each `gitIgnoredAuthors` entry by Set.delete — an EXACT string match. Not a login, not a regex, not case-insensitive, not a display name. `fs-gg-cross-repo-dispatch[bot]` (the login) matches NOTHING and would look identical to a working fix until the next release froze. The literal below is the value both workflows set with `git config user.email`, and it is the value GitHub records: the blocking commit FS.GG.Governance@12eac4b has author.email == committer.email == this string. The `297630107` prefix is the id of the `[bot]` USER (`gh api users/fs-gg-cross-repo-dispatch%5Bbot%5D`), NOT the App id — the same trap #514 fixed in the workflows themselves. Measured against renovate 43.281.1's own dist/util/git/index.js, not the docs.",
"IF YOU CHANGE THE PUSH IDENTITY IN EITHER WORKFLOW, CHANGE IT HERE IN THE SAME PR. This entry and the two `git config user.email` lines in .github/workflows/{kit-materialize,lockfile-sync}.yml are one fact spelled in three places, and nothing in CI compares them — a gate for exactly that is .github#1538, which also adds the renovate-config-validator run this org-shared preset has never had. A drift is invisible: the workflows keep pushing, Renovate keeps parking, and the only symptom is a bump PR that never opens.",
"WHAT THIS DOES NOT DO — DO NOT READ A MERGED PRESET CHANGE AS A CLEARED BOARD. The filer of #1533 guessed this entry would also release the branches already parked, and flagged it as worth confirming rather than assuming. It was right to: MEASURED, THAT GUESS IS WRONG WHENEVER THE BOT KEEPS A REPOSITORY CACHE. isBranchModified consults a per-branch memo keyed by the branch SHA (dist/util/git/modified-cache.js) BEFORE it ever looks at authors, a parked branch's SHA never moves because nothing is updating it, and the repo-cache fingerprint is a hash of (repo id, endpoint) — NOT of config — so editing this file does not invalidate it. Driven through renovate 43.281.1's real isBranchModified against a fixture branch of this exact shape: with this entry and no persisted cache -> false (released); with this entry and a pre-fix `isModified: true` stored against the SAME sha -> STILL true (stays parked); with the stored sha stale -> false. `repositoryCache` is globalOnly (bot-admin, renovate's own default `disabled`), so a repo cannot read which way its own bot runs — assume the parked case.",
"SO EACH ALREADY-PARKED BRANCH NEEDS ONE MANUAL TICK of its `rebase-branch=<branch>` checkbox on the Dependency Dashboard. That is the one path that bypasses the `pr-edited` early return (dist/workers/repository/update/branch/index.js: the return is guarded by `!(dependencyDashboardCheck || rebaseRequested)`), it is correct whichever way `repositoryCache` is set, it is what Renovate's own `Edited/Blocked Notification` comment tells you to do, and it is needed only ONCE per branch: the tick moves the SHA, the next isBranchModified recomputes, and this entry answers it from then on. Renovate force-pushing over the materialize/lockfile commits is CORRECT and self-limiting — both workflows re-run on the new head and re-push, and their second pass writes nothing.",
"EXPECT SOME PARKED PRs TO BE AUTOCLOSED RATHER THAN REVIVED, AND DO NOT READ THAT AS DAMAGE. The pruning pass skipped them only because it believed the branch was edited; once this entry is in force, a branch whose update is no longer WANTED becomes eligible for the autoclose it was always due. FS.GG.Governance#303 and FS.GG.Rendering#1027 are both in that state right now — open, titled `... to 0.2.3 - abandoned`, and absent from their dashboards' blocked lists because those two repos were hand-advanced to kit 0.7.0, so there is no pending update left for that branch to carry. Renovate closing them and deleting the branches is the correct cleanup, not lost work: every commit on them is a regenerable materialize/lockfile artifact.",
"SCOPE: a preset only reaches a repo that extends it. FS.GG.SDD, FS.GG.Rendering, FS.GG.Governance, FS.GG.Templates, FS.GG.Game, FS.GG.Audio and FS-GG/.github all do. FS-GG/FS.GG.Net does NOT — it has no Renovate config file at ANY location Renovate reads (checked: renovate.json, renovate.json5, .github/renovate.json, .github/renovate.json5, .renovaterc, .renovaterc.json, .renovaterc.json5, package.json — all 404), its onboarding PR (FS.GG.Net#1, `Configure Renovate`) is still open, and it has no Dependency Dashboard — yet it already adopts BOTH kit-materialize.yml and lockfile-sync.yml callers. It is therefore pre-broken the moment Renovate is onboarded there (FS-GG/FS.GG.Net#33). The 19 branches parked BEFORE this entry existed are unparked and the end-to-end path proven under .github#1539 — merging this file is necessary and not sufficient."
],
"extends": [
"config:recommended",
":dependencyDashboard",
":semanticCommits"
],
"gitIgnoredAuthors": [
"297630107+fs-gg-cross-repo-dispatch[bot]@users.noreply.github.com",
"41898282+github-actions[bot]@users.noreply.github.com"
],
"nuget": {
"description": [
"A BARE `.proj` IS AN MSBUILD PROJECT FILE AND RENOVATE'S nuget MANAGER DOES NOT READ ONE (.github#1564). Its shipped managerFilePatterns are `/\\.(?:cs|fs|vb|sql)proj$/`, `/\\.(?:props|targets)$/`, `/(^|/)dotnet-tools\\.json$/`, `/(^|/)global\\.json$/` — read out of renovate 43.281.1's own dist/modules/manager/nuget/index.js, not the docs. `.proj` is in none of them: `foo.proj` is not `foo.csproj`, and the `(?:cs|fs|vb|sql)` alternation has no empty branch. So every `coordination-kit` receiver's `.config/kit/FS.GG.Kit.receiver.proj` — the FS.GG.Kit receiver project, ADR-0062/#1262 — was invisible to the bot, in all seven of them.",
"IT ONLY HURT IN ONE OF THEM, WHICH IS WHY IT SURVIVED SO LONG. Six receivers put the kit VERSION somewhere the nuget manager already reads: FS.GG.Game, FS.GG.Governance, FS.GG.Rendering and FS.GG.SDD in `Directory.Packages.local.props`, FS.GG.Audio and FS.GG.Net in their own hand-authored `Directory.Packages.props`. Their `.receiver.proj` carries a versionless `<PackageReference Include=\"FS.GG.Kit\" />` under CPM, so nothing is lost by not reading it. FS.GG.Templates has NO CPM anywhere in its tree, so its pin is an inline `Version=` ON that PackageReference — the one place no manager looked. Measured consequence: FS.GG.Templates has never had a single `renovate/fs.gg.kit-*` branch in its history, every kit version it ever adopted arrived on a hand-authored PR (#279, #281, #295, #296, #312), and it sat on 0.8.0 while 0.8.1, 0.9.0 and 0.10.0 shipped. A bump that is never PROPOSED is not an error and appears nowhere — the #1533 sentence, a third mechanism over.",
"WHY EXTENDING THE MANAGER, AND NOT THE OTHER TWO REMEDIES #1564 PUT UP. (b) annotating the pin with a `# renovate:` comment so the generic custom manager below catches it: that is a per-repo edit in a per-repo file, so the NEXT receiver onboarded without CPM is broken again and nothing says so — the shape this preset exists to retire. (c) giving FS.GG.Templates a `Directory.Packages.props`: that changes somebody else's build to suit the bot, and #1564 filed it believing the file would then land inside the build-config disable rule. That belief is now STALE and recorded here rather than left to mislead — since #1552 that rule is a POSITIVE allow-list of the four `build-config` receivers, and FS.GG.Templates is not one, so (c) would in fact work. It is still the wrong remedy: it is a build change, in another repo, for a defect that lives in this file. (a) is one line, in the org-shared preset every receiver already extends, and it fixes the class rather than the instance.",
"BLAST RADIUS, MEASURED RATHER THAN BOUNDED BY ARGUMENT. At the time of writing the only bare `.proj` files across the eight repos that extend this preset were those seven receiver projects — a present-tense fact that nothing re-checks, so it bounds the change that LANDED and is not a guarantee about tomorrow. Driven through renovate 43.281.1's real `extractPackageFile` against each one: FS.GG.Templates yields `{depName: FS.GG.Kit, currentValue: 0.8.0}` — the bump that has never been proposed — and the other six yield `skipReason: invalid-version`, because a versionless PackageReference has no version to bump. The nuget manager exports `extractPackageFile` ONLY (no `extractAllPackageFiles`), so it never cross-references a project file against a `Directory.Packages.props`, and there is no path by which reading these six files can manufacture a duplicate or a guaranteed-red PR. This adds one real PR in one repo and six skips.",
"AND NO LOCKFILE IS TOUCHED, BECAUSE nuget's EXTRACT AND ARTIFACT PREDICATES DISAGREE ABOUT THIS FILE — recorded rather than discovered later. `updateArtifacts` (dist/modules/manager/nuget/artifacts.js) early-returns unless the package file is a `Directory.Packages.props`/`Packages.props`, a `Directory.Build.props`, a `global.json`, or matches `/(?:cs|vb|fs)proj$/i`. A bare `.proj` is none of those, so a bump against the receiver project runs no `dotnet restore` and writes no `packages.lock.json`, even though FS.GG.Templates has a root lockfile and wires `lockfile-sync.yml`. That is the CORRECT outcome here — the receiver project sets `RestorePackagesWithLockFile=false`, is excluded from the normal build, and no lockfile depends on it — but the two predicates are now knowingly out of step, and a future `.proj` that DOES need artifact updates will need more than this line.",
"`managerFilePatterns` IS ADDITIVE, AND THAT IS LOAD-BEARING RATHER THAN CONVENIENT. If it replaced the shipped list, the line below would make the nuget manager read `.proj` and NOTHING ELSE — no `.fsproj`, no `.props`, no `dotnet-tools.json`, no `global.json` — zeroing the NuGet surface of every repo that extends this preset, org-wide, in the direction #1552 already had to repair once. It does not: the option is declared `type: array, mergeable: true`, so `mergeChildConfig` CONCATENATES it onto the manager's shipped default. Verified by running renovate 43.281.1's own `getManagerConfig` over this preset merged into the default config — the resolved list is the four shipped patterns PLUS the one below — and then `getMatchingFiles` over FS.GG.Templates' real 134-file tree, which returns `.config/dotnet-tools.json`, `FS.GG.Templates.csproj` AND `.config/kit/FS.GG.Kit.receiver.proj`, where before it returned only the first two. Do not restate the four shipped patterns here in the belief that they must be repeated; that would survive today and break on the day upstream adds a fifth.",
"`/\\.proj$/` AND NOT `/\\.receiver\\.proj$/`, DELIBERATELY. The narrow spelling is a rule that a rename silently defeats — #943's shape, which is the shape that produced this defect. The broad one states the actual fact (an MSBuild project file the nuget manager should read) and costs nothing measurable, per the blast radius above. Note it does not double-match `.csproj`/`.fsproj`: the regex needs a literal `.` immediately before `proj`, and `getMatchingFiles` de-duplicates through a Set anyway.",
"WHAT NOTICES IF A RECEIVER'S PIN GOES UNMATCHED AGAIN (#1564 criterion 4). Not this file, and deliberately not a second gate asserting that these patterns cover the pin files — that gate would grade the preset against a list somebody maintains, and would have passed on the day this broke. The detector is the SYMPTOM and it is already live: `scripts/repos-audit.sh`'s kit-pin freshness sweep (#1540) reads every `coordination-kit` receiver's FS.GG.Kit pin — it knows all three spellings, including FS.GG.Templates' inline `Version=` on the receiver project — compares it to the newest stable FS.GG.Kit on nuget.org, and reds daily on any receiver that is behind. That is `f(roster, feed)`: no receiver has to push for it to fire, and it does not care WHY the pin froze — an unmatched file, a disable rule, a parked branch and a broken publish all surface as the same red. It would have caught this within a day of 0.8.1.",
"THE RECEIVER PROJECT ASSERTED THE OPPOSITE, AND THIS LANDING IS WHAT MAKES IT TRUE. `.config/kit/FS.GG.Kit.receiver.proj` in FS.GG.Templates says `<!-- Version pinned directly (no CPM). Renovate-managed. -->` and its header promises `a hub change reaches this repo as an ordinary Renovate bump of the pin below, and kit-materialize.yml re-runs the materialize on that PR`. Both were false for the file's whole life, and `kit-materialize.yml` is gated `if: startsWith(head.ref, 'renovate/')`, so its primary trigger had never once been able to fire there. #1564 criterion 3 asks for those comments to be corrected OR MADE TRUE; this makes them true, which is the repair that does not leave a second copy of the fact to drift."
],
"managerFilePatterns": ["/\\.proj$/"]
},
"packageRules": [
{
"description": [
"All FS.GG.* packages resolve from nuget.org, where every one of them is PUBLIC.",
"This rule used to route them to https://nuget.pkg.github.com/FS-GG/index.json on the premise — stated in this repo's renovate.json — that `FS.GG.* are not on nuget.org`. That premise is false, and was false for all 32 of the 32 packages the org publishes: every id on the GitHub Packages feed is also on nuget.org, anonymously readable, at the same latest version (verified id-by-id against api.nuget.org, .github#576).",
"The override was therefore forcing every FS.GG.* lookup onto the one registry that needs a credential — a Mend App Secret (FSGG_PACKAGES_READ_TOKEN) that has never resolved. A 401 on a Renovate datasource is not an error; it is an empty version list. So the bot detected the dependency, enumerated no versions, and opened nothing. The FS.GG.SDD.Cli pin froze at 0.2.1 (#127), at 0.5.0 (#263), at 0.9.0 (#566), and at 0.10.0 — four freezes, each hand-advanced, none fixed.",
"The confirming experiment was already run, by accident. `matchPackageNames` regexes are CASE-SENSITIVE, and the coordination tool is pinned by its lowercase id `fs.gg.coord.cli` in dist/dotnet/.config/dotnet-tools.json. It therefore MISSES this rule, escapes the override, falls back to nuget.org — and is the only FS.GG.* bump PR Renovate has ever opened in this repo (#660). The packages that match the rule freeze; the one that escapes it bumps. That is the whole defect.",
"Renovate needs no credential to read nuget.org, so no hostRules token is required anywhere. CI's own restore is unaffected: it authenticates to GitHub Packages with GITHUB_TOKEN (packages: read), a different credential on a different path, and keeps working."
],
"matchDatasources": ["nuget"],
"matchPackageNames": ["/^FS\\.GG\\./i"],
"registryUrls": ["https://api.nuget.org/v3/index.json"]
},
{
"description": "The fs-gg-ui coherent set moves as one — group FS.GG.UI.* (members + BOM + template) into a single PR.",
"matchDatasources": ["nuget"],
"matchPackageNames": ["/^FS\\.GG\\.UI($|\\.)/i"],
"groupName": "FS.GG.UI coherent set"
},
{
"description": "The Expecto/FsCheck test stack is version-locked: Expecto.FsCheck N peer-depends on both Expecto N and a specific FsCheck major (e.g. Expecto.FsCheck 11 → FsCheck 3, Expecto.FsCheck 10 → FsCheck 2). Split across separate PRs, any single major bump violates the peer constraint (NU1608) and can never go green alone. Group them so the coherent set moves as one PR.",
"matchDatasources": ["nuget"],
"matchPackageNames": ["/^Expecto($|\\.)/", "/^FsCheck($|\\.)/"],
"groupName": "Expecto/FsCheck test stack"
},
{
"description": [
"Cap YoloDev.Expecto.TestSdk below 1.0.0: its LATEST version is a DOWNGRADE. The published order and the Expecto range each version's nuspec declares (fetched from api.nuget.org, not relayed): 0.15.6 -> Expecto [10.2.2, 11.0.0); 0.16.0 -> Expecto 10.2.3 with NO upper bound; 1.0.0 -> Expecto [9.0.0, 10.0.0). So 1.0.0 is a higher number on an OLDER lineage, and the only version that admits Expecto 11 is 0.16.0, in the MIDDLE. Renovate reads versions, not lineages, so absent this rule it proposes 1.0.0 to every repo with an Expecto suite.",
"Taking 1.0.0 forces Expecto back to 9.x, and NU1608 (Warning-As-Error, org house style) rejects the resulting graph — so the PR can never go green and churns every cycle. This is live, not hypothetical: FS.GG.Audio, FS.GG.Governance and FS.GG.Game all pin Expecto 11.1.0 alongside adapter 0.16.0 today, and FS.GG.Rendering pins Expecto 10.2.3 with the same adapter. Measured end-to-end through renovate 43.265.4's own filterVersions against the real published list: uncapped it proposes 1.0.0, capped it proposes nothing (.github#850).",
"`<1.0.0` is NOT nuget range syntax, and that is deliberate rather than a mistake to be tidied up. Renovate's allowedVersions filter tries three branches in order: a /regex/, then versioningApi.isValid, then npm semver. nuget's isValid REJECTS `<1.0.0` (the native spelling would be `(,1.0.0)`), so this cap lands on the third branch and Renovate logs `Falling back to npm semver syntax for allowedVersions`. It filters correctly there — both spellings were driven end-to-end and both cap 1.0.0 — and `<` is the spelling FS.GG.Rendering's own Expecto cap already uses. Do not 'fix' this to `(,1.0.0)` on the theory that the nuget scheme must parse it; check which branch you are on before believing either spelling is broken.",
"A PLAIN STRING here, never a /regex/, and NOT because the #660 casing trap does not apply — because it cannot. matchPackageNames routes the two spellings to different code: a /regex/ goes to parseRegexMatch, which is CASE-SENSITIVE unless the pattern ends in `i`, whereas a plain string goes to minimatch with `nocase: true`, where dots are literal anyway. So this literal matches a lowercase or otherwise re-cased pin already, and an `/i` regex would buy nothing. (The rule that the lowercase id `fs.gg.coord.cli` escaped was case-sensitive at the time; the FS.GG.* rule above carries `/i` since #576, so it no longer escapes — the trap was the missing flag on a REGEX, which is a spelling this rule does not use.) Measured against renovate 43.265.4's string-match.js, not inferred.",
"ACTION WHEN a version above 1.0.0 ships: read that version's nuspec Expecto range, and delete this cap only if it admits Expecto >= 11. Do NOT reason from the version number — the number is precisely what lies here. This cap is the same shape as the defect that filed it: FS.GG.Rendering's `<11.0.0` Expecto cap was correct when written about adapter 0.15.6, its stated ACTION condition came true when 0.16.0 shipped, and nothing re-checked it, so it went on capping Expecto at 10.x long after the repo had moved to an adapter that does not constrain it (.github#850, FS.GG.Rendering#845). A rule nothing re-checks is a rule that outlives its reason.",
"fsgg-cap-expires-when: dependency=Expecto admits=11.0.0",
"That line is the ACTION paragraph above, in the form something EXECUTES (.github#943). scripts/check-pin-coherence.py reads it out of this description, and every day it asks api.nuget.org — the registry Renovate itself resolves from — whether any version this cap EXCLUDES declares an Expecto range admitting 11.0.0. Today none does: 1.0.0 is the only excluded stable version and its nuspec says Expecto [9.0.0, 10.0.0). The day YoloDev ships an adapter above 1.0.0 whose nuspec admits Expecto 11, the gate goes red, names this rule and that version's range, and says delete the cap. It cannot pass vacuously: an unreadable nuspec, an unparsable range, a cap that excludes nothing, and disagreeing per-framework ranges are each an ERROR rather than a green (epic #266 — 'could not look' is never 'looked, and fine'). The annotation lives in `description` because that is the only place it CAN live: Renovate's own validator rejects an invented sibling field on a packageRule (measured against renovate-config-validator 43.265.4), and this is the shared preset every repo extends, so a config error here breaks the org. Adding a cap without one of these lines is now RED — write `fsgg-cap-expires-when: manual — <why>` if the trigger is not a fact about a nuspec."
],
"matchDatasources": ["nuget"],
"matchPackageNames": ["YoloDev.Expecto.TestSdk"],
"allowedVersions": "<1.0.0"
},
{
"description": [
"Cap FS.GG.SDD.Cli at the newest qualified validator identity: 2.1.0. The accepted 1.0.0, 1.5.0 and 2.1.0 identities in `src/FS.GG.Coord.Cli/CycleLedgerApplication.fs`, BOTH exact 2.1.0 workflow install pins and this <=2.1.0 ceiling are one coordinated authority boundary.",
"The genuinely published 2.1.0 producer at source 518517f6b90330a6e99f90bbce68faa0a891287f passed fresh public archive/source/official-Core joins and the compiled production engine's 190 write assertions, including the valid-provider case and all six original authority refusals. Actual verify --require-observed --dry-run reported verificationReady with zero blocking/classified/journey unmet counts. Its one VF001 warning is admitted by the unchanged existing succeededWithWarnings guard, not a new acceptance policy. Coherent dual-feed publication and literal payload readback completed in native release run https://github.com/FS-GG/FS.GG.SDD/actions/runs/37114843541 (attempt 2); feed signatures change archive envelopes only.",
"The 1.5.0 adoption was exercised against the real `fsgg-sdd verify --dry-run` output and all six discriminating #2133 provider-authority assertions, rather than re-baselining a version string to green. The accepted identity, both workflow pins, and this Renovate ceiling are one coupled fact; moving any one without the other three recreates .github#2464's split-brain validator boundary.",
"ACTION WHEN FS.GG.SDD.Cli later than 2.1.0 should be adopted: re-vet the target release's `verify` output against the #2133 provider-authority contract, update the accepted validator identity in `src/FS.GG.Coord.Cli/CycleLedgerApplication.fs`, advance BOTH workflow install pins (`coord-engine.yml`, `contract-coherence.yml`) to match, confirm all six #2133 assertions still reject their original attack shapes, and raise this cap in the same change.",
"fsgg-cap-expires-when: manual — the trigger is a same-repo source-code coupling (CycleLedgerApplication.fs:116's hardcoded validator-identity literal), not a fact any nuspec range can express; lifting the cap requires .github#2465 (making the accepted fsgg-sdd validator version a coordinated, checkable fact rather than a bare source literal) to land, then the re-vet and coordinated pin update named above in the same change"
],
"matchDatasources": ["nuget"],
"matchPackageNames": ["FS.GG.SDD.Cli"],
"allowedVersions": "<=2.1.0"
},
{
"description": [
"Directory.Build.props and Directory.Packages.props are MATERIALIZED into a build-config receiver from its pinned FS.GG.Kit, not authored there (ADR-0006 sync-not-fork, ADR-0062). A receiver's copy must equal what the package materializes, and its own `build-config-drift` job — a REQUIRED check in adopting repos — fails any PR that changes one. So a Renovate bump against a receiver's copy is a PR that structurally CANNOT merge, in a receiver, ever. FS.GG.Game#278 and FS.GG.Game#322, FS.GG.SDD#441 and FS.GG.Governance#194 are the confirmed instances, all closed unmerged (.github#678, .github#794).",
"THIS PARAGRAPH USED TO SAY \"all THREE files scripts/sync-build-config.sh copies out of dist/dotnet/\", NAMING .config/dotnet-tools.json AS THE THIRD. That was false, and had been since #1077 — sync-build-config.sh's FILES list is the two .props and nothing else, and the engine tool manifest moved to the coordination kit (registry/repos.yml `kit:`, kind: config), which reaches SEVEN receivers where build-config reaches four. The false sentence is why the two file classes sat fused in ONE rule under ONE repo set for as long as they did: fixing the repo set for the .props would have silently re-enabled a kit-managed file in three more repos, or leaving it fused would have gone on zeroing two repos' authored pins. They are two different fabrics with two different receiver sets, so they are now two rules (#1552). Sibling rule below.",
"CLOSING such a PR is the TRIGGER for the next one, not a workaround — so the rule must suppress CREATION. Measured (.github#794): FS.GG.SDD#441 was closed at 12:22:43Z and Renovate re-filed the identical bump as FS.GG.SDD#468 at 12:25:17Z, 2m34s later, same branch, same guaranteed red. Absent this rule a maintainer's only options are a permanently-red PR in every receiver, or hand-editing the managed file — the sync-not-fork violation the drift gate exists to prevent.",
"This disables the RECEIVER copy only. `.github` keeps managing dist/dotnet/ — that is how the baseline is MEANT to be bumped. dist/dotnet/.config/dotnet-tools.json is the one FS.GG.* bump PR Renovate has ever opened in this repo (#660), and the mechanism that would have caught the 0.1.0 → 0.1.1 staleness in #677 instead of a human finding it; #753 (`update dependency fsharp.core to 10.1.302`) is Renovate bumping dist/dotnet/Directory.Packages.props here, which is how every receiver got 10.1.302 through the sync.",
"REPO SCOPING IS AN ALLOW-LIST OF THE REPOS THAT ACTUALLY RECEIVE THESE FILES, and until #1552 it was the NEGATION `matchRepositories: [\"!FS-GG/.github\"]` — every repo except the one that authors them. That predicate was correct when written and became false silently. `.github` does NOT adopt the org baseline — its root Directory.Packages.props is hand-authored, because the baseline's FSharp.Core pin would collide (NU1506) — so naming these files UNCONDITIONALLY would freeze this repo's own engine pins (FSharp.Core, Spectre.Console, xunit, FsCheck.Xunit — #739 and #753 are Renovate bumping the root file), the #576 failure exactly. Excluding the authority got that right. What it got WRONG is everybody else: `!FS-GG/.github` says \"every repo that is not the author\", but the true predicate is \"every repo that RECEIVES this file from a fabric\", and those two sets stopped being equal the moment templates, audio and net were onboarded WITHOUT build-config. They hand-author their own build config, so the rule disabled a file they own — zeroing FS.GG.Net's and FS.GG.Audio's ENTIRE NuGet surface, feature and SECURITY updates alike, with no error anywhere (#1552). A non-proposal is not an error; that is the #1533 sentence and this is another instance of it.",
"SO THE ALLOW-LIST IS POSITIVE, AND IT IS DERIVED — see the fsgg-repo-scope line below. The authority falls out of it by construction (`.github` receives neither capability; it is the SOURCE of both), so the negation is not merely replaced, it is subsumed: there is no longer a spelling of this rule in which the authority's own hand-authored files could be caught. An UNROSTERED repo is now MANAGED rather than blocked, and that direction is deliberate — the old default silently zeroed the NuGet surface of every repo that extended this preset without receiving anything, which is the defect. A repo that genuinely receives build config is rostered (scripts/check-roster-closure.py reds an org repo missing from registry/repos.yml), and rostering it is exactly what the coherence gate turns into a required edit here.",
"#925 recorded this half as INEXPRESSIBLE in the shared preset and routed it to a re-enable in this repo's own renovate.json. It is expressible — that finding stands, only its spelling changed. It was first spelled as a negation (matchRegexOrGlobList reads a leading `!` through minimatch, which negates); #1552 replaced that with the positive derived allow-list above, for the reason recorded there. Either way it is ONE place, with no ordering dependency and no second file that must be kept in step. A re-enable in renovate.json would also work (packageRules merge in order, last wins), but it is the very shape this preset refuses, and it makes default.json assert something false about the repo reading it.",
"matchFileNames, NOT ignorePaths — and that choice is the whole substance of this rule, because #678 proposed `ignorePaths: [\".config/dotnet-tools.json\"]` and that fix defeats its own stated intent. Renovate's filterIgnoredFiles ignores a file when `file.includes(ignorePath) || minimatch(ignorePath, {dot:true}).match(file)`. The first branch is a SUBSTRING test, so that literal pattern also swallows dist/dotnet/.config/dotnet-tools.json and un-manages the source of truth #678 set out to protect. It is not the only spelling that does: \".config\", \"dist/\", \".json\" and even \"/\" all freeze the baseline, because the shorter the entry the more of the path it occurs inside. matchFileNames goes through matchRegexOrGlobList, which anchors: it matches the receiver root copy and leaves dist/dotnet/ managed. Measured against renovate 43.265.2's own dist/workers/repository/extract/file-match.js and dist/util/package-rules/files.js, then driven end-to-end through its real matcher — not inferred from the docs.",
"Two things NOT to conclude from the above, both measured rather than reasoned. (a) `ignorePaths` is not incapable of anchoring — its minimatch branch does anchor, so `[.]config/dotnet-tools.json` and `*.config/dotnet-tools.json` really do ignore the receiver copy while leaving dist/dotnet/ managed. They work by ESCAPING the substring branch on a glob metacharacter, which is far too fine a coincidence to rest the baseline on, so the gate refuses them — but the honest reason is fragility, not impossibility. (b) The one genuinely dead spelling is the regex: `/^\\.config\\/dotnet-tools\\.json$/` matches NOTHING, because ignorePaths is not a regex surface — it fails silently and looks exactly like a fix. Note also that `**/.config/dotnet-tools.json` in matchFileNames would re-introduce the original bug: the leading `**/` is what makes it reach dist/dotnet/ again.",
"Scope the exclusion to the FILES, never to the packages and never to the whole repo. `ignoreDeps: [\"FSharp.Core\"]` would freeze the baseline HERE too. A blanket path ignore would freeze a receiver's OWN pins: FS.GG.SDD keeps 7 in the unmanaged Directory.Packages.local.props and Renovate bumps them correctly (FS.GG.SDD#425). And global.json is DELIBERATELY not in sync-build-config.sh's FILES list, so dotnet-sdk is legitimately Renovate's — FS.GG.Governance#194 is the grouped `dotnet monorepo` PR where a real, wanted dotnet-sdk bump sat parked behind the un-mergeable FSharp.Core half. A file-scoped exclusion drops FSharp.Core out of that group and lets the SDK bump re-open cleanly on its own.",
"SECURITY UPDATES ARE SUPPRESSED HERE TOO, and that is CORRECT for a materialized file rather than an oversight — recorded because #1552 asked it as an open question and the honest answer looks alarming. Measured against renovate 43.281.1 by appending the exact rule dist/workers/repository/process/vulnerabilities.js builds (isVulnerabilityAlert: true plus force: {...vulnerabilityAlerts}) to the resolved config and re-running applyPackageRules: a SECURITY update on a receiver's Directory.Packages.props comes back enabled=false, skipReason=package-rules, commitMessageSuffix=[SECURITY]. Renovate's SHIPPED default vulnerabilityAlerts object carries no `enabled` key, so `force` has nothing to override the file-level disable with. Do NOT 'fix' this with a blanket \"vulnerabilityAlerts\": {\"enabled\": true} — that would re-open, for security bumps, precisely the guaranteed-red PR this rule exists to suppress. A CVE on a materialized pin is fixed at the SOURCE (dist/dotnet/ here, where this rule does not apply) and reaches receivers through the kit. What was genuinely broken is that the same suppression also covered files the receiver AUTHORS; that is what the repo scoping above fixes, and FS.GG.Net's System.Security.Cryptography.Xml override (NU1903) is maintainable by the bot again because its Directory.Packages.props is its own.",
"Driven end-to-end through renovate's real matcher (dist/util/string-match.js, dist/util/package-rules/{files,repositories}.js) rather than inferred from the docs; re-driven at 43.281.1 for #1552 and again at 43.281.1 for #1798. In a BUILD-CONFIG receiver, BLOCKED = the two root .props; MANAGED = dist/dotnet/*, .config/dotnet-tools.json, Directory.Packages.local.props, global.json, and ordinary project files. In FS-GG/.github this rule does not apply at all. In FS.GG.Audio / FS.GG.Net / FS.GG.Templates the two .props are MANAGED — they are hand-authored there. That is no longer asserted only in this sentence: tests/preset-repo-scope-coherence/drive-package-rules.mjs drives every one of those claims through applyPackageRules on each CI run, so a sentence here that stops being true reds instead of ageing.",
"THERE WAS A SIBLING RULE HERE AND IT IS GONE (#1798, 2026-07-28). IT WAS RIGHT WHEN WRITTEN — DO NOT READ ITS DELETION AS A REPAIR OF A MISTAKE. From #1077 until today a second, separate rule disabled `.config/dotnet-tools.json` across the SEVEN coordination-kit receivers, and its reasoning was sound for every day it existed: the engine tool manifest was a `kind: config` row of the coordination kit, so each receiver held a materialized copy that `scripts/coordination-sync --check` compared byte-for-byte against `dist/dotnet/.config/dotnet-tools.json` here. A Renovate bump of `fake-cli` or `fs.gg.coord.cli` in a receiver was therefore a guaranteed-red PR that Renovate re-filed on close — the #794 churn class exactly, and #1552's own analysis calls scoping it away 'the trap this rule exists to not fall into'.",
"WHAT REMOVED THE RULE WAS THE REMOVAL OF ITS PREMISE, NOT A CHANGE OF MIND. #1615 / ADR-0068 took `coord-engine-manifest` off the `kit:` block: the manifest is no longer materialized into anybody's tree, each receiver OWNS its copy outright, and `coordination-sync --check` no longer compares it. Nothing is red for a bump to be a guaranteed red against. In the same stroke ADR-0068 made Renovate the ONLY delivery path the engine pin has — `/(^|/)dotnet-tools\\.json$/` is one of the nuget manager's four shipped managerFilePatterns — so the rule stopped suppressing churn and started being the thing that stops the pin ever moving. Measured before the change at renovate 43.281.1: `applyPackageRules` returned `enabled=false, skipReason=package-rules` for BOTH tools in every receiver, and every receiver's Renovate Dependency Dashboard listed `.config/dotnet-tools.json` under `nuget` with ZERO dependencies while the file demonstrably declared two — the dashboard filters deps carrying a skipReason (dist/workers/repository/package-files.js). A rule nothing re-checks is a rule that outlives its reason; this one outlived its reason by six hours and nothing would have said so, because a non-proposal is not an error (#1533).",
"SO THE CONDITION FOR RE-ADDING IT IS NAMED, RATHER THAN LEFT TO JUDGEMENT: the manifest becomes an authority-synced file again — a `kit:` `kind: config` row, or a sync-build-config.sh FILES entry. `scripts/check-pin-coherence.py` DERIVES that set from those two owners and requires a `matchFileNames` + `enabled: false` rule for every member, so re-adding the row to either owner turns this rule back into a required edit and reds until it is made. Do not re-add it on the strength of this paragraph alone; the gate is the check, and it says which files and why.",
"AND THE HALF THAT DID NOT LEAVE WITH IT: `dist/dotnet/.config/dotnet-tools.json` is STILL this repo's canonical manifest, still engine-pin-coherence's subject, and still #660 — the one FS.GG.* bump PR Renovate has ever opened here. It must stay MANAGED, and the ignorePaths substring hazard three paragraphs up reaches it whether or not any rule names the receiver copy. That protection is therefore keyed on the dist/dotnet/ BASELINE rather than on the synced set (check-pin-coherence.py's BASELINE_MANAGED_PATHS), so it survived this deletion instead of quietly going with it.",
"fsgg-repo-scope: receives=build-config"
],
"matchFileNames": [
"Directory.Build.props",
"Directory.Packages.props"
],
"matchRepositories": [
"FS-GG/FS.GG.Game",
"FS-GG/FS.GG.Governance",
"FS-GG/FS.GG.Rendering",
"FS-GG/FS.GG.SDD"
],
"enabled": false
},
{
"description": [
"Prefer stable releases for FS.GG.*; do not propose prereleases over them.",
"This description used to assert that 'FS-GG packages now all ship on a STABLE channel (FS.GG.Audio 0.1.0 was the last -preview producer)'. That is nearly true again: FS.GG.NewSddWorkspace was PROMOTED to stable at 0.3.0 (the full-platform release, 2026-07-16), so Renovate can now propose a bump for it. The one remaining prerelease-only producer is FS.GG.NewSddFullstack (0.1.1-preview.1) — `ignoreUnstable` means Renovate can never propose a bump for it. Recorded rather than asserted away, because a false sentence in this file is what froze the SDD.Cli pin four times (.github#576)."
],
"matchDatasources": ["nuget"],
"matchPackageNames": ["/^FS\\.GG\\./i"],
"ignoreUnstable": true,
"respectLatest": true
},
{
"description": [
"AUTOMERGE THE MECHANICAL FS.GG.Kit BUMPS, AND NOTHING ELSE (.github#1587). 12 kit bump PRs opened org-wide and zero ever merged (#1565), and every hand-repair that followed was a human doing by hand what an unmerged PR was already offering. This rule is the last link: a kit bump whose diff is the pin plus materializer output merges itself, and every other class keeps waiting for a person.",
"WHAT DECIDES 'MECHANICAL' IS A CHECK RUN, NOT THIS RULE. `materialize / kit-bump-mechanical` (#1815, 615bba1) is a job in the reusable kit-materialize.yml that all seven receivers already call, and it is green IFF the version-pinned shape rule exits 0. Exit `0` mechanical and exit `2` abstains-because-this-is-not-a-kit-PR are GREEN; `1` not-mechanical, `3` refused and `4` mechanical+repair are RED. Its sibling `materialize / kit-bump-shape` stays green on 4 ON PURPOSE (#1713), so that context is NOT the one automerge may rest on — do not swap them. Observed discriminating on real FS.GG.Net pull requests, by check-run id: class 2 (`#55`, `b9face7`) run 90344760135 success; class 0 (`#56`, `92b12d4`) run 90344840444 success; class 4 (`#56`, `ff1fd76`) run 90345145774 FAILURE.",
"NOTHING IS ARMED, AND THAT IS THE DESIGN RATHER THAN A SHORTCUT. Both contexts are REQUIRED IN ZERO REPOSITORIES, and `scripts/repos.sh require-context` was not invoked in any mode by the change that added this rule. A red kit-bump-mechanical stops the automerge anyway, because Renovate's own pre-merge poll reads the branch's COMBINED status: dist/modules/platform/github/index.js `getBranchStatus` pages `repos/<repo>/commits/<branch>/check-runs?per_page=100` — EVERY check run on the head commit, filtered by nothing, with branch protection never consulted on that path — then returns 'red' if `checkRuns.some(run => run.conclusion === 'failure')`, and 'green' only if every conclusion is skipped/neutral/success. dist/workers/repository/update/pr/automerge.js then refuses at `if (branchStatus !== 'green')` with prAutomergeBlockReason 'BranchNotGreen', BEFORE it ever reaches mergePr. Read out of renovate 43.281.1 — the version this repo's own two gates pin and install — not out of the docs. So the mechanical+repair class keeps waiting for a person AND a person can still merge it with an ordinary review, which is what #1726 asked for and what FS.GG.Rendering#1088 proves is sometimes correct. REQUIRING the context would buy branch protection as a second, independent enforcer, at exactly one price: class 4 would then need an ADMIN rather than a reviewer, because a required red context blocks the human too.",
"`platformAutomerge: false` IS THE LOAD-BEARING LINE IN THIS RULE AND DELETING IT SILENTLY MERGES THE CLASS ABOVE. The combined-status poll only governs the merge WHEN RENOVATE DOES THE MERGING. Renovate's default is `platformAutomerge: true`, which instead calls GitHub's `enablePullRequestAutoMerge` GraphQL mutation at PR creation (dist/modules/platform/github/index.js `tryPrAutomerge`, re-armed after every rebase from dist/workers/repository/update/branch/index.js) and hands the merge decision to GITHUB — whose native auto-merge fires on the REQUIRED subset only. Renovate's own documentation carries the warning: 'If you don't select any status check, and you use platform automerge, then GitHub might automerge PRs with failing tests!'. Because kit-bump-mechanical is required NOWHERE, native auto-merge would not see it at all and would merge the mechanical+repair class — the exact class this rule exists to withhold. This is not hypothetical: measured 2026-07-28, `allow_auto_merge` (the `autoMergeAllowed` field tryPrAutomerge early-returns on) is TRUE in six of the seven receivers — SDD, Rendering, Governance, Templates, Game, Audio — and false only in FS.GG.Net, so nothing else would have stopped it. Turning platform automerge off is what makes 'arm nothing' actually enforce the decision instead of merely appearing to.",
"AC5 — AUTOMERGE DOES NOT BYPASS BRANCH PROTECTION. With platform automerge off, Renovate merges through `PUT repos/<repo>/pulls/<n>/merge` (dist/modules/platform/github/index.js `mergePr`), the same REST endpoint a human uses, so every required context, required review and ruleset still applies exactly as it would to a person. On a 405 whose body matches `Required status check \"...\" is expected.` mergePr returns false and the PR STAYS OPEN — it waits, it does not merge. That is the #1575 / FS.GG.Rendering#1027 case, where a Renovate branch predating a newly-armed required context can never satisfy it, landing as a hold rather than as a bypass.",
"AC6 — WHAT HAPPENS WHEN AN AUTOMERGE CANNOT COMPLETE, because a non-event is exactly how #1533 hid for weeks. Three signals, in ascending order of not needing anyone to be looking. (1) ON THE PR: the red `materialize / kit-bump-mechanical` check run, named and linked, which is also the thing that withheld the merge — so the signal and the cause are the same object. (2) ON THE RECEIVER'S DEPENDENCY DASHBOARD: the branch stays listed with its rebase checkbox under `Open` or `Pending Status Checks`, and Renovate records the refusal as a prAutomergeBlockReason in its run log. (3) THE ONE THAT FIRES WITH NOBODY WATCHING, and the only one that is f(roster, feed) rather than f(someone noticing): scripts/repos-audit.sh's kit-pin freshness sweep (#1540) reds DAILY on any receiver whose pin is behind the newest stable FS.GG.Kit on nuget.org, and #1768's bump-offer sweep grades that receiver `offer-current` / `offer-superseded` / `offer-ratelimited` / `offer-none`. A stuck automerge leaves its receiver behind, so it surfaces there within a day WHATEVER the reason — an unmatched file, a parked branch, a red guard, and an automerge that never fired all red the same way. A silently-open PR is therefore not a silent state.",
"AC3 — THIS MATCHES FS.GG.Kit AND NOTHING ELSE. `matchPackageNames` is a PLAIN STRING here, never a /regex/, so it routes to minimatch with `nocase: true` where the dots are literal (dist/util/string-match.js): it matches a re-cased pin already, and it does NOT match `FS.GG.Kit.Anything` or any other FS.GG.* package. Do not widen this to a regex or a prefix. Blanket-automerging dependencies is explicitly out of scope, and the guard that makes automerge safe here is derived from the KIT's own materialize contract — it means nothing for any other package, so a wider match would automerge things nothing is checking.",
"NOT DEMONSTRATED ON A LIVE BUMP, AND SAYING SO RATHER THAN LETTING THE SILENCE IMPLY OTHERWISE (#266). When this landed, repos-audit graded 7 of 7 receivers CURRENT at 0.18.0 — the newest stable on nuget.org — so there was no bump in flight for this rule to be watched acting on, and a release was deliberately NOT manufactured to create one. The two contexts' discrimination IS measured (the check-run ids above); this rule's end-to-end effect on a real fan-out is NOT. The first genuine kit release is what will show it, and it is worth watching rather than assuming.",
"ONE OPEN RISK, RECORDED RATHER THAN DISCOVERED LATER (#1786). The shape RULE is pinned to `kit/v<version>` (#1772), but the CONCLUSION MAPPING that turns its exit code into these two check runs lives in kit-materialize.yml, which receivers call `@main`. #1783 accepted that moving ref deliberately and the acceptance still holds, but automerge makes it binding rather than academic: a bad mapping on main now merges or withholds across seven repositories. #1799 is what that looks like when it goes wrong — a guard that matched everything and refused every bump PR in every receiver for two and a half hours — and GitHub pins reusable-workflow resolution for a run's lifetime, so a re-run re-resolves the SAME bad commit and only a fresh `pull_request` event clears it. The direction of that failure is safe for this rule (a broken mapping reds, and red does not automerge), but the reverse — a mapping that wrongly greens class 4 — would merge what this rule withholds. #1786 is open and unmeasured."
,"fsgg-platform-automerge: false — Renovate must read the combined status; GitHub-native auto-merge cannot see the unrequired mechanical guard."
],
"matchDatasources": ["nuget"],
"matchPackageNames": ["FS.GG.Kit"],
"automerge": true,
"automergeType": "pr",
"platformAutomerge": false
}
],
"customManagers": [
{
"customType": "regex",
"description": [
"fs-gg-ui template pin: the FsGgUiVersion MSBuild property → FS.GG.UI.Template (the published coherent-set version).",
"versioningTemplate is `loose`, NOT `nuget`, and the token is load-bearing (.github#1131, the #576 class). Under `nuget` versioning a bare literal is a RANGE — `0.10.0` parses as `>=0.10.0`, which the newest release always satisfies — so Renovate resolves currentVersion to newest, finds the constraint already met, and proposes NOTHING. That is the exact freeze that held the FS.GG.SDD.Cli pin for four rounds (#576, fixed by #1119's `versioning=loose`). This manager has no per-pin escape — the `<FsGgUiVersion>` property carries no `# renovate:` annotation — so the scheme MUST be single-version here, on the manager itself. Measured against renovate@latest --platform=local: `<FsGgUiVersion>0.10.0</FsGgUiVersion>` under `nuget` → 0 updates while FS.GG.UI.Template 0.12.0 is live; under `loose` → newVersion 0.12.0. `loose` also tolerates the 4-segment / preview grammar strict semver rejects. Do NOT drop this back to `nuget` without re-reading #576/#1131."
],
"managerFilePatterns": [
"/\\.props$/",
"/\\.targets$/",
"/\\.template\\.config/.*\\.json$/"
],
"matchStrings": [
"<FsGgUiVersion>(?<currentValue>[^<]+)</FsGgUiVersion>",
"\"FsGgUiVersion\"\\s*:\\s*\"(?<currentValue>[^\"]+)\""
],
"depNameTemplate": "FS.GG.UI.Template",
"datasourceTemplate": "nuget",
"versioningTemplate": "loose"
},
{
"customType": "regex",
"description": "Generic annotation-driven manager: any embedded pin tagged `# renovate: datasource=<ds> depName=<pkg> [versioning=<v>]` on the line(s) above the version literal. Used for the FS.GG.Governance gate-set pin and any other non-standard embedded literal across repos. The `versioning` DEFAULT is `loose`, NOT `nuget` (.github#1135, the #576 class — same fix as the FsGgUiVersion manager above, #1131). Under `nuget` versioning a bare literal is a RANGE — `0.13.0` parses as `>=0.13.0`, which the newest release always satisfies — so Renovate finds the constraint met and proposes NOTHING, and the pin freezes silently. That froze the FS.GG.SDD.Cli pin four times (#127/#263/#566/#885) until #1119 added an explicit `versioning=loose`, whose own comment called that token *load-bearing* against this default. Making the default `loose` retires the trap: a bare-literal pin can no longer freeze by OMISSION, and `versioning=loose` on an annotation becomes belt-and-suspenders rather than the only thing standing between a pin and a silent freeze. An author who genuinely needs range semantics writes `versioning=nuget` explicitly. `loose` also tolerates the 4-segment / preview grammar strict semver rejects (the live `governance-reference-gate-set` pin is `1.2.1.1`). Do NOT drop this back to `nuget` without re-reading #576/#1131/#1135.",
"managerFilePatterns": [
"/\\.ya?ml$/",
"/\\.props$/",
"/\\.targets$/",
"/\\.json$/",
"/\\.fsproj$/",
"/\\.sh$/"
],
"matchStrings": [
"renovate:\\s*datasource=(?<datasource>\\S+)\\s+depName=(?<depName>\\S+)(?:\\s+versioning=(?<versioning>\\S+))?[^\\n]*\\n(?:[^\\n]*\\n){0,1}?[^\\n]*?[\\s=:\"'>/]v?(?<currentValue>\\d+(?:\\.\\d+)*[\\w.+-]*)"
],
"versioningTemplate": "{{#if versioning}}{{{versioning}}}{{else}}loose{{/if}}"
}
]
}