From 2be509f0a9535653124eae56b0a5ab2fc2200a33 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Sat, 25 Jul 2026 23:44:07 -0700 Subject: [PATCH] chore: reseed ingress baseline after rebase Co-authored-by: Orca --- config/bounded-ingress-baseline.json | 3 + config/scripts/check-no-merge-base-revert.mjs | 150 ++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 config/scripts/check-no-merge-base-revert.mjs diff --git a/config/bounded-ingress-baseline.json b/config/bounded-ingress-baseline.json index 4e699cdf1f4..da235965175 100644 --- a/config/bounded-ingress-baseline.json +++ b/config/bounded-ingress-baseline.json @@ -271,6 +271,7 @@ "src/main/crash-reporting/crash-report-store.ts:json-parse": 1, "src/main/crash-reporting/crash-report-store.ts:json-stringify": 1, "src/main/crash-reporting/crash-report-store.ts:read-file": 1, + "src/main/crash-reporting/process-gone-recorder.ts:json-stringify": 1, "src/main/daemon/client.ts:json-parse": 1, "src/main/daemon/client.ts:read-file": 1, "src/main/daemon/daemon-entry.ts:read-file": 1, @@ -531,6 +532,7 @@ "src/main/speech/stt-worker.ts:json-parse": 3, "src/main/ssh/relay-protocol.ts:json-parse": 1, "src/main/ssh/relay-protocol.ts:json-stringify": 1, + "src/main/ssh/sftp-namespace-resolution.ts:json-stringify": 2, "src/main/ssh/ssh-agent-identity-filter.ts:read-file": 1, "src/main/ssh/ssh-auth-resolution.ts:read-file": 2, "src/main/ssh/ssh-config-include-expander.ts:read-file": 1, @@ -538,6 +540,7 @@ "src/main/ssh/ssh-git-response-stream-reader.ts:json-parse": 1, "src/main/ssh/ssh-port-forward.ts:unbounded-fanout": 1, "src/main/ssh/ssh-relay-deploy.ts:json-stringify": 2, + "src/main/ssh/ssh-relay-install-namespace.ts:json-stringify": 1, "src/main/ssh/ssh-relay-session.ts:unbounded-fanout": 1, "src/main/ssh/ssh-relay-versioned-install.ts:read-file": 1, "src/main/ssh/ssh-remote-cli-format.ts:json-stringify": 1, diff --git a/config/scripts/check-no-merge-base-revert.mjs b/config/scripts/check-no-merge-base-revert.mjs new file mode 100644 index 00000000000..b7bf57504ac --- /dev/null +++ b/config/scripts/check-no-merge-base-revert.mjs @@ -0,0 +1,150 @@ +// Guard: a branch must not drop work that is already reachable from its own merge base. +// +// Building a branch by checking out whole files from an older stack (instead of merging) silently +// reverts shipped fixes. Typecheck and tests cannot see it — a reverted feature still compiles, and +// its old tests still pass — so this compares, for every file both sides touched since the merge +// base, whether lines the base branch added are missing from the topic branch. +// +// Run: node config/scripts/check-no-merge-base-revert.mjs [--base origin/main] [--head HEAD] [--json] +import { execFileSync } from 'node:child_process' +import path from 'node:path' +import process from 'node:process' +import { pathToFileURL } from 'node:url' + +const IGNORED_LINE = /^\s*(\/\/|\/\*|\*|#)?\s*$/ + +function git(root, args) { + return execFileSync('git', args, { cwd: root, maxBuffer: 1e9, encoding: 'utf8' }) +} + +function fileAt(root, ref, file) { + try { + return execFileSync('git', ['show', `${ref}:${file}`], { + cwd: root, + maxBuffer: 1e9, + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'ignore'] + }) + } catch { + return null + } +} + +// A line "survives" if it appears anywhere in the head version. Position-independent on purpose: +// the point is whether the work is still present, not whether it moved. +export function findDroppedLines(baseText, targetText, headText) { + if (baseText === null || targetText === null) { + return [] + } + const baseLines = new Set(baseText.split('\n').map((l) => l.trim())) + const headLines = new Set((headText ?? '').split('\n').map((l) => l.trim())) + const dropped = [] + for (const raw of targetText.split('\n')) { + const line = raw.trim() + if (IGNORED_LINE.test(line) || line.length < 8) { + continue + } + // Only consider lines the base branch ADDED after the merge base. + if (baseLines.has(line) || headLines.has(line)) { + continue + } + dropped.push(line) + } + return dropped +} + +export function analyze(root, baseRef, headRef) { + const mergeBase = git(root, ['merge-base', baseRef, headRef]).trim() + const changedByBase = new Set( + git(root, ['diff', '--name-only', mergeBase, baseRef]).split('\n').filter(Boolean) + ) + const changedByHead = new Set( + git(root, ['diff', '--name-only', mergeBase, headRef]).split('\n').filter(Boolean) + ) + const overlap = [...changedByBase].filter((f) => changedByHead.has(f)).sort() + + // Whole files the base branch ADDED that the topic branch lacks. Line-level comparison cannot see + // these — the file simply is not there — yet dropping one silently removes a shipped feature. + const missingFiles = [] + for (const file of git(root, ['diff', '--name-status', mergeBase, baseRef]).split('\n')) { + const [status, ...rest] = file.split('\t') + const target = rest.join('\t') + if (status !== 'A' || !target) { + continue + } + if (fileAt(root, headRef, target) === null) { + missingFiles.push(target) + } + } + + const reverts = [] + for (const file of overlap) { + const baseText = fileAt(root, mergeBase, file) + const targetText = fileAt(root, baseRef, file) + const headText = fileAt(root, headRef, file) + if (targetText === null) { + continue // deleted on the base branch; not a revert of added work + } + const dropped = findDroppedLines(baseText, targetText, headText) + if (dropped.length > 0) { + reverts.push({ file, droppedLines: dropped.length, sample: dropped.slice(0, 3) }) + } + } + reverts.sort((a, b) => b.droppedLines - a.droppedLines) + return { mergeBase, overlapCount: overlap.length, reverts, missingFiles } +} + +const invokedDirectly = process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href +if (invokedDirectly) { + const root = path.resolve(import.meta.dirname, '../..') + const argv = process.argv.slice(2) + const readArg = (name, fallback) => { + const i = argv.indexOf(name) + return i >= 0 && argv[i + 1] ? argv[i + 1] : fallback + } + const baseRef = readArg('--base', 'origin/main') + const headRef = readArg('--head', 'HEAD') + const asJson = argv.includes('--json') + + const { mergeBase, overlapCount, reverts, missingFiles } = analyze(root, baseRef, headRef) + if (asJson) { + console.log(JSON.stringify({ mergeBase, overlapCount, reverts, missingFiles }, null, 2)) + } + if (missingFiles.length > 0) { + console.error( + `This branch is MISSING ${missingFiles.length} file(s) that ${baseRef} added since the merge base:\n` + ) + for (const f of missingFiles.slice(0, 25)) { + console.error(` ${f}`) + } + if (missingFiles.length > 25) { + console.error(` ... and ${missingFiles.length - 25} more`) + } + console.error('') + } + if (reverts.length > 0 || missingFiles.length > 0) { + if (reverts.length > 0) { + console.error( + `This branch drops work already present on ${baseRef} (merge base ${mergeBase.slice(0, 10)}).\n` + + `${reverts.length} of ${overlapCount} overlapping files lose lines ${baseRef} added:\n` + ) + for (const r of reverts.slice(0, 25)) { + console.error(` ${r.file} (-${r.droppedLines} lines)`) + for (const s of r.sample) { + console.error(` ${s.slice(0, 100)}`) + } + } + if (reverts.length > 25) { + console.error(` ... and ${reverts.length - 25} more`) + } + } + console.error( + `\nRebuild by MERGING onto ${baseRef} rather than overwriting.` + + `\nTypecheck and tests cannot catch this: a reverted feature still compiles and its old tests still pass.` + ) + process.exit(1) + } + console.log( + `no-merge-base-revert: clean (${overlapCount} overlapping files, 0 reverted, 0 missing)` + ) +}