From 27eb0556d2ddf1ae78a0c7e80aa83f3fb7d75ffa Mon Sep 17 00:00:00 2001 From: "Eduardo A." Date: Sat, 26 Sep 2026 20:40:39 -0300 Subject: [PATCH] chore(ci): stop test-map.json from conflicting on every PR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JUM-910. test-map.json is fully generated (deterministic filesystem walk + sort), so any two branches touching test files diverge and conflict on merge even when neither is wrong — this hit every PR in the docs epic. setup-git-merge-drivers.js (run from `prepare`, so every clone self-registers on `bun install`) registers a merge driver that keeps "ours" instead of a textual 3-way merge, so a spurious conflict here can never block a merge. Verified this is safe by reproducing a real two-branch conflict: a first design that regenerated *inside* the driver silently produced an incomplete manifest, because a merge driver isn't guaranteed the full merged tree is checked out to disk yet. The shipped design instead defers to test-map:check, now part of ci:gate:static (previously only in the strict dev/main matrix), so every branch-gate preflight fails closed on the resulting staleness until `test-map:generate` runs for real. Co-Authored-By: Claude Opus 5.5 --- .gitattributes | 8 +++++++ ci-cd/setup-git-merge-drivers.js | 36 ++++++++++++++++++++++++++++++++ package.json | 4 ++-- 3 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 ci-cd/setup-git-merge-drivers.js diff --git a/.gitattributes b/.gitattributes index dfe077042..8f0316d33 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,10 @@ # Auto detect text files and perform LF normalization * text=auto + +# Generated from the current test tree (ci-cd/generate-test-map.js). Two +# branches that both touch test files textually conflict on this file even +# when neither is wrong, so conflicts here keep "ours" instead of blocking +# (driver registered by `prepare` — ci-cd/setup-git-merge-drivers.js); +# `test-map:check` (in `ci:gate:static`) fails closed on the resulting +# staleness until `test-map:generate` runs for real. +test-map.json merge=keep-ours-test-map diff --git a/ci-cd/setup-git-merge-drivers.js b/ci-cd/setup-git-merge-drivers.js new file mode 100644 index 000000000..cd8687ef5 --- /dev/null +++ b/ci-cd/setup-git-merge-drivers.js @@ -0,0 +1,36 @@ +/* eslint-disable no-console */ +const { execFileSync } = require('child_process'); + +/** + * `test-map.json` is a pure function of the current test tree, regenerated by + * its own `test-map:generate` script — so a textual 3-way merge on it + * conflicts whenever two branches both touch test files, even when neither + * branch's content is actually wrong. + * + * The driver does NOT regenerate the file itself: a merge driver runs mid-merge, + * before git guarantees every other path in the same merge is checked out to + * disk (proven empirically — a driver that shelled out to the generator here + * silently produced a manifest missing the other side's new test file, with + * no conflict marker to catch it). Instead the driver just keeps the current + * branch's version (`true`, exit 0, %A untouched) so a real conflict never + * blocks the merge; `test-map:check` — now part of `ci:gate:static`, which + * every branch-gate preflight runs (JUM-903) — catches the resulting + * staleness immediately and fails closed until `test-map:generate` runs for + * real, against a fully checked-out tree. + * + * Git looks up drivers in local (untracked) config, so every clone must + * register them once; this runs from `prepare` (already invoked by + * `bun install` on every clone/worktree) so no extra onboarding step is needed. + */ +const DRIVERS = [ + { + name: 'keep-ours-test-map', + command: 'true' + } +]; + +for (const driver of DRIVERS) { + execFileSync('git', ['config', `merge.${driver.name}.name`, `keep ours for ${driver.name}; test-map:check catches staleness`]); + execFileSync('git', ['config', `merge.${driver.name}.driver`, driver.command]); + console.log(`[setup-git-merge-drivers] registered merge.${driver.name}`); +} diff --git a/package.json b/package.json index af2567cc6..ae65d6bb5 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "docs:consumers:package-scripts": "bun tooling/scripts/generate-consumer-package-scripts-docs.mjs", "docs:check-current-governance": "bun ci-cd/check-current-governance-docs.js", "docs:check-audience": "bun ci-cd/check-documentation-audience.js", - "prepare": "husky", + "prepare": "husky && node ci-cd/setup-git-merge-drivers.js", "changelog:update": "bun ci-cd/update-changelog.js", "changelog:check": "bun ci-cd/update-changelog.js --check", "oas:check-routes": "bun ci-cd/check-oas-route-resolution.js", @@ -86,7 +86,7 @@ "ci:gate": "bun run check-bun-version && bun run deps:check-overrides && bun run deps:audit && bun run lint && bun run deps:check-cycles && bun run arch:check-boundaries && bun run arch:check-users-legacy-imports && bun run arch:check-workspace-boundaries && bun run arch:check-ownership-placement && bun run arch:check-http-adapters && bun run workspace:check-quality && bun run workspace:check-coverage-policy && bun run release:governance:check && bun run governance:check-authorship && bun run requirements:check && bun run docs:check-current-governance && bun run docs:check-audience && bun run packages:check-suites && bun run packages:check-build-freshness && bun run cli:check-template-freshness && bun run website:check-content-routes && bun run rtdb:check-indexes && bun run test:integrity && bun run test-map:check && bun run ci:check-provider && bun run ci:check-third-party-review && bun run integrations:check && bun run integration-migration:check && bun run agent-registry:check && bun run test:unit && bun run frontend:test:coverage && bun run frontend:coverage:check && bun run ci:security-smoke && bun run oas:check-routes && bun run oas:check-relations && bun run serverless:check-handlers && bun run build:dev && bun run ci:smoke", "ci:gate:branch": "bun ci-cd/run-branch-quality-gate.js", "ci:gate:task": "bun run workspace:build:packages && bun ci-cd/run-task-change-tests.js", - "ci:gate:static": "bun run deps:check-overrides && bun run deps:audit && bun run arch:check-http-adapters && bun run governance:check-authorship && bun run packages:check-suites && bun run packages:check-build-freshness && bun run cli:check-template-freshness && bun run website:check-content-routes && bun run rtdb:check-indexes && bun run ci:check-provider && bun run oas:check-relations", + "ci:gate:static": "bun run deps:check-overrides && bun run deps:audit && bun run arch:check-http-adapters && bun run governance:check-authorship && bun run packages:check-suites && bun run packages:check-build-freshness && bun run cli:check-template-freshness && bun run website:check-content-routes && bun run rtdb:check-indexes && bun run test-map:check && bun run ci:check-provider && bun run oas:check-relations", "ci:gate:strict": "bun ci-cd/run-full-test-matrix.js", "ci:gate:generated-automation": "bun ci-cd/check-generated-automation-pr.js", "dev": "bun run pm2:start:dev:restapi",