Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions tools/renovate/config.json5
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,32 @@
matchDepNames: ["go"],
enabled: false,
},
{
// ── wails/v3: hold below v3.1 (GTK4-migration floor, RIG-2818/RIG-2819) ──
// The GTK4 migration record freezes a hard "Never v3.1" floor
// (docs/designs/product/compass-gtk4-migration/design.md §Global Constraints
// "Wails floor"): wails v3.1 drops the legacy GTK3 build tag, so bumping to it
// before the GTK4 flip (RIG-2819) is proven would strand the app with no
// working native shell. go/go.mod pins a v3.0.0 prerelease today; Renovate would
// auto-open a v3.1.x bump the moment upstream ships it, violating the floor
// silently. Cap it here until the flip lands and every wails bump is gated by
// the gtk4-e2e lane.
//
// REGEX, not a semver range: gomod uses `semver` versioning, whose node-semver
// ranges admit a prerelease ONLY when a comparator shares its exact
// major.minor.patch tuple — so `< 3.1.0-0` would REJECT the current v3.0.0
// prerelease pin (its only comparator sits at tuple 3.1.0), opening zero PRs
// and freezing the pin. A slash-delimited regex is matched against the raw
// version string instead, sidestepping prerelease-range semantics entirely
// (mirrors the postgres-stack `/^18$/` rule above). `^v?3\.0\.` admits
// v3.0.0-beta.N and any future v3.0.x, rejects v3.1.x and v4+. config.test.ts
// replays it against the live go.mod pin plus the v3.0.x / v3.1.x / v4.0.0
// boundaries. Remove this rule + its
// guard once RIG-2819's GTK4 flip is merged.
matchManagers: ["gomod"],
matchDepNames: ["github.com/wailsapp/wails/v3"],
allowedVersions: "/^v?3\\.0\\./",
},
{
// ── bunfig soak exemption for the bun types packages ──
// bunfig.toml `minimumReleaseAgeExcludes` exempts `@types/bun` and `bun-types`
Expand Down Expand Up @@ -645,6 +671,11 @@
"tools/toolchain/versions/node.nix",
"tools/toolchain/versions/moon.nix",
"guest-image/default.nix",
// flake.nix carries the SAME Go vendorHash as guest-image/default.nix
// (both buildGoModule + proxyVendor over go/); refresh-fod-hashes.ts mirrors
// the recomputed value into it, so it must be committable too or a gomod
// bump lands with flake.nix's vendorHash stale → `nix flake check` red.
"flake.nix",
"agent-image/entrypoint.nix",
],
executionMode: "branch",
Expand Down
61 changes: 59 additions & 2 deletions tools/renovate/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,10 +281,16 @@ describe("tools/renovate FOD-hash refresh wiring (PR #579)", () => {
expect(topLevel?.executionMode).toBe("branch");
});

test("the top-level task commits BOTH FOD files (fileFilters cover them)", () => {
test("the top-level task commits ALL THREE Go/bun FOD files (fileFilters cover them)", () => {
// gomod branches + bun/npm-first branches inherit this slot; it must be able
// to commit both the Go vendorHash file and the bun outputHash file.
// to commit the Go vendorHash file, its flake.nix mirror (identical hash,
// same buildGoModule proxyVendor set over go/ — refresh-fod-hashes.ts mirrors
// the recomputed value into it), and the bun outputHash file. Renovate only
// commits files a task's fileFilters names, so a missing flake.nix here would
// silently drop the mirror edit → a gomod bump lands with flake.nix's
// vendorHash stale and `nix flake check` red (RIG-2852 Gap 1).
expect(topLevel?.fileFilters).toContain("guest-image/default.nix");
expect(topLevel?.fileFilters).toContain("flake.nix");
expect(topLevel?.fileFilters).toContain("agent-image/entrypoint.nix");
});

Expand Down Expand Up @@ -662,6 +668,57 @@ describe("tools/renovate postgres + gomod go disables", () => {
});
});

