From 20c29442e8ad45331f3f56442478ce824e6914c5 Mon Sep 17 00:00:00 2001 From: Doxa Maps Date: Sat, 26 Sep 2026 07:58:06 +0000 Subject: [PATCH] Landing precondition 0: no file in the commit, public/ included, may hold a merge-conflict marker d6bf410a (a merge) committed conflict markers into 10 built files under public/ (big-picture, dashboard-plus-grid, research-map, research-dashboard, both manifests) and every precondition passed: a built bundle with markers throws on load while its SRI hash still matches. Step 0 now scans the COMMIT's tree with git grep (1.3 s, every file, whatever the working tree holds) and refuses. Run on d6bf410a it names exactly those 10 files; --markers-self-test builds a throwaway commit object holding a textbook conflict under public/ and requires the scan to refuse it (PASS). --- internal/landing-preconditions.mjs | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/internal/landing-preconditions.mjs b/internal/landing-preconditions.mjs index 805838172..96c5cea91 100644 --- a/internal/landing-preconditions.mjs +++ b/internal/landing-preconditions.mjs @@ -11,6 +11,9 @@ * Every one of those landings passed its own checks. Nothing refused them. * * PRECONDITIONS, in order (cheap first): + * 0. NO MERGE MARKERS no file in the COMMIT (public/ included) holds a conflict marker. d6bf410a put them + * into 10 built files under public/ and every other precondition passed; a built bundle with + * markers throws on load while its SRI still matches. --markers-self-test proves the scan refuses. * 1. CLEAN the working tree equals HEAD for everything the checks read — otherwise they would judge * files that are not in the commit. * 2. ONE FOLDER public/ holds exactly the configured tree (vite.doxa-maps-build-config.json "name") and @@ -20,6 +23,7 @@ * 4. FRESH every composite rebuilds to exactly its committed bytes (check:composite-freshness). * * USAGE node internal/landing-preconditions.mjs exit 0 = may land · 1 = refused + * node internal/landing-preconditions.mjs --markers-self-test proves step 0 refuses a conflict * DOXA_SKIP_FRESHNESS=1 skips (4) — for a DRY check only; main-capture scan never sets it. */ import { spawnSync } from 'node:child_process' @@ -36,6 +40,30 @@ const refuse = (why, detail = '') => { } const run = (cmd, args) => spawnSync(cmd, args, { cwd: ROOT, encoding: 'utf8', env: process.env }) +// 0. NO MERGE MARKERS — scanned in the COMMIT's tree (git grep), so public/ and every file is covered, +// whatever the working tree holds. Markers are built from char codes so this file never contains one. +const OPEN = String.fromCharCode(60).repeat(7), MID = String.fromCharCode(61).repeat(7), CLOSE = String.fromCharCode(62).repeat(7) +function markerFiles(ref) { + const g = run('git', ['grep', '-I', '-l', '-E', '-e', `^(${OPEN}( |$)|${MID}$|${CLOSE}( |$))`, ref, '--']) + if (g.status > 1) refuse('could not scan the commit for merge markers', g.stderr.trim()) + return g.stdout.split('\n').filter(Boolean).map((l) => l.replace(new RegExp('^' + ref + ':'), '')) +} +if (process.argv.includes('--markers-self-test')) { + // A throwaway commit object (no branch, no working-tree change) holding one built file with a textbook + // conflict; the scan must name it. + const blob = spawnSync('git', ['hash-object', '-w', '--stdin'], { cwd: ROOT, encoding: 'utf8', input: `a\n${OPEN} HEAD\nmine\n${MID}\ntheirs\n${CLOSE} other\n` }).stdout.trim() + const tree = spawnSync('git', ['mktree'], { cwd: ROOT, encoding: 'utf8', input: `100644 blob ${blob}\tpoison.js\n` }).stdout.trim() + const pub = spawnSync('git', ['mktree'], { cwd: ROOT, encoding: 'utf8', input: `040000 tree ${tree}\tpublic\n` }).stdout.trim() + const commit = spawnSync('git', ['commit-tree', pub, '-m', 'marker self-test'], { cwd: ROOT, encoding: 'utf8' }).stdout.trim() + const hits = markerFiles(commit) + if (hits.includes('public/poison.js')) { console.log('LANDING-PRECONDITIONS: markers self-test PASS — a conflict in a public/ file is refused.'); process.exit(0) } + console.error('LANDING-PRECONDITIONS: markers self-test FAIL — a textbook conflict under public/ was not found.'); process.exit(1) +} +const markers = markerFiles('HEAD') +if (markers.length) refuse(`${markers.length} file(s) in this commit hold merge-conflict markers`, + markers.slice(0, 10).join('\n') + '\nFor a BUILT file: take either side, REBUILD it, commit the rebuild. Never `git add -A public` mid-merge.') +console.log('LANDING-PRECONDITIONS: 0 markers — no file in the commit holds a merge-conflict marker') + // 1. CLEAN const dirty = run('git', ['status', '--porcelain', '--', 'public', 'plugin-designs', 'library', 'internal', 'vite.config.js', 'vite.doxa-maps-build-config.json', 'package.json']) if (dirty.status !== 0) refuse('could not read git status')