From 96d4acb3dcac482ec323e8e6f1e24e7d717b8459 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sat, 5 Sep 2026 07:06:10 +0700 Subject: [PATCH] feat(loop): what survives of a branch the base moved past, as a specification rather than a patch Five branches had jammed the landing pipeline for four rounds. `land` could say they conflict and how far the base had travelled; the next question was the one a person has to answer, and I kept deferring it: rebase, or re-do the work? Rebasing #1302 would have deleted #1308's landed code and reintroduced a non-ASCII ellipsis into a redaction the base already performs in ASCII, breaking L3 in the same stroke. AND A CLEAN REBASE WOULD NOT HAVE SHOWN IT. A semantic conflict carries no markers: git resolves the text correctly while the combined code is logically broken - upstream renames a function your commit still calls, the changes sit on different lines, nothing conflicts, everything is wrong. So "it applied cleanly" is not evidence, and `salvage.mjs` does not offer it. It applies nothing. It never rebases. The selftest fails if the words `git rebase`, `cherry-pick` or `git apply` ever appear in it. WHAT IT PRODUCES is the standard answer for a branch whose module was restructured underneath it: use the old diff as a statement of what was WANTED, not as patches. Concretely - the names the branch introduces that the repository still does not have: queen-1302 8 of 200 candidates absent: quotaAuthority, provider_quota, estimated_usd_gate, statusMode, readTelemetry, QuotaAuthority, billingProjection, ResearchDeps queen-1303 4 of 243: started_running, plantedSecret, startedUnfinished, startedRunning queen-1387 19 of 273: specQualityHeadingParity, extractorHeadings, checkNames, maskNonAscii, RING_COPY, scanMatchingClose, ... Three branches of 881 added lines reduce to 31 missing names, because most of what they carried had already landed by another route. IT IS NOT A PARSE, deliberately. Six false accusations in this project came from a checker that tried to recognise a declaration by its shape across Swift, TypeScript and markdown, and the list of ways to spell "define" has no end. Every identifier-shaped token is a candidate and the BASE TREE decides which are new - absence is checkable without knowing any language. A hex blob is dropped, because queen-1303's diff offered `fedcba9876` from a fake sha in a fixture, and proposing that as a capability the repository lacks is the `node_modules` accusation in a new costume. The cap on candidates is printed alongside the total, since a silent truncation would read as "this is everything". `--brief` writes the measurement as an issue a bee can pass, and every criterion is FAIL-TO-PASS BY CONSTRUCTION: the name was measured absent from the base a moment earlier, so it cannot be satisfied by an empty patch and `verdict-audit` will check it against both the fork point and the branch without being asked. Filed as gHashTag/trios#1537, #1538 and #1539, all three passing the delegation gate with their boundaries parsed. They carry no `queen-authored` label on purpose: that label is `author.mjs`'s own WIP accounting, and salvage work is not the backlog it is metering. selftest 155 pass 0 fail. `tri salvage [--brief]`. --- trios/.trinity/loop/coverage.mjs | 1 + trios/.trinity/loop/salvage.mjs | 261 +++++++++++++++++++++++++++++++ trios/.trinity/loop/selftest.mjs | 52 ++++++ 3 files changed, 314 insertions(+) create mode 100644 trios/.trinity/loop/salvage.mjs diff --git a/trios/.trinity/loop/coverage.mjs b/trios/.trinity/loop/coverage.mjs index 10732d681a..d9b08d09fa 100644 --- a/trios/.trinity/loop/coverage.mjs +++ b/trios/.trinity/loop/coverage.mjs @@ -72,6 +72,7 @@ const ACTS = { 'loop.mjs': { kinds: [], note: 'the library the others import - it has no act path of its own' }, 'brief-gate.mjs': { kinds: [], note: 'a gate: it refuses or permits, and changes nothing' }, 'coverage.mjs': { kinds: [], note: 'this file' }, + 'salvage.mjs': { kinds: [], note: 'a reader: it measures what a stale branch would still add and writes a brief; it applies nothing and never rebases' }, 'proven.mjs': { kinds: [], note: 'a comparison: recent verdicts against the baseline this process established; it records a reading and changes nothing' }, 'selftest.mjs': { kinds: [], note: 'the calibration harness - proven by running, not by acting' }, } diff --git a/trios/.trinity/loop/salvage.mjs b/trios/.trinity/loop/salvage.mjs new file mode 100644 index 0000000000..c615541edc --- /dev/null +++ b/trios/.trinity/loop/salvage.mjs @@ -0,0 +1,261 @@ +#!/usr/bin/env node +// What is left of a branch the base has moved past. +// +// WHY THIS IS NOT A REBASE, AND WILL NEVER RUN ONE. +// +// `land` can already say a branch conflicts and how far the base has travelled +// on the files it touches. The next question is the one a person has to answer: +// rebase it, or re-do the work? Rebasing #1302 would have deleted #1308's landed +// code and reintroduced a non-ASCII ellipsis into a redaction the base already +// performs in ASCII. The advice was one line long and would have destroyed +// finished work. +// +// And a clean rebase would not have proved otherwise. A SEMANTIC CONFLICT +// carries no markers: git resolves the text correctly while the combined code is +// logically broken - upstream renames a function your commit still calls, the +// changes are on different lines, nothing conflicts, everything is wrong. So +// "it applied cleanly" is not evidence, and this tool does not offer it. +// +// WHAT IT PRODUCES INSTEAD is a SPECIFICATION OF INTENT: the names this branch +// introduces that the repository still does not have. That is the standard +// advice for a branch whose module has been restructured underneath it - use the +// old diff as a statement of what was wanted, not as a patch - and it is +// language-independent, because ABSENCE is checkable without knowing how Swift +// or TypeScript spells "define". +// +// It is also exactly the shape a brief needs: a name that appears nowhere in the +// tree today is a criterion `verdict-audit` can run, and the fork-point check +// makes it a fail-to-pass one by construction. +// +// Usage: +// node salvage.mjs [...] +// node salvage.mjs --conflicting # every branch land reports as stuck + +import { execSync } from 'node:child_process' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const DIR = path.dirname(fileURLToPath(import.meta.url)) +const isMain = process.argv[1] && process.argv[1].endsWith('/salvage.mjs') +const ROOT = process.env.TRIOS_ROOT || '/Users/playra/BrowserOS' +const BASE = process.env.TRIOS_BASE || 'feat/queen-supervisor' + +const sh = (cmd) => { + try { + return execSync(cmd, { cwd: ROOT, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 64 * 1024 * 1024 }).trim() + } catch { return null } +} + +// Words that are not a contribution. A keyword, a type name from the standard +// library, or a token every file already contains says nothing about what this +// branch was for - and proposing one as "the name the repository lacks" is the +// `node_modules` accusation in a new costume. +export const NOISE = new Set([ + 'const', 'let', 'var', 'function', 'return', 'import', 'export', 'from', 'type', + 'interface', 'class', 'enum', 'async', 'await', 'this', 'null', 'undefined', + 'true', 'false', 'string', 'number', 'boolean', 'object', 'void', 'never', + 'public', 'private', 'static', 'readonly', 'extends', 'implements', 'struct', + 'guard', 'throws', 'where', 'case', 'switch', 'default', 'break', 'continue', + 'catch', 'throw', 'finally', 'yield', 'typeof', 'instanceof', 'delete', + 'describe', 'expect', 'test', 'beforeEach', 'afterEach', 'console', 'process', + 'Promise', 'Array', 'Object', 'String', 'Number', 'Boolean', 'Error', 'Math', + 'JSON', 'Date', 'Map', 'Set', 'RegExp', 'Symbol', 'BigInt', 'globalThis', +]) + +/** + * Candidate names introduced by the added lines of a diff. + * + * Deliberately NOT a parse. Six false accusations came from a checker that tried + * to recognise a declaration by its shape across Swift, TypeScript and markdown; + * the list of ways to spell "define" has no end. Every identifier-looking token + * is a candidate, and the base tree decides which of them are actually new. + */ +export function candidates(addedLines) { + const seen = new Map() + for (const line of addedLines) { + // Skip the diff's own leading '+', and anything inside a string or comment + // is fine to include - a name that only appears in a comment will be + // filtered out by the base-tree check if the base has it, and reported with + // its count if it does not. + for (const m of line.slice(1).matchAll(/[A-Za-z_][A-Za-z0-9_]{3,}/g)) { + const id = m[0] + if (NOISE.has(id)) continue + // A hex blob is a fixture, not a name. queen-1303's diff yielded `fedcba` + // and `fedcba9876` - fragments of a fake commit sha in a test - and + // proposing them as capabilities the repository lacks would be exactly + // the `node_modules` accusation wearing a new costume. + if (/^[0-9a-fA-F]{6,}$/.test(id)) continue + seen.set(id, (seen.get(id) || 0) + 1) + } + } + return [...seen.entries()].sort((a, b) => b[1] - a[1]).map(([id, n]) => ({ id, n })) +} + +/** Is this name anywhere in the tree at this ref? */ +export function inTree(ref, id, run = sh) { + if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(id)) return false + return Boolean(run(`git grep -l -w -e ${id} ${ref} -- . | head -1`)) +} + +/** + * What this branch would still add, expressed as names the base does not have. + * + * `cap` exists because a large branch yields hundreds of candidates and each one + * costs a `git grep`. The cap is on the CANDIDATES, ordered by how often the + * branch mentions them, and the number examined is printed - a silent truncation + * would read as "this is everything", which it would not be. + */ +export function surviving(branch, opts = {}) { + const { cap = 80, run = sh } = opts + const fork = run(`git merge-base origin/${BASE} origin/${branch}`) + if (!fork) return null + const files = (run(`git diff --name-only ${fork}..origin/${branch}`) || '').split('\n').filter(Boolean) + const added = (run(`git diff ${fork}..origin/${branch} -- . | grep '^+' | grep -v '^+++'`) || '').split('\n').filter(Boolean) + const all = candidates(added) + const examined = all.slice(0, cap) + const missing = examined.filter((c) => !inTree(`origin/${BASE}`, c.id, run)) + return { + branch, + fork, + files, + addedLines: added.length, + candidates: all.length, + examined: examined.length, + missing, + } +} + + +/** + * The measurement, written as a brief a bee can pass. + * + * Every criterion here is fail-to-pass BY CONSTRUCTION: the name was measured + * absent from the base a moment ago, so the check cannot be satisfied by an + * empty patch and `verdict-audit` will run it against both the fork point and + * the branch without being asked. + * + * The old branch is cited as the specification of intent and NOT as a patch to + * apply, which is the whole finding of the round that produced this: rebasing + * one of these would have deleted landed work and reintroduced a law violation, + * and a clean rebase would not have shown it, because a semantic conflict + * carries no markers. + */ +export function brief(s, issue) { + const names = s.missing.map((m) => m.id) + const shown = names.slice(0, 10) + return `## Why + +\`origin/${s.branch}\` carries accepted work that never reached the base. Its issue +is closed and its code is not in the branch, which is a false statement about the +repository in the direction nobody checks for. + +It cannot simply be rebased. The base has moved on the files it touches, and on a +sibling branch of the same vintage a replay would have deleted work that landed +since and reintroduced a non-ASCII character into a file governed by L3. A clean +rebase would not have revealed either: a semantic conflict carries no conflict +markers. + +So the old branch is a SPECIFICATION OF INTENT here, not a patch. What follows was +measured, not read: of ${s.candidates} identifier-shaped tokens in its ${s.addedLines} +added lines, these ${names.length} appear nowhere in the base today. + +## What done looks like + +The capability those names stand for exists on today's base, with tests, written +as if for the first time. Reproducing the old file byte for byte is not the goal +and is very likely wrong. + +## What a bee can do here + +Read \`git diff $(git merge-base origin/${process.env.TRIOS_BASE || 'feat/queen-supervisor'} origin/${s.branch})..origin/${s.branch}\` +as a statement of what was wanted. Then write it against the base as it stands. + +## User Scenarios & Testing + +### User Story 1 - The capability exists on today's base (P1) + +**Acceptance Scenarios**: +1. **Given** the base as it stands, **When** the change is applied, + **Then** the names below exist and are exercised by a test. +2. **Given** the same base, **When** the test suite runs, + **Then** nothing that passed before fails. + +## Requirements + +- **FR-001**: The capability MUST be implemented against the CURRENT base, not + copied from \`origin/${s.branch}\`. +- **FR-002**: Every name in the criteria below MUST exist in the tree afterwards. +- **FR-003**: Source files MUST be ASCII-only (L3). The old branch violates this + in at least one sibling; do not inherit it. + +## Success Criteria + +${shown.map((id) => `- The tree defines \`${id}\`; that identifier appears nowhere in the tree today.`).join('\n')} +- \`bun test ${s.files.find((f) => /test/.test(f)) || ''}\` exits 0, and its raw stdout is quoted in the report. +- \`LC_ALL=C grep -cP '[^\\x00-\\x7F]' \` prints 0, and the raw output is quoted. + +## Boundary + +${s.files.map((f) => `\`${f}\``).join('\n')} +` +} + +export function render(s) { + if (!s) return ' no fork point - this branch cannot be compared with the base' + const out = [] + out.push(` ${s.files.length} file(s), ${s.addedLines} added line(s), fork ${s.fork.slice(0, 9)}`) + for (const f of s.files) { + const moved = sh(`git rev-list --count ${s.fork}..origin/${BASE} -- ${JSON.stringify(f)}`) + out.push(` ${f} ${Number(moved) ? `base moved ${moved} commit(s) since the fork` : 'base untouched since the fork'}`) + } + out.push('') + if (!s.missing.length) { + out.push(` every one of the ${s.examined} name(s) this branch introduces is ALREADY in the base.`) + out.push(' Nothing here is a missing capability. Close it as superseded rather than rebasing it.') + return out.join('\n') + } + out.push(` ${s.missing.length} name(s) of ${s.examined} examined (${s.candidates} candidates) are absent from the base:`) + for (const m of s.missing.slice(0, 14)) out.push(` ${String(m.n).padStart(4)}x ${m.id}`) + if (s.missing.length > 14) out.push(` ... and ${s.missing.length - 14} more`) + out.push('') + out.push(' These are a SPECIFICATION OF INTENT, not a patch. Each is a criterion a brief') + out.push(' can state and verdict-audit can run: absent at the fork point, present on the') + out.push(' branch, which is a fail-to-pass check by construction.') + out.push('') + out.push(' A clean rebase would not have proved this branch correct either: a semantic') + out.push(' conflict carries no markers, so "it applied" is not evidence.') + return out.join('\n') +} + +if (isMain) { + let names = process.argv.slice(2).filter((a) => !a.startsWith('--')) + .map((a) => (/^\d+$/.test(a) ? `queen-${a}` : a)) + + if (process.argv.includes('--conflicting')) { + const LAND = await import(path.join(DIR, 'land.mjs')) + const rows = await LAND.survey() + names = rows + .filter((r) => r.state === 'conflict' || /conflict/i.test(r.why || '')) + .map((r) => r.branch) + } + + if (!names.length) { + console.log('usage: salvage.mjs [...] | --conflicting') + process.exit(1) + } + + const CAP = Number(process.env.SALVAGE_CAP || 400) + for (const n of names) { + const s = surviving(n, { cap: CAP }) + if (process.argv.includes('--brief')) { + if (!s || !s.missing.length) { + console.log(`# ${n}: nothing absent from the base - close as superseded rather than re-filing`) + continue + } + console.log(brief(s)) + continue + } + console.log(`\n──── ${n}`) + console.log(render(s)) + } +} diff --git a/trios/.trinity/loop/selftest.mjs b/trios/.trinity/loop/selftest.mjs index f1b12e2eeb..2c960769d9 100644 --- a/trios/.trinity/loop/selftest.mjs +++ b/trios/.trinity/loop/selftest.mjs @@ -1922,6 +1922,58 @@ check('the reaper does not promise megabytes it cannot return', () => { if (/freeing about \$\{freed\} MB/.test(code)) throw new Error('"freeing about N MB" was a promise the tool could not keep') }) +check('salvage proposes names, not tokens, and never a hex fixture', async () => { + const { candidates, NOISE } = await import('./salvage.mjs') + const c = candidates([ + '+const quotaAuthority = readTelemetry()', + '+ const sha = "fedcba9876"', + '+export function quotaAuthority() {}', + ]) + const ids = c.map((x) => x.id) + if (!ids.includes('quotaAuthority')) throw new Error('a name the branch introduces is the point') + if (c.find((x) => x.id === 'quotaAuthority').n !== 2) throw new Error('frequency orders the candidates') + if (ids.includes('fedcba9876')) throw new Error('a hex blob is a fixture, not a capability the repository lacks') + if (ids.includes('const') || ids.includes('export')) throw new Error('a keyword is not a contribution') + if (!NOISE.has('describe')) throw new Error('test scaffolding is noise too') +}) + +check('salvage measures absence in the tree, in any language', async () => { + const { inTree, surviving } = await import('./salvage.mjs') + // A Swift enum case, an object-literal method and a function reached by + // import are all "defined" differently and all present in the tree the same + // way. Absence needs no parser. + const run = (cmd) => (cmd.includes('connectionFailed') ? 'a/b/Transport.swift' : null) + if (!inTree('ref', 'connectionFailed', run)) throw new Error('present is present') + if (inTree('ref', 'neverWritten', run)) throw new Error('absent is absent') + if (inTree('ref', 'not-an-identifier', run)) throw new Error('only an identifier is looked up as one') + // No fork point means no comparison, and no answer is better than a wrong one. + if (surviving('nope', { run: () => null }) !== null) throw new Error('a branch with no fork point cannot be compared') +}) + +check('salvage refuses to call itself a patch', () => { + const code = codeOf('salvage.mjs') + // Rebasing #1302 would have deleted #1308's landed work and reintroduced a + // non-ASCII character into a file governed by L3 - and a clean rebase would + // not have shown either, because a semantic conflict carries no markers. + if (/git rebase|cherry-pick|git apply/.test(code)) throw new Error('this tool must never apply anything') + if (!/SPECIFICATION OF INTENT/.test(code)) throw new Error('the old branch is a statement of what was wanted, not a patch') + if (!/semantic conflict/i.test(code)) throw new Error('"it applied cleanly" is not evidence and the file must say so') +}) + +check('a generated brief is fail-to-pass by construction', async () => { + const { brief } = await import('./salvage.mjs') + const out = brief({ + branch: 'queen-1', files: ['a/b.ts', 'a/b.test.ts'], addedLines: 10, + candidates: 20, examined: 20, missing: [{ id: 'thingOne', n: 3 }], + }) + if (!/appears nowhere in the tree today/.test(out)) { + throw new Error('the criterion must assert absence, which is what makes it fail at the fork point') + } + if (!/## Boundary/.test(out) || !/## Success Criteria/.test(out)) throw new Error('the delegation gate demands both sections') + if (!/ASCII-only/.test(out)) throw new Error('L3 is the law the sibling branch broke; do not inherit it') + if (/copied from/.test(out) && !/not\s+copied from/.test(out)) throw new Error('the brief must forbid copying the old file') +}) + 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.