describe("tools/renovate wails/v3 floor cap (RIG-2852, GTK4 migration)", () => {
// The GTK4 migration record freezes a "Never v3.1" floor: wails v3.1 removes
// the legacy GTK3 build tag, so an auto-opened v3.1 bump before the GTK4 flip
// (RIG-2819) is proven would strand the app with no native shell. A gomod
// packageRule caps github.com/wailsapp/wails/v3 below v3.1 via a REGEX
// allowedVersions (not a semver range — gomod's node-semver ranges exclude a
// prerelease at a different major.minor.patch, so `< 3.1.0-0` would wrongly
// reject the current v3.0.0 prerelease pin). Find it by behavior, not index.
const wailsRule = cfg.packageRules.find(
(r) =>
r.matchManagers?.includes("gomod") &&
r.matchDepNames?.includes("github.com/wailsapp/wails/v3"),
);

test("a gomod cap rule exists for wails/v3 with a regex allowedVersions", () => {
expect(wailsRule).toBeDefined();
expect(wailsRule?.matchManagers).toEqual(["gomod"]);
expect(wailsRule?.matchDepNames).toEqual(["github.com/wailsapp/wails/v3"]);
const allowedVersions = wailsRule?.allowedVersions ?? "";
// Slash-delimited regex form (matched against the raw version string),
// mirroring the postgres-stack /^18$/ rule.
expect(allowedVersions.startsWith("/")).toBe(true);
expect(allowedVersions.endsWith("/")).toBe(true);
});

test("the cap admits the LIVE go.mod pin + future v3.0.x, rejects v3.1.x and v4+", () => {
// Compile the shipped regex from its /.../ delimiters and replay it. The
// load-bearing assertion reads the ACTUAL wails require line from go/go.mod
// and asserts the cap admits whatever is pinned — so a future pin/regex
// pairing that would open zero PRs (the cap silently rejecting the real pin,
// the SEA-1220 freeze shape) fails HERE, tied to ground truth rather than a
// hard-coded literal. The boundary cases below then pin the reject edge so a
// fat-fingered cap (e.g. `^v?3\.`) that leaked v3.1 also fails.
const allowedVersions = wailsRule?.allowedVersions ?? "";
const matcher = new RegExp(allowedVersions.slice(1, -1));

const goMod = readFileSync(join(repoRoot, "go", "go.mod"), "utf8");
const pin = goMod.match(
/github\.com\/wailsapp\/wails\/v3\s+(?<version>\S+)/,
)?.groups?.version;
expect(pin).toBeDefined(); // the require line must exist
expect(matcher.test(pin as string)).toBe(true); // the cap MUST admit it

expect(matcher.test("v3.0.0-beta.7")).toBe(true); // a newer beta
expect(matcher.test("v3.0.1")).toBe(true); // a future v3.0 patch
expect(matcher.test("v3.1.0")).toBe(false); // the frozen floor
expect(matcher.test("v3.1.0-beta.0")).toBe(false); // a v3.1 prerelease
expect(matcher.test("v4.0.0")).toBe(false); // a future major
});
});

