From 89234fc79e8cd677b442de89fd7c6a79ecb5b1b1 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sat, 5 Sep 2026 07:19:17 +0700 Subject: [PATCH] fix(loop): a branch whose intent was re-filed is history, not debt - and stop advising a rebase that would destroy work For four rounds `why` warned that all remaining accepted branches conflict, that nothing can land and that the swarm will starve behind them. Mechanically true and useless: those branches will never land. Their base has moved past them, replaying one would delete work that landed since, and there was nothing in the loop that could say so. `salvage` has now measured what each still owes and filed it as a brief against today's base - gHashTag/trios#1537, #1538, #1539, #1540. So the branches are recorded as REPLACED, with the issue that replaces each: queen-1302 -> #1537 billing mode and quota authority queen-1303 -> #1538 the started_running state queen-1387 -> #1539 the heading-parity checker queen-1422 -> #1540 the noise filter that deleted its answer Five conflicting branches become one, and the one that remains - queen-1484 - is not debt either: it is an OPEN issue whose own criterion fails against the base today, which the swarm can pick up as ordinary work. RECORDED, NEVER INFERRED. Nothing here guesses that a branch is superseded. Someone says so and says by what, in a file, with a timestamp. The branches themselves are untouched: no deletion, no force, nothing irreversible. The selftest fails if `--force`, `branch -D` or `push --delete` ever appear in this file. AND THE ADVICE CHANGED, because it was wrong. For four rounds this tool told a reader that a conflicting branch "needs a rebase, or an honest closure as superseded". Rebasing #1302 would have deleted #1308's landed code and reintroduced a non-ASCII ellipsis into a redaction the base already performs in ASCII. It now says what was learned: A rebase is usually the WRONG remedy once the base has moved: replaying an old branch can delete work that landed since, and applying cleanly proves nothing, because a semantic conflict carries no markers. tri salvage measures what the branch would still add, asks the issue its own criterion against the base, and writes the remainder as a brief A tool that recommends the destructive option in one line, every round, for four rounds, is worse than one that says nothing. selftest 160 pass 0 fail. --- trios/.trinity/loop/land.mjs | 52 ++++++++++++++++++++++++++++++-- trios/.trinity/loop/selftest.mjs | 20 ++++++++++++ 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/trios/.trinity/loop/land.mjs b/trios/.trinity/loop/land.mjs index dafa92d439..604ea95ce4 100644 --- a/trios/.trinity/loop/land.mjs +++ b/trios/.trinity/loop/land.mjs @@ -31,6 +31,7 @@ // node land.mjs # report the gap // node land.mjs --land # squash-merge the next batch that applies cleanly +import fs from 'node:fs' import path from 'node:path' import { execSync } from 'node:child_process' import { fileURLToPath } from 'node:url' @@ -91,6 +92,36 @@ const sh = (c, opts = {}) => { * somewhere it is known. git is asked the loose question and the boundary is * checked here. */ + +// ------------------------------------------------------------ replacements + +// A BRANCH WHOSE INTENT HAS BEEN RE-FILED IS NOT DEBT, IT IS HISTORY. +// +// Five branches conflicted for four rounds and `why` warned every round that the +// pipeline would starve behind them. The warning was mechanically true and +// useless: they will never land. Their base has moved past them, rebasing one +// would delete work that landed since, and `salvage` has now measured what each +// still owes and filed it as a fresh brief against today's base. +// +// So they are recorded as REPLACED, with the issue that replaces them. This is +// deliberately a written act rather than an inference: nothing here guesses that +// a branch has been superseded, someone says so and says by what. The branches +// are left alone - no deletion, no force, nothing irreversible - and the +// pipeline stops counting them as work it is stuck behind. +const REPLACED = path.join(DIR, 'state', 'replaced-branches.json') + +export function replacements() { + try { return JSON.parse(fs.readFileSync(REPLACED, 'utf8')) } catch { return {} } +} + +export function recordReplacement(branch, issue, note = '') { + const all = replacements() + all[branch] = { issue, note, at: new Date().toISOString() } + fs.mkdirSync(path.dirname(REPLACED), { recursive: true }) + fs.writeFileSync(REPLACED, JSON.stringify(all, null, 2)) + return all[branch] +} + export function closedInBase(branch, run = sh) { const m = String(branch).match(/queen-(\d+)$/) if (!m) return false @@ -287,6 +318,10 @@ export async function survey() { // The right question is whether the WORK is in the base, and the diff // answers it whatever route the work took: empty diff, work landed. if (isLanded(b)) continue + // Recorded as replaced: its intent was measured and re-filed against today's + // base. Not debt, and not something the pipeline is stuck behind. + const repl = replacements()[b] + if (repl) continue const issue = (b.match(/queen-(\d+)/) || [])[1] const stat = sh(`git diff --shortstat origin/${BASE}...origin/${b}`) || '' const files = Number((stat.match(/(\d+) files? changed/) || [])[1] || 0) @@ -346,6 +381,12 @@ if (isMain) { const m = mergesCleanly(r.branch) if (m.clean) { r.clean = true; batch.push(r) } else { r.clean = false; r.why = m.why; skipped.push(r) } } + const replaced = Object.entries(replacements()) + if (replaced.length) { + console.log(`\n${replaced.length} branch(es) recorded as REPLACED - their intent was measured and re-filed, so they are history rather than debt:`) + for (const [b, r] of replaced) console.log(` ${b.padEnd(16)} -> #${r.issue}${r.note ? ` ${r.note}` : ''}`) + console.log(' The branches are untouched. Nothing was deleted and nothing was forced.') + } for (const r of skipped) { // The whole reason, not the first 96 characters of it. The truncation hid // exactly the half that says what to DO about the conflict. @@ -357,9 +398,14 @@ if (isMain) { if (!batch.length && landable.length) { console.log('') console.log(`ALL ${landable.length} remaining branch(es) conflict. Nothing here can be landed by merging.`) - console.log('A conflict is reported for a person, never resolved by guessing - these need a') - console.log('rebase, or an honest closure as superseded. Meanwhile close-done refuses to') - console.log('close anything that has not landed, so this is where the pipeline stops.') + console.log('A conflict is reported for a person, never resolved by guessing. And a rebase') + console.log('is usually the WRONG remedy once the base has moved: replaying an old branch') + console.log('can delete work that landed since, and applying cleanly proves nothing, because') + console.log('a semantic conflict carries no markers.') + console.log(' tri salvage measures what the branch would still add, asks the issue its') + console.log(' own criterion against the base, and writes the remainder as a brief') + console.log('Meanwhile close-done refuses to close anything that has not landed, so this is') + console.log('where the pipeline stops.') } console.log(`\n${landable.length} landable, showing the next ${batch.length} (batch ${BATCH}).`) console.log('A closed issue whose code is not in the branch is a false statement about the repository.') diff --git a/trios/.trinity/loop/selftest.mjs b/trios/.trinity/loop/selftest.mjs index 0f2c66d37e..c7b1087cdd 100644 --- a/trios/.trinity/loop/selftest.mjs +++ b/trios/.trinity/loop/selftest.mjs @@ -2020,6 +2020,26 @@ check('an unmeasurable branch says so instead of saying superseded', () => { if (!/a reader still decides/.test(code)) throw new Error('the tool measures; the person concludes') }) +check('a replacement is written down, never inferred', async () => { + const LAND = await import('./land.mjs') + const code = codeOf('land.mjs') + // Five branches conflicted for four rounds and the warning was mechanically + // true and useless: they will never land. Recording the issue that replaces + // one is a deliberate act - nothing here GUESSES that a branch is superseded. + if (!/recordReplacement/.test(code)) throw new Error('a replacement must be recorded by someone') + if (typeof LAND.replacements() !== 'object') throw new Error('and readable back') + if (/git push --delete|branch -D|--force/.test(code)) throw new Error('nothing about this is destructive; the branches stay') +}) + +check('land no longer advises a rebase it knows is usually wrong', () => { + const code = codeOf('land.mjs') + // Rebasing #1302 would have deleted #1308's landed work and reintroduced an + // L3 violation, and the tool recommended it in one line for four rounds. + if (/these need a\s+rebase/.test(code)) throw new Error('that advice was toward destroying finished code') + if (!/tri salvage/.test(code)) throw new Error('point at the tool that measures instead of guessing') + if (!/semantic conflict/.test(code)) throw new Error('and say why "it applied cleanly" is not evidence') +}) + check('the harness can fail an async check', async () => { // Guarding the fix above: before it, this file reported 0 failures while an // async case was rejecting into the void.