oxedyne/daimond/dev/verify_harness.mjs
13.4 KiB, 1 run
created by r2519314175:477, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | // verify_harness.mjs — the harness's own rules about where a browser is allowed |
| 2 | // to paint, put to it without launching one. |
| 3 | // |
| 4 | // `dev/harness.mjs` has named this file since `displayFault` was made "separate |
| 5 | // and pure so `dev/verify_harness.mjs` can put the cases to it". The file did not |
| 6 | // exist. Nothing in the tree tested that function, and on 2026-08-24 it was |
| 7 | // measured wrong -- it refused a forwarded `DISPLAY`, correctly, and had nothing |
| 8 | // to say about `WAYLAND_DISPLAY`, so a verifier started under `xvfb-run` opened a |
| 9 | // real window on the owner's desktop while he was working. A doc comment naming a |
| 10 | // test is not a test. |
| 11 | // |
| 12 | // Nothing here starts a browser, a server or a model. It imports the two pure |
| 13 | // functions and puts environments to them, so it costs a second and can be run |
| 14 | // before anything headed. |
| 15 | // |
| 16 | // node dev/verify_harness.mjs |
| 17 | // node dev/verify_harness.mjs --prove # and show each check failing against |
| 18 | // # the code as it stood before the fix |
| 19 | // node dev/verify_harness.mjs --break oldguard # 1: display.mjs before it could |
| 20 | // # tell an unattended run from a watched one |
| 21 | // |
| 22 | // ── The break, and why it is a patched copy of the module ──────────── |
| 23 | // |
| 24 | // `--prove` above is a paraphrase of the old function written out by hand, and a |
| 25 | // paraphrase proves that the paraphrase disagrees. The break below is not: it |
| 26 | // reads `dev/display.mjs` off the disk, cuts the unattended rule out of it, writes |
| 27 | // the result to the scratch directory and imports THAT. So the red is the module |
| 28 | // as it stood, in the layer the change was made in -- which is the standard the |
| 29 | // rest of this tree's breaks are held to, and this file was the exception. |
| 30 | // |
| 31 | // It can be done here and almost nowhere else because `display.mjs` has no imports |
| 32 | // at all, so a copy of it anywhere on the disk is the same module. That property |
| 33 | // is stated at the top of that file for a different reason and pays twice. |
| 34 | |
| 35 | import path from 'node:path'; |
| 36 | import fs from 'node:fs'; |
| 37 | import os from 'node:os'; |
| 38 | import { pathToFileURL } from 'node:url'; |
| 39 | |
| 40 | import * as GUARD from './harness.mjs'; |
| 41 | |
| 42 | const { cleanDisplayEnv, WAYLAND_VARS } = GUARD; |
| 43 | |
| 44 | const PROVE = process.argv.includes('--prove'); |
| 45 | |
| 46 | /// The one break this file declares, or `''` for the clean run. |
| 47 | const BREAK = (() => { |
| 48 | const i = process.argv.indexOf('--break'); |
| 49 | return i >= 0 && process.argv[i + 1] ? String(process.argv[i + 1]) : ''; |
| 50 | })(); |
| 51 | if (BREAK && BREAK !== 'oldguard') { |
| 52 | console.log(`no such break: ${BREAK}. This file declares: oldguard.`); |
| 53 | process.exit(1); |
| 54 | } |
| 55 | |
| 56 | /// The two lines inside `displayFault` that ask the question at all. |
| 57 | /// |
| 58 | /// A marker rather than a line number: the file is edited by hand and a number |
| 59 | /// would rot into a break that silently removed the wrong thing -- or nothing, |
| 60 | /// which is the failure `normalise()` in the hand calls the most damning. |
| 61 | const CALL_MARK = '\tconst nobody = unattendedFault(e);'; |
| 62 | |
| 63 | /// The banner the rule and the three names it is spelled with all sit under. |
| 64 | const BLOCK_MARK = '// │ A display nobody is looking at, and the seat that is │'; |
| 65 | |
| 66 | /// The last line of that block, so the cut ends where the old file ended. |
| 67 | const BLOCK_END = '\nexport function unattendedFault(e) {'; |
| 68 | |
| 69 | /// `dev/display.mjs` as it stood before the unattended rule, as a module. |
| 70 | /// |
| 71 | /// BOTH halves go -- the rule and its three names, and the two lines in |
| 72 | /// `displayFault` that call it -- because the file before this change had neither, |
| 73 | /// and a break that left the names behind would leave one check unreddenable and |
| 74 | /// therefore unmeasured. What is left is byte for byte the old `displayFault`: |
| 75 | /// nothing is paraphrased, which is what `--prove` below does and why it is not |
| 76 | /// enough on its own. Refused if either cut changes nothing, so a break that has |
| 77 | /// stopped reproducing anything says so rather than passing quietly. |
| 78 | async function guardBefore() { |
| 79 | const src = fs.readFileSync(path.join(HERE, 'display.mjs'), 'utf8'); |
| 80 | let cut = src; |
| 81 | const at = cut.indexOf(CALL_MARK); |
| 82 | if (at < 0) { |
| 83 | throw new Error('the call to unattendedFault was not found in dev/display.mjs, so ' |
| 84 | + 'this break reproduces nothing. Fix CALL_MARK here.'); |
| 85 | } |
| 86 | // Back over the comment the call sits under, and forward past the `if`. |
| 87 | const top = cut.lastIndexOf('\t// UNATTENDED FIRST', at); |
| 88 | const after = cut.indexOf('\n', cut.indexOf('if (nobody) return nobody;', at)) + 1; |
| 89 | cut = cut.slice(0, top < 0 ? at : top) + cut.slice(after); |
| 90 | const nb = cut.indexOf(BLOCK_MARK); |
| 91 | const ne = cut.indexOf(BLOCK_END); |
| 92 | if (nb < 0 || ne < nb) { |
| 93 | throw new Error('the unattended block was not found in dev/display.mjs; fix ' |
| 94 | + 'BLOCK_MARK and BLOCK_END here.'); |
| 95 | } |
| 96 | // Back to the top of the box-drawn banner, and forward to the end of the |
| 97 | // function the banner introduces. |
| 98 | const bt = cut.lastIndexOf('// ┌', nb); |
| 99 | const be = cut.indexOf('\n}\n', ne) + 4; // and the blank line after it |
| 100 | cut = cut.slice(0, bt < 0 ? nb : bt) + cut.slice(be); |
| 101 | if (cut === src) throw new Error('the cut changed nothing.'); |
| 102 | if (cut.includes('UNATTENDED_VAR')) { |
| 103 | throw new Error('the cut left the rule behind, so this break reproduces nothing.'); |
| 104 | } |
| 105 | const dir = path.join(process.env.DAIMOND_SCRATCH |
| 106 | || path.join(os.homedir(), '.cache/daimond'), 'harness-break'); |
| 107 | fs.mkdirSync(dir, { recursive: true }); |
| 108 | const f = path.join(dir, `display.before.${process.pid}.mjs`); |
| 109 | fs.writeFileSync(f, cut); |
| 110 | const m = await import(pathToFileURL(f).href); |
| 111 | fs.rmSync(f, { force: true }); |
| 112 | return m; |
| 113 | } |
| 114 | |
| 115 | const HERE = path.dirname(new URL(import.meta.url).pathname); |
| 116 | |
| 117 | /// The module the checks below are put to: this tree's, or this tree's as it stood. |
| 118 | const UNDER = BREAK === 'oldguard' ? await guardBefore() : GUARD; |
| 119 | const displayFault = UNDER.displayFault; |
| 120 | const { UNATTENDED_VAR, OWNED_VAR, SEAT_DISPLAY } = UNDER; |
| 121 | if (BREAK) { |
| 122 | console.log(`\n── BREAK ${BREAK}: dev/display.mjs with the unattended rule cut out ──`); |
| 123 | } |
| 124 | |
| 125 | let ok = 0, bad = 0; |
| 126 | |
| 127 | /// One check, named as a property rather than as a step. |
| 128 | function check(name, pass, detail) { |
| 129 | if (pass) { ok++; console.log(` ok ${name}${detail ? ` — ${detail}` : ''}`); } |
| 130 | else { bad++; console.log(` FAIL ${name}${detail ? ` — ${detail}` : ''}`); } |
| 131 | } |
| 132 | |
| 133 | // ── The seat this machine actually has ────────────────────────── |
| 134 | // |
| 135 | // Every case below is spelled the way argonaut spells it, because that is the |
| 136 | // machine the fault happened on: a Wayland session, `WAYLAND_DISPLAY=wayland-0` |
| 137 | // and `XDG_SESSION_TYPE=wayland` in every rc session, and `xvfb-run` adding a |
| 138 | // `DISPLAY` beside them rather than instead of them. |
| 139 | const UNDER_XVFB = { DISPLAY: ':99', WAYLAND_DISPLAY: 'wayland-0', XDG_SESSION_TYPE: 'wayland' }; |
| 140 | const OWN_SEAT = { DISPLAY: ':0', WAYLAND_DISPLAY: 'wayland-0', XDG_SESSION_TYPE: 'wayland' }; |
| 141 | const FORWARDED = { DISPLAY: 'localhost:10.0' }; |
| 142 | const NOTHING = {}; |
| 143 | const WAYLAND_ONLY = { WAYLAND_DISPLAY: 'wayland-0', XDG_SESSION_TYPE: 'wayland' }; |
| 144 | |
| 145 | console.log('\n── What is taken out of a launch environment ─────────'); |
| 146 | |
| 147 | { |
| 148 | const cleaned = cleanDisplayEnv(UNDER_XVFB); |
| 149 | check('the Wayland variables are taken out, so Chromium takes the X path', |
| 150 | WAYLAND_VARS.every((v) => cleaned[v] === undefined), |
| 151 | JSON.stringify(cleaned)); |
| 152 | check('and the display xvfb provided is left alone, or there is nowhere to paint', |
| 153 | cleaned.DISPLAY === ':99'); |
| 154 | } |
| 155 | |
| 156 | { |
| 157 | // The variable Chromium needs for reasons that have nothing to do with the |
| 158 | // screen. Taking it out breaks a headless run, so a check says it is kept. |
| 159 | const cleaned = cleanDisplayEnv({ ...UNDER_XVFB, XDG_RUNTIME_DIR: '/run/user/1000' }); |
| 160 | check('XDG_RUNTIME_DIR survives, since it is not about the display at all', |
| 161 | cleaned.XDG_RUNTIME_DIR === '/run/user/1000'); |
| 162 | } |
| 163 | |
| 164 | { |
| 165 | const src = { ...UNDER_XVFB }; |
| 166 | cleanDisplayEnv(src); |
| 167 | check('the environment handed in is not edited, only the copy handed back', |
| 168 | src.WAYLAND_DISPLAY === 'wayland-0'); |
| 169 | } |
| 170 | |
| 171 | console.log('\n── What a headed run is refused ──────────────────────'); |
| 172 | |
| 173 | check('a run under xvfb is allowed, Wayland variables and all', |
| 174 | displayFault(UNDER_XVFB) === null, JSON.stringify(displayFault(UNDER_XVFB))); |
| 175 | |
| 176 | check('argonaut\'s own seat is allowed, because watching a run is a thing people do', |
| 177 | displayFault(OWN_SEAT) === null); |
| 178 | |
| 179 | { |
| 180 | const said = displayFault(FORWARDED); |
| 181 | check('a display forwarded from another machine is refused, and named', |
| 182 | typeof said === 'string' && /localhost/.test(said) && /somebody else/.test(said)); |
| 183 | } |
| 184 | |
| 185 | { |
| 186 | // The case the fix is for: a headed run with no xvfb on a Wayland seat. Before |
| 187 | // the fix this returned the bare "DISPLAY is unset" sentence, which is true and |
| 188 | // says nothing about the compositor the browser would have found instead. |
| 189 | const said = displayFault(WAYLAND_ONLY); |
| 190 | check('a Wayland seat with no X display is refused, and the refusal says why', |
| 191 | typeof said === 'string' && /WAYLAND_DISPLAY/.test(said) && /owner's own screen/.test(said), |
| 192 | JSON.stringify(said)); |
| 193 | } |
| 194 | |
| 195 | check('an environment with no display at all is refused', |
| 196 | typeof displayFault(NOTHING) === 'string'); |
| 197 | |
| 198 | check('a bare DISPLAY string is still read the way it always was', |
| 199 | displayFault(':99') === null && typeof displayFault('gilgamesh:0') === 'string'); |
| 200 | |
| 201 | check('and so is nothing at all, rather than throwing', |
| 202 | typeof displayFault(undefined) === 'string'); |
| 203 | |
| 204 | console.log('\n── A display nobody is looking at, and the seat that is ─'); |
| 205 | |
| 206 | // B13: a daimon reaches a headed instrument through `verify`, which runs a tracked |
| 207 | // script OUTSIDE the command fence -- so the script inherits the hand's own |
| 208 | // environment. Start the hand from a desktop session and that environment carries |
| 209 | // `DISPLAY=:0`, and the check above says yes to it, correctly, because a person |
| 210 | // watching their own run is the case it was written for. Nobody is watching this one. |
| 211 | const UNATT = { DAIMOND_UNATTENDED: '1', DAIMOND_OWNED_DISPLAY: ':99' }; |
| 212 | |
| 213 | { |
| 214 | const said = displayFault({ ...OWN_SEAT, ...UNATT }); |
| 215 | check('an unattended run is refused the seat, which an attended one is given', |
| 216 | typeof said === 'string' && said.includes('OWN SEAT'), |
| 217 | JSON.stringify(said)); |
| 218 | } |
| 219 | |
| 220 | check('and the same environment without the mark is still allowed, so watching still works', |
| 221 | displayFault(OWN_SEAT) === null); |
| 222 | |
| 223 | check('an unattended run on the display it started for itself is allowed', |
| 224 | displayFault({ ...UNDER_XVFB, ...UNATT }) === null, |
| 225 | JSON.stringify(displayFault({ ...UNDER_XVFB, ...UNATT }))); |
| 226 | |
| 227 | { |
| 228 | // The ordinary way a wrong display arrives: inherited from whatever started the |
| 229 | // process, never chosen. `:98` is somebody else's xvfb, which is not this run's |
| 230 | // to paint on either. |
| 231 | const said = displayFault({ DISPLAY: ':98', ...UNATT }); |
| 232 | check('an unattended run on a display it did not start is refused', |
| 233 | typeof said === 'string' && said.includes(':98') && said.includes(':99'), |
| 234 | JSON.stringify(said)); |
| 235 | } |
| 236 | |
| 237 | { |
| 238 | const said = displayFault({ DISPLAY: ':99', DAIMOND_UNATTENDED: '1' }); |
| 239 | check('an unattended run that claims no display of its own is refused', |
| 240 | typeof said === 'string' && said.includes(String(OWNED_VAR)), |
| 241 | JSON.stringify(said)); |
| 242 | } |
| 243 | |
| 244 | { |
| 245 | // A launcher that names the seat AS its own must not be believed. This is the |
| 246 | // belt beside the brace: the comparison above would pass a launcher whose two |
| 247 | // names agree, and agreeing on the wrong display is exactly the accident. |
| 248 | const said = displayFault({ DISPLAY: ':0', DAIMOND_UNATTENDED: '1', DAIMOND_OWNED_DISPLAY: ':0' }); |
| 249 | check('a launcher that claims the seat as its own display is refused anyway', |
| 250 | typeof said === 'string' && said.includes('OWN SEAT'), |
| 251 | JSON.stringify(said)); |
| 252 | } |
| 253 | |
| 254 | { |
| 255 | const said = displayFault({ ...FORWARDED, ...UNATT }); |
| 256 | check('a forwarded display is refused for an unattended run as it always was, and named', |
| 257 | typeof said === 'string' && /localhost/.test(said) && /somebody else/.test(said)); |
| 258 | } |
| 259 | |
| 260 | check('the two names and the seat are exported, so a launcher spells none of them itself', |
| 261 | UNATTENDED_VAR === 'DAIMOND_UNATTENDED' && OWNED_VAR === 'DAIMOND_OWNED_DISPLAY' |
| 262 | && SEAT_DISPLAY === ':0', |
| 263 | `${UNATTENDED_VAR} ${OWNED_VAR} ${SEAT_DISPLAY}`); |
| 264 | |
| 265 | // ── Proved against the code as it stood ───────────────────────── |
| 266 | // |
| 267 | // The old `displayFault` in one line: it saw a DISPLAY string and nothing else. |
| 268 | // Every check above that the fix is FOR must fail against it, and every check |
| 269 | // that was already right must still pass -- otherwise the fix is doing something |
| 270 | // other than what it says. |
| 271 | if (PROVE) { |
| 272 | console.log('\n── The same cases, against displayFault as it stood ───'); |
| 273 | const before = (display) => { |
| 274 | const d = (display || '').trim(); |
| 275 | if (!d) return 'A headed run needs a display and DISPLAY is unset.'; |
| 276 | const host = d.slice(0, d.indexOf(':') < 0 ? d.length : d.indexOf(':')); |
| 277 | if (host) return `DISPLAY is "${d}" ... on somebody else's screen`; |
| 278 | return null; |
| 279 | }; |
| 280 | // It was handed `env.DISPLAY`, so that is what it is handed here. |
| 281 | const said = before(WAYLAND_ONLY.DISPLAY); |
| 282 | console.log(` ${/WAYLAND_DISPLAY/.test(String(said)) ? 'ok ' : 'FAIL'} a Wayland seat with no X display is refused, and the refusal says why` |
| 283 | + ` — ${JSON.stringify(said)}`); |
| 284 | console.log(` ${typeof before !== 'function' ? 'ok ' : 'FAIL'} the Wayland variables are taken out, so Chromium takes the X path` |
| 285 | + ' — there was no such function to call'); |
| 286 | console.log(` ok a display forwarded from another machine is refused, and named` |
| 287 | + ' — this half was always right, and is unmoved'); |
| 288 | } |
| 289 | |
| 290 | console.log(`\n${ok} ok, ${bad} failed.`); |
| 291 | if (bad) { console.log('failed checks above.'); process.exit(1); } |