describe("tools/renovate postgres-stack digest manager (RIG-2774, DL-260)", () => {
// DefaultPostgresImage (go/internal/stack/postgres_image.go) is a standalone Go
// const the native managers can't see; a custom.regex manager surfaces it as a
Expand Down
38 changes: 38 additions & 0 deletions tools/renovate/refresh-fod-hashes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,16 @@ const GO_NIX_FIXTURE = `let
};
in guestd
`;
// The flake.nix mirror carries the SAME Go vendorHash marker; the script writes
// the recomputed value here too (FodEntry.mirrorFiles). A distinct placeholder so
// a missing-mirror regression is unmistakable (it would stay at this value).
const PLACEHOLDER_GO_MIRROR = "sha256-PLACEHOLDERflakemirror00000000000000000=";
const GO_MIRROR_FIXTURE = `let
compass-app = pkgs.buildGoModule {
${GO_ENTRY.marker}${bodyOf(PLACEHOLDER_GO_MIRROR)}";
};
in compass-app
`;
const BUN_NIX_FIXTURE = `let
nodeModules = pkgs.stdenv.mkDerivation {
${BUN_ENTRY.marker}${bodyOf(PLACEHOLDER_BUN)}";
Expand Down Expand Up @@ -133,6 +143,12 @@ async function buildBaselineRepo(): Promise<string> {
// Pin files at the paths the TABLE declares.
await write(GO_ENTRY.file, GO_NIX_FIXTURE);
await write(BUN_ENTRY.file, BUN_NIX_FIXTURE);
// Mirror pin files the Go entry declares (same vendorHash, refreshed in
// lockstep — derived from the shipped table so a rebase that edits mirrorFiles
// keeps this honest with no second edit).
for (const mirror of GO_ENTRY.mirrorFiles ?? []) {
await write(mirror, GO_MIRROR_FIXTURE);
}
// Trigger manifests (content is irrelevant; only their diff-vs-base matters).
for (const trigger of [...GO_ENTRY.triggers, ...BUN_ENTRY.triggers]) {
await write(trigger, "baseline\n");
Expand Down Expand Up @@ -198,6 +214,28 @@ describe("tools/renovate/refresh-fod-hashes.ts gate (PR #579)", () => {
);
});

// RIG-2852 Gap 1: the SAME go/go.mod bump must refresh the flake.nix MIRROR to
// the identical value — not just guest-image/default.nix. Before this fix the
// mirror was never touched, so an auto-opened Go bump landed with flake.nix's
// vendorHash stale and `nix flake check` red. Assert every declared mirror got
// the go-modules SRI and none kept its distinct placeholder.
test("refreshes every flake.nix mirror to the same SRI on a go/go.mod bump", async () => {
await Bun.write(join(repo, "go/go.mod"), "bumped\n");

const res = await runRefresh(repo);
expect(res.exitCode).toBe(0);

const mirrors = GO_ENTRY.mirrorFiles ?? [];
expect(mirrors.length).toBeGreaterThan(0); // the Go entry declares flake.nix
for (const mirror of mirrors) {
const text = await readFile(join(repo, mirror), "utf8");
expect(hashOnMarker(text, GO_ENTRY.marker)).toBe(
stubSriForFragment("go-modules"),
);
expect(text).not.toContain(bodyOf(PLACEHOLDER_GO_MIRROR));
}
});

// A gomod-only bump refreshes the Go vendorHash and leaves the bun outputHash
// pin byte-for-byte untouched — the per-FOD self-gate granularity.
test("a go/go.mod bump leaves the bun outputHash pin untouched", async () => {
Expand Down
31 changes: 27 additions & 4 deletions tools/renovate/refresh-fod-hashes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,19 @@
// red on a `hash mismatch in fixed-output derivation` build break (the RIG-2432
// easy-dep-bump goal, PR #579's failure class).
//
// Compass pins exactly two FOD hashes outside the vendored forks/ trees, each
// content-addressing a fetched dependency set that MOVES when a manifest bumps:
// Compass pins two FOD hash VALUES outside the vendored forks/ trees, each
// content-addressing a fetched dependency set that MOVES when a manifest bumps.
// The Go vendorHash is pinned in TWO files that share it by design (below):
//
// guest-image/default.nix vendorHash compass-guestd's Go module set
// (buildGoModule; no vendor/ dir) —
// invalidated by a go/go.mod|go.sum bump
// flake.nix vendorHash compass-app + cmd-binaries' module set
// — the SAME proxyVendor hash over go/
// (flake.nix:46-52 documents the equality),
// both invalidated by a go/go.mod|go.sum
// bump. The build vehicle realises ONLY
// guestd's FOD; flake.nix is refreshed as a
// MIRROR — the identical value, no second
// realise — see FodEntry.mirrorFiles.
// agent-image/entrypoint.nix outputHash compass-agent's installed node_modules
// tree (recursive FOD of `bun install`) —
// invalidated by a bun.lock bump
Expand Down Expand Up @@ -85,6 +92,13 @@ export type FodEntry = {
// Manifests whose change invalidates this FOD (repo-root-relative). The
// per-entry self-gate fires when any of these differs from the base branch.
triggers: string[];
// Extra files carrying the IDENTICAL pinned hash (same `marker`), equal to
// `file`'s by construction — e.g. a second buildGoModule with the same
// proxyVendor set over the same go/. They are NOT separately realised (the
// build vehicle content-addresses only `file`'s FOD, so a faked mirror pin
// would never surface in its output); each is rewritten to the SRI `file`'s
// realise reports. Absent for a lone pin.
mirrorFiles?: string[];
};

export const FOD_ENTRIES: FodEntry[] = [
Expand All @@ -93,6 +107,7 @@ export const FOD_ENTRIES: FodEntry[] = [
marker: 'vendorHash = "sha256-',
drvFragment: "go-modules",
triggers: ["go/go.mod", "go/go.sum"],
mirrorFiles: ["flake.nix"],
},
{
file: "agent-image/entrypoint.nix",
Expand Down Expand Up @@ -264,6 +279,14 @@ async function main(): Promise<void> {
rewriteInlineHash(origText, entry.marker, got, entry.file),
);
console.log(`renovate-fod: ${entry.file} -> ${got}`);
for (const mirror of entry.mirrorFiles ?? []) {
const mirrorText = await Bun.file(mirror).text();
await Bun.write(
mirror,
rewriteInlineHash(mirrorText, entry.marker, got, mirror),
);
console.log(`renovate-fod: ${mirror} (mirror) -> ${got}`);
}
}

console.log("renovate-fod: FOD hashes refreshed.");
Expand Down
Loading