diff --git a/scripts/upstream-port-pin.json b/scripts/upstream-port-pin.json index 276c871cb5..2ea9063b40 100644 --- a/scripts/upstream-port-pin.json +++ b/scripts/upstream-port-pin.json @@ -158,6 +158,146 @@ "ported": "# to stop (#7259). The pair of DEPTHS is what makes the section discriminating, and the\n" } ] + }, + { + "ported": "scripts/check-bash32-floor.mjs", + "upstreamPath": "scripts/check-bash32-floor.mjs", + "ref": "61362932b5ad4c85b39169e70cf9be64d4332ce5", + "upstreamSha256": "0da45049c680c2b2d58d52d637f17f7c45421a46a92a8dd0e6385dadb384dffa", + "divergences": [ + { + "id": "header-card-and-ported-banner", + "why": "The header names this repository's card and carries the PORTED provenance banner; upstream's names upstream's. ⚠️ Its \"Deliberately NOT registered\" clause is stale the moment this entry lands — correcting it edits the ported file and is a follow-up card, not a silent fix here.", + "upstream": "/**\n * The bash-3.2 floor, repo-wide (#12221).\n *\n", + "ported": "/**\n * The bash-3.2 floor, repo-wide (objectui#7692).\n *\n * ⚠️ PORTED from objectstack-ai/objectstack `scripts/check-bash32-floor.mjs` at\n * commit `6136293`, which is where the class, the construct table and the\n * exemption rules were worked out. This header states which claims are\n * upstream's and which were re-measured HERE; ⛔ do not read an inherited\n * measurement as a reading about this tree. Deliberately NOT registered in\n * `scripts/upstream-port-pin.json` — the reason is at \"## Why this port is not\n * pinned\" below.\n *\n" + }, + { + "id": "incidents-are-upstreams-and-local-coverage-census", + "why": "The two incidents that established the class are UPSTREAM's, and this repository's own pre-gate coverage was re-measured here (zero, each zero with a live control) instead of inheriting upstream's sentence.", + "upstream": " *\n * A sweep found and fixed all four. Nothing stopped a fifth being typed. Today's\n * coverage before this gate was two FILE-SCOPED scans living inside two\n * unrelated gates' self-tests — which is the arrangement that let the class\n * survive in the first place.\n *\n", + "ported": " *\n * Both incidents are UPSTREAM's, and they are what established the class. A\n * sweep found and fixed all four sites there. Nothing stopped a fifth being\n * typed, in either repository.\n *\n * ## The coverage this repository had before this gate: none\n *\n * Measured on `origin/main` `28cfff4`, with a live control beside each zero —\n * a zero taken with no control is not a reading:\n *\n * - tracked files whose name carries `bash32` / `bash-32` / `bash3`: 0.\n * Control, same listing: 47 `scripts/check-*.mjs` gates exist. So the\n * absence is the tree's, not the grep's.\n * - the two neighbours that look closest are neither, by their own verdicts:\n * `check-control-bytes` — \"scanned 6443 tracked text file(s); skipped 85\n * binary\". It judges BYTES, in every text file, and says nothing about\n * shell grammar.\n * `check-shell-escape-residue` — \"206 file(s) and 1309 fenced block(s)\n * examined in total\". Its population is markdown FENCES in `AGENTS.md`,\n * `CLAUDE.md`, `skills`, `.claude/skills` and `content/docs`: prose\n * about shell, not shell programs.\n *\n * Upstream additionally retains two FILE-SCOPED simulated runs that this\n * repository does not have (`enable -n mapfile readarray` via `BASH_ENV`, plus\n * `unset` of the bash-5 variables). That difference matters for what the\n * \"Known limit\" section below can promise, and it is stated there rather than\n * left to be discovered.\n *\n" + }, + { + "id": "what-this-gate-is-not-no-simulated-runs-here", + "why": "Upstream keeps two FILE-SCOPED simulated runs beside this scan and forbids retiring them; this repository has no such pair, so the same sentence has to be read forwards and a green verdict here carries less than upstream's.", + "upstream": " *\n * ⛔ It does not supersede those two scans and must not be used to retire them.\n * A static scan sees parse-level constructs a simulated run cannot reach; a\n * simulated run (`enable -n mapfile readarray` via `BASH_ENV`, plus `unset` of\n * the bash-5 variables) proves the real code path COMPLETES without them, and\n * reaches constructs assembled at runtime that no static scan can see. They\n * catch different things. This gate is the repo-wide third leg — defence in\n * depth over the class, at the price of a regex — and the blind spot named\n * under \"Known limit\" below is exactly the half the simulation harness holds.\n *\n", + "ported": " *\n * ⛔ Upstream carries this scan as the THIRD leg beside those two simulated\n * runs, and its header forbids using it to retire them. Here there is no such\n * pair to retire — so the same sentence has to be read forwards instead: a\n * static scan sees parse-level constructs a simulated run cannot reach, and a\n * simulated run reaches constructs assembled at runtime that no static scan can\n * see. This repository now holds the first half only. ⛔ Do not read a green\n * verdict here as the coverage upstream's green verdict carries.\n *\n" + }, + { + "id": "exemption-rule-remeasured-zero-suppressions", + "why": "Upstream's 8 false positives are upstream's measurement. Re-measured here the exemptions suppress nothing today; carrying the number across would have been inventing a reading about this tree.", + "upstream": " * The hard part of a repo-wide scan is that the files which document this floor\n * have to NAME the constructs they refuse in order to explain why, and the\n * lock's own self-test has to name them in order to hunt them. A scan that\n * cannot tell a mention from a use reddens itself on day one. Measured on this\n * tree with the tree's existing rule (full-line comments exempt, everything\n * else a use): 8 findings, every one of them a false positive, all in one file.\n *\n", + "ported": " * The hard part of a repo-wide scan is that the files which document this floor\n * have to NAME the constructs they refuse in order to explain why, and a\n * self-test hunting these tokens has to name them in order to hunt them. A scan\n * that cannot tell a mention from a use reddens itself on day one. Upstream met\n * exactly that: 8 findings there, every one a false positive, all in one file.\n *\n * ⚠️ Re-measured HERE, and the answer is different — recorded because carrying\n * upstream's number across would be inventing a reading. Over the 12 shell\n * files in this population, with E1 (full-line comments) AND E3 (command\n * position) both switched OFF, the 19 patterns match on **0 lines**. So on this\n * tree today the exemptions suppress NOTHING, and none of them is load-bearing\n * yet. They are carried anyway, and that is a decision rather than inertia:\n * the doctrine this repository is acquiring — this file, its wiring comment in\n * `.github/workflows/lint.yml`, and any hook that later refuses one of these\n * tokens — has to name the constructs in order to refuse them, which is the\n * arrangement that produced upstream's 8. An exemption rule invented on the day\n * it is first needed is invented under pressure to make a red gate green.\n *\n" + }, + { + "id": "dynamic-construct-hole-is-uncovered-here", + "why": "Upstream can call the dynamically-built-construct hole \"precisely what the retained SIMULATED runs cover\"; this repository retains none, so here the hole is simply UNCOVERED — a smaller claim than upstream's identical-looking sentence.", + "upstream": " * parser, and widening E3 instead would re-red the tree on the very files that\n * hunt these tokens. The hole is precisely what the retained SIMULATED runs\n * cover: they execute the real path with the builtin disabled, so a dynamically\n * built `mapfile` fails there and nowhere else. Two instruments, one class.\n *\n", + "ported": " * parser, and widening E3 instead would re-red the tree on the very files that\n * hunt these tokens.\n *\n * ⚠️ Upstream can say the hole is \"precisely what the retained SIMULATED runs\n * cover\" — it keeps two of them. THIS repository has none, so here the hole is\n * simply UNCOVERED, and that is a smaller claim than upstream's identical-\n * looking sentence. Said plainly rather than inherited: after this gate lands,\n * an `eval \"mapfile -t x < f\"` in this tree is refused by nothing. Closing it\n * is a separate card, not a line to loosen here.\n *\n" + }, + { + "id": "why-this-port-is-not-yet-pinned-section", + "why": "A port-local rationale section with no upstream counterpart. ⚠️ This entry is what makes it false; rewriting it would edit the ported file and change this divergence, so it is tracked as a follow-up card rather than folded in.", + "upstream": " *\n * ## Population\n", + "ported": " *\n * ## Why this port is not YET pinned in `scripts/upstream-port-pin.json`\n *\n * That ledger is how this repository stops a ported copy drifting into a\n * confident-but-stale report — `scripts/pm/check-half-states.mjs` reached a\n * 4,637-line diff from upstream while reporting greenly. The same argument\n * applies to this file with force: its whole subject is that \"an absence from a\n * denylist reads as an approval\", and upstream actively sweeps the table.\n *\n * It is still NOT registered, but the blocker that kept it out was structural\n * and is GONE. The pin used to carry ONE ledger-wide `upstream.ref` beside\n * per-file digests, and `--resync` rewrote that global field on every run. So\n * registering this port at the revision it was actually taken from meant one of\n * two bad trades: port from the older revision the ledger happened to name —\n * deliberately shipping a WEAKER construct table so a provenance field stayed\n * true, which inverts the point of the gate — or drag every other pinned file\n * to a new ref, an unrelated rewrite of other ported tooling. A third route,\n * pinning this file's digest while the global ref named a different revision,\n * was the one that must never be taken: the digest would verify and the\n * provenance line would be false.\n *\n * objectui#8288 retired the field and all three trades with it. Read off\n * `check-upstream-port-parity.mjs`: `ref` is now a REQUIRED key on each\n * `files[]` entry beside the digest it was taken with, `validatePin` REFUSES a\n * pin that still carries `upstream.ref`, and `resyncedPin` writes only the\n * re-synced entry's own `ref` and digest while returning every other entry\n * untouched. `upstream` keeps `repo` alone. Entries at different refs now\n * coexist by design, so this file can be registered at its own revision without\n * disturbing anything already pinned.\n *\n * ⛔ Do not write a revision into this prose. A port's ref lives on that port's\n * entry in `scripts/upstream-port-pin.json`; read it from there, where\n * `--resync` keeps it correct. This section has already gone stale TWICE by\n * naming one: objectui#7749 moved the global ref out from under the sentence\n * describing it, and objectui#8288 then deleted the field that sentence named.\n * Both times the prose stayed confident and wrong — which is this file's own\n * subject, aimed at itself.\n *\n * What registration still costs is per-file work rather than a schema change,\n * which is why it did not ride along with objectui#8288 and is a card of its\n * own: read the upstream blob at a named ref, compute its SHA-256, and declare\n * every divergence as an exact `upstream`/`ported` text pair with a stated\n * `why`. The same unblocking reaches the ported `.claude/hooks/**` files that\n * objectui#7953 recorded from the other direction — a per-file ref can name a\n * revision where each of them exists — with one extra cost there and not here:\n * those are GOVERNED surface, so `--resync` refuses to write them without\n * `--rewrite-governed-file`.\n *\n * Consequence, stated so it is inherited rather than rediscovered: until that\n * registration lands, this file has NO drift gate. Upstream improvements to\n * `CONSTRUCTS` arrive here only if someone carries them by hand. That cost was\n * once accepted because paying it meant weakening a gate; it is now simply\n * unpaid, and the work to pay it is ordinary.\n *\n * ## Population\n" + }, + { + "id": "population-shebang-census-is-zero-here", + "why": "Upstream measures 2 shebang-only scripts and calls the shebang half load-bearing; every shell file in this population is `.sh`, so the census is 0 here and upstream's sentence would be the false half of the port.", + "upstream": " * Tracked files under `POPULATION_ROOTS` that are shell: a `.sh` name, or a\n * `sh`/`bash`/`dash`/`ksh`/`zsh` shebang whatever the name. The shebang half is\n * not decoration — measured on this tree it adds `.githooks/pre-commit` and\n * `.githooks/pre-push`, two `#!/bin/sh` scripts a `*.sh` glob does not see and\n * whose floor is TIGHTER than bash 3.2, not looser. An extension-only census\n * would have called this population complete at 18 while running past both.\n *\n", + "ported": " * Tracked files under `POPULATION_ROOTS` that are shell: a `.sh` name, or a\n * `sh`/`bash`/`dash`/`ksh`/`zsh` shebang whatever the name.\n *\n * ⚠️ The shebang half's STATUS DIFFERS from upstream's, and inheriting\n * upstream's sentence would have been the false half of this port. Upstream\n * measures 2 shebang-only scripts (`.githooks/pre-commit`, `.githooks/pre-push`)\n * and calls the half load-bearing. Measured HERE on `origin/main` `28cfff4`:\n * every one of the 12 shell files in this population carries a `.sh` name, so\n * the shebang census is **0** and the half catches nothing TODAY. It is kept\n * because it is the half that would catch the first extension-less script\n * someone adds — and because dropping it would make a future re-sync with\n * upstream a semantic merge rather than a textual one. Its mechanism is\n * therefore pinned where it can actually be exercised: the end-to-end fixture\n * in `--self-test` plants a shebang-only script under a walked root and\n * requires the census to see it. ⛔ The real-tree leg must NOT assert\n * `byShebang > 0` here; upstream's does, and it would be red on day one.\n *\n" + }, + { + "id": "population-roots-docblock-e2e-root", + "why": "The roots docblock records the port's one substantive change (`e2e/**`) and separates upstream's reason for source literals — a watch hint no tool here reads — from the local one the self-test can actually check.", + "upstream": "/**\n * The population, declared as SUBTREE GLOBS — the one spelling that is both a\n * watch hint the dispatch derivation can read and a claim narrow enough to be\n * true.\n *\n * ⛔ These must stay SOURCE LITERALS. A root assembled at runtime\n * (`` `${r}/**` ``) builds no watch hint at all, which is the invisible half of\n * the bare-root species: a gate nameable by no dispatch brief, leaving no\n * residue saying so. And the glob form is what keeps this out of the\n * `escapable-literal` species too — the extractor admits any literal carrying a\n * separator, and refuses a bare single-segment word.\n *\n", + "ported": "/**\n * The population, declared as SUBTREE GLOBS.\n *\n * ⭐ `e2e/**` IS THE PORT'S ONE SUBSTANTIVE CHANGE, and it is the reason\n * objectui#7692 exists rather than being closed by a copy. Upstream declares\n * `scripts/**`, `.claude/hooks/**`, `.githooks/**`. This repository has no\n * `.githooks/` at all, and TWO of the four shell files it wrote itself —\n * `e2e/live/ci/start-backend.sh` and `e2e/live/ci/stop-backend.sh` — live under\n * `e2e/`. Copying upstream's roots verbatim would have walked past half of the\n * population this card is about while printing a confident green line, which is\n * the failure mode this whole file is built to refuse. ⛔ Do not \"restore\" the\n * upstream list.\n *\n * ⛔ These must stay SOURCE LITERALS in the glob form. Two independent reasons,\n * one of them upstream's and one of them local:\n *\n * - upstream's: a root assembled at runtime (`` `${r}/**` ``) builds no watch\n * hint for its dispatch derivation, and a bare single-segment word is\n * refused by its literal extractor. Neither tool runs in THIS repository —\n * said so it is not mistaken for a live local mechanism — but the shape is\n * kept so a re-sync stays textual.\n * - local, and load-bearing on its own: the shape is what `--self-test` can\n * actually check. A declaration it cannot read is a declaration nothing\n * pins.\n *\n" + }, + { + "id": "population-roots-literal", + "why": "This repository has no `.githooks/` and two of the four shell files it wrote itself live under `e2e/`; copying upstream's roots verbatim would walk past half the population while printing a confident green line.", + "upstream": " */\nexport const POPULATION_ROOTS = ['scripts/**', '.claude/hooks/**', '.githooks/**'];\n\n", + "ported": " */\nexport const POPULATION_ROOTS = ['scripts/**', '.claude/hooks/**', 'e2e/**'];\n\n" + }, + { + "id": "battery-roster-floors-and-host-premise-battery", + "why": "Two battery floors GREW because upstream's `byShebang > 0` leg is false here and the clean-tree fixture now asserts both census halves by number, plus objectui#8404's host-premise battery which upstream does not have.", + "upstream": " '⭐ the declaration, and the two obligations it makes unreachable': 5,\n '⭐ end to end, through the real discovery path': 5,\n '⭐ the instrument is real: the flagged construct really does break': 4,\n 'the real tree': 2,\n});\n", + "ported": " '⭐ the declaration, and the two obligations it makes unreachable': 5,\n // ⚠️ Two floors differ from upstream's, and the difference is the port:\n // end to end 5 -> 6 the clean-tree fixture now asserts BOTH census\n // halves by number instead of matching the word\n // \"shebang\", which the green line prints regardless.\n // the real tree 2 -> 3 upstream's `byShebang > 0` is false in this\n // repository (12 shell files, all `.sh`); it is\n // replaced by the partition and the really-read legs.\n // ⛔ Neither is a floor LOWERED: both batteries grew. A future re-sync that\n // restores upstream's numbers here will red, which is the intent.\n '⭐ end to end, through the real discovery path': 6,\n '⭐ the instrument is real: the flagged construct really does break': 4,\n 'the real tree': 3,\n // objectui#8404. Declared LAST because its third case reads `narrowed`, which\n // only has its final value once every host-dependent leg above has run.\n '⭐ objectui#8404: the self-test declares its own host premise': 5,\n});\n" + }, + { + "id": "battery-roster-size-floor", + "why": "The roster's own size floor follows the extra battery declared above it — deleting an entry silences a floor exactly as effectively as zeroing it.", + "upstream": "// zeroing it, so the roster's own size is pinned too.\nconst SELF_TEST_BATTERY_FLOOR = 17;\n\n", + "ported": "// zeroing it, so the roster's own size is pinned too.\nconst SELF_TEST_BATTERY_FLOOR = 18;\n\n" + }, + { + "id": "host-premise-probes-and-narrowing-helper", + "why": "objectui#8404: every bash-4 leg below drives the HOST's bash, and the host this gate exists for is bash 3.2, so the self-test detects the premise by capability probe and narrows those cases instead of failing. Upstream has no such host premise.", + "upstream": "\n console.log('check-bash32-floor --self-test\\n');\n", + "ported": "\n // ── objectui#8404: the self-test's own host premise, made explicit ───────\n //\n // ⭐ A floor gate whose battery cannot run ON the floor it declares is a gate\n // asserting something it has never demonstrated. That was this file: every\n // probe, harness and `bash -n` leg below drives a bash-4+ construct through\n // the HOST's bash, and the host this gate exists for is macOS, which ships\n // 3.2.57 and no bash 4+. There, `coproc`, `|&` and `&>>` are syntax errors\n // `bash -n` refuses, `mapfile` is not a builtin there is anything to disable,\n // and `[[ -v ]]` is a conditional operator `bash -n` also refuses (this\n // file's `has-v` row already records that `bash -n` judges `[[` operators).\n // So `--self-test` exited non-zero on the one host whose behaviour the whole\n // gate is about, and `bash32-floor-wiring.test.ts` reported it as a red test.\n //\n // ⛔ The production scan is NOT affected and is not narrowed: `scanText` is\n // pure JS, so `node scripts/check-bash32-floor.mjs` judges a 3.2 host exactly\n // as it judges CI. Only `--self-test` needs the newer parser.\n //\n // The premise is detected by CAPABILITY and never by platform name — a\n // `process.platform === 'darwin'` branch would red again on the next non-GNU\n // host, which is the very species this gate is about. Two probes, because the\n // legs below need two different things from the host: a parser that accepts\n // bash-4 GRAMMAR, and a runtime that has a bash-4 BUILTIN to disable.\n const HOST_PROBES = [\n // The canonical bash-4.0 operator. A shell at the floor answers with a\n // syntax error near the `&`.\n { need: 'grammar', probe: 'echo a |& cat', run: () => spawnSync('bash', ['-n'], { input: 'echo a |& cat\\n', encoding: 'utf8' }) },\n // The canonical bash-4.0 builtin. A shell at the floor answers 127.\n { need: 'builtin', probe: 'mapfile -t x < /dev/null', run: () => spawnSync('bash', ['-c', 'mapfile -t x < /dev/null'], { encoding: 'utf8' }) },\n ];\n const hostCan = Object.fromEntries(HOST_PROBES.map((h) => [h.need, h.run().status === 0]));\n const HOST_JUDGES_BASH4 = HOST_PROBES.every((h) => hostCan[h.need]);\n const NARROWING = HOST_PROBES.filter((h) => !hostCan[h.need]).map((h) => `\\`${h.probe}\\``).join(' and ');\n let narrowed = 0;\n\n /**\n * An assertion whose SUBJECT is a bash-4 construct run through the host bash.\n *\n * Where the host can judge bash-4, this is `t` unchanged — CI and every\n * bash-4+ host lose nothing. Where it cannot, the case still REGISTERS (so\n * its battery floor still holds and a battery that stops running still names\n * itself) but is reported as NARROWED rather than passed, and the verdict\n * line carries the count. ⛔ A narrowed case is a real coverage loss and is\n * printed as one: at the floor a typo and a bash-4 construct are the same\n * syntax error, so \"the probe is not a typo\" is genuinely unmeasurable there.\n */\n const tHostBash4 = (label, ok, detail = '') => {\n if (HOST_JUDGES_BASH4) {\n t(label, ok, detail);\n return;\n }\n registerCase();\n cases += 1;\n narrowed += 1;\n console.log(` \\u2298 ${label} — NARROWED: this host's bash refuses ${NARROWING}, so it is at or below the 3.2 floor this gate declares and cannot judge the case.`);\n };\n\n console.log('check-bash32-floor --self-test\\n');\n" + }, + { + "id": "constructs-probe-leg-narrowed", + "why": "The per-construct probe is parsed by the host bash, so at the 3.2 floor a bash-4 probe and a typo are the same syntax error; the leg narrows rather than reading as a false measurement.", + "upstream": " const parse = spawnSync('bash', ['-n'], { input: `${c.probe}\\n`, encoding: 'utf8' });\n t(`${c.id}: the probe is shell this host can parse`, parse.status === 0, (parse.stderr || '').trim());\n }\n", + "ported": " const parse = spawnSync('bash', ['-n'], { input: `${c.probe}\\n`, encoding: 'utf8' });\n tHostBash4(`${c.id}: the probe is shell a bash-4 parser accepts`, parse.status === 0, (parse.stderr || '').trim());\n }\n" + }, + { + "id": "has-v-parse-leg-narrowed", + "why": "Same reason as the constructs probe leg: `bash -n` at the floor refuses the `[[ -v ]]` operator this row is about, so the parse leg is unmeasurable there.", + "upstream": " const vParse = spawnSync('bash', ['-n'], { input: `${line}\\n`, encoding: 'utf8' });\n t(`\\`${label}\\` is shell this host can parse`, vParse.status === 0, (vParse.stderr || '').trim());\n }\n", + "ported": " const vParse = spawnSync('bash', ['-n'], { input: `${line}\\n`, encoding: 'utf8' });\n tHostBash4(`\\`${label}\\` is shell a bash-4 parser accepts`, vParse.status === 0, (vParse.stderr || '').trim());\n }\n" + }, + { + "id": "population-membership-fixture-paths", + "why": "`isShell` never touches the filesystem, so upstream's `.githooks/` fixture paths would \"work\" while naming a directory this gate does not walk — coverage of a population that is not scanned.", + "upstream": " t('a .sh name is shell', isShell('scripts/x.sh', 'echo hi').by === 'extension');\n t(\n 'a shebang-only script is shell — the half a *.sh glob misses',\n isShell('.githooks/pre-push', '#!/bin/sh\\necho hi').by === 'shebang',\n );\n t('`#!/usr/bin/env bash` counts', isShell('.githooks/pre-commit', '#!/usr/bin/env bash\\n').by === 'shebang');\n t('a node script is not shell', isShell('scripts/x.mjs', '#!/usr/bin/env node\\n').shell === false);\n", + "ported": " t('a .sh name is shell', isShell('scripts/x.sh', 'echo hi').by === 'extension');\n // ⚠️ These fixture paths are under THIS repository's roots, not upstream's\n // `.githooks/`. `isShell` never touches the filesystem, so any string would\n // \"work\" — and that is the trap: a fixture naming a directory this gate does\n // not walk reads as coverage of a population that is not scanned.\n t(\n 'a shebang-only script is shell — the half a *.sh glob misses',\n isShell('e2e/live/ci/pre-push', '#!/bin/sh\\necho hi').by === 'shebang',\n );\n t('`#!/usr/bin/env bash` counts', isShell('scripts/release-helper', '#!/usr/bin/env bash\\n').by === 'shebang');\n t('a node script is not shell', isShell('scripts/x.mjs', '#!/usr/bin/env node\\n').shell === false);\n" + }, + { + "id": "clean-tree-fixture-shebang-member-root", + "why": "The end-to-end fixture's shebang-only member has to sit under a root THIS gate walks (`e2e/**`); under upstream's `.githooks/` it would never be discovered and the census leg would have passed anyway.", + "upstream": "\n const cleanRepo = fixtureRepo({\n 'scripts/ok.sh': '#!/usr/bin/env bash\\n# no mapfile, no declare -A\\nwhile IFS= read -r l; do :; done < f\\n',\n '.githooks/pre-push': '#!/bin/sh\\nnow=\"${EPOCHSECONDS:-$(date +%s)}\"\\n',\n });\n", + "ported": "\n // ⭐ The shebang-only member sits under `e2e/**` — a root THIS gate walks.\n // Upstream's fixture puts it under `.githooks/`, which is in upstream's roots\n // and not in these; carried across unchanged it would never be discovered,\n // and the census leg below would have passed anyway (see its own note).\n const cleanRepo = fixtureRepo({\n 'scripts/ok.sh': '#!/usr/bin/env bash\\n# no mapfile, no declare -A\\nwhile IFS= read -r l; do :; done < f\\n',\n 'e2e/live/ci/pre-push': '#!/bin/sh\\nnow=\"${EPOCHSECONDS:-$(date +%s)}\"\\n',\n });\n" + }, + { + "id": "clean-tree-census-asserted-by-count", + "why": "Upstream asserts the word `shebang` on the output, which the green line prints unconditionally — it passes on a discovery path that found nothing. The counts are what carry the claim, so both halves are asserted by number.", + "upstream": " );\n t(\n 'and its green line reports the shebang half of the census separately',\n /shebang/.test(cleanRun.stdout),\n cleanRun.stdout,\n", + "ported": " );\n // ⚠️ Upstream asserts `/shebang/` on this output. That word is in the green\n // line UNCONDITIONALLY — it is printed even when the count is 0 — so the leg\n // passes on a discovery path that found nothing, which is the species this\n // file exists to refuse. The COUNTS are what carry the claim, so they are\n // what is asserted: this fixture has exactly one `.sh` member and exactly one\n // shebang-only member, and both halves of the census must say so.\n t(\n 'and its green line reports the census as 1 by extension and 1 by shebang alone',\n /census: 1 by \\.sh extension, 1 by shebang alone/.test(cleanRun.stdout),\n cleanRun.stdout,\n );\n t(\n 'so the shebang half really is reachable under THIS repository\\'s roots',\n / 2 tracked shell file\\(s\\) /.test(cleanRun.stdout),\n cleanRun.stdout,\n" + }, + { + "id": "simulated-harness-leg-narrowed", + "why": "objectui#8404: both harness legs need a host that HAS `mapfile` to remove. At the floor the first leg is false for the wrong reason and the second passes for the wrong one, so they narrow together.", + "upstream": " const sim = spawnSync('bash', [probe], { encoding: 'utf8', env: { ...process.env, BASH_ENV: noBash4 } });\n t(\n 'the simulated-3.2 harness really removes the builtin (else the next leg proves nothing)',\n", + "ported": " const sim = spawnSync('bash', [probe], { encoding: 'utf8', env: { ...process.env, BASH_ENV: noBash4 } });\n // ⚠️ objectui#8404: both legs need a host that HAS `mapfile` to remove. At\n // the 3.2 floor `plain` never prints MAPFILE-WORKS either, so the first leg\n // is false for the wrong reason and the second passes for the wrong one —\n // 127 there is the absent builtin, not the harness. Narrowed together,\n // because the second leg's meaning is carried by the first.\n tHostBash4(\n 'the simulated-3.2 harness really removes the builtin (else the next leg proves nothing)',\n" + }, + { + "id": "harness-127-leg-narrowed", + "why": "The second harness leg's meaning is carried by the first, so it narrows with it rather than passing on a 127 that is the absent builtin instead of the harness.", + "upstream": " );\n t(\n 'and a script this gate flags really does die at 127 under it',\n", + "ported": " );\n tHostBash4(\n 'and a script this gate flags really does die at 127 under it',\n" + }, + { + "id": "real-tree-legs-replace-byshebang-floor", + "why": "Upstream's `live.byShebang > 0` would be RED here on the day this landed (every shell file is `.sh`). It is replaced by two legs that are true here and could break silently: the census PARTITIONS the population, and every member was really READ.", + "upstream": " );\n t(\n 'and the shebang half of the census is non-empty, so it is not decoration',\n live.byShebang > 0,\n `${live.byShebang} shebang-only file(s)`,\n );\n", + "ported": " );\n // ⛔ Upstream's second leg here is `live.byShebang > 0`, and it CANNOT be\n // carried: measured on `origin/main` `28cfff4` this repository has 12 shell\n // files and every one of them ends in `.sh`, so the shebang census is 0 and\n // upstream's leg would be red on the day this landed. The temptation is then\n // to drop the shebang half of discovery, which is the wrong repair — see the\n // \"## Population\" note above. What replaces it are the two things that ARE\n // true here and that a future edit could break silently:\n //\n // - the census PARTITIONS the population. If a member is ever counted in\n // neither half (or in both), the totals the green line prints stop\n // describing what was scanned, and that is the arithmetic upstream's\n // `> 0` never checked at all.\n // - every member really was READ. `listPopulation` skips a path it cannot\n // open, so a population of empty texts is a walk that discovered names\n // and judged nothing — green, over nothing.\n //\n // The shebang half's own MECHANISM is pinned end to end in the clean-tree\n // fixture above, where it is exercised rather than merely counted.\n t(\n 'the census partitions the population — every file counted in exactly one half',\n live.byExtension + live.byShebang === live.population.length,\n `${live.byExtension} + ${live.byShebang} != ${live.population.length}`,\n );\n t(\n 'and every member was actually read, not merely named',\n live.population.every((f) => typeof f.text === 'string' && f.text.length > 0),\n `${live.population.filter((f) => !f.text).length} member(s) came back empty`,\n );\n" + }, + { + "id": "host-premise-battery-block", + "why": "The narrowing mechanism is itself a place a gate can go quiet, so it is pinned in both directions — a host that can judge bash-4 must narrow nothing, one that cannot must narrow something. Upstream has no narrowing to pin.", + "upstream": "\n // ── The floor: every declared battery RAN, and ran its cases (#13489) ───\n", + "ported": "\n // --- ⭐ objectui#8404: the self-test declares its own host premise -------\n //\n // The narrowing mechanism above is itself a place a gate can go quiet, so it\n // is pinned in both directions: a host that CAN judge bash-4 must narrow\n // nothing, and a host that cannot must narrow something. A mechanism that\n // narrowed on every host would read as green while measuring nothing.\n battery('⭐ objectui#8404: the self-test declares its own host premise');\n // ⚠️ E1 again, and for E1's reason: this file has to NAME the branch it\n // refuses in order to explain why, and a check hunting that name has to name\n // it too. So the needle is assembled from parts rather than written as a\n // literal, and the prose lines are dropped before the search — exactly the\n // mention-versus-use rule the CONSTRUCTS table is built on. A real branch\n // lives on a code line, which is what survives the filter.\n const PLATFORM_BRANCH = new RegExp(['process', 'platform'].join('\\\\.'));\n const codeLines = readFileSync(SELF, 'utf8')\n .split('\\n')\n .filter((l) => !/^\\s*(\\/\\/|\\*|\\/\\*)/.test(l))\n .join('\\n');\n t(\n '⛔ the premise is a capability, never a platform name — a `darwin` branch reds again on the next non-GNU host',\n !PLATFORM_BRANCH.test(codeLines),\n 'a code line here branches on the platform name; the host premise must be measured instead',\n );\n t(\n 'and the filter that allows the prose above is not blanket permission — a code line IS searched',\n PLATFORM_BRANCH.test(codeLines + '\\nconst x = process' + '.platform;'),\n 'the mention/use filter matched nothing at all, so the leg above proves nothing',\n );\n t(\n 'and the two capability probes are constructs THIS gate declares above the floor',\n ids(HOST_PROBES[0].probe).includes('pipe-both') && ids(HOST_PROBES[1].probe).includes('mapfile'),\n `got ${JSON.stringify(HOST_PROBES.map((h) => ids(h.probe)))}`,\n );\n t(\n 'judging and narrowing are exclusive: a host that judges bash-4 narrowed nothing',\n !HOST_JUDGES_BASH4 || narrowed === 0,\n `HOST_JUDGES_BASH4=${HOST_JUDGES_BASH4} narrowed=${narrowed}`,\n );\n t(\n 'and a host that does NOT judge bash-4 really did narrow — a narrowing nobody takes is a claim nobody made',\n HOST_JUDGES_BASH4 || narrowed > 0,\n `HOST_JUDGES_BASH4=${HOST_JUDGES_BASH4} narrowed=${narrowed}`,\n );\n\n // ── The floor: every declared battery RAN, and ran its cases (#13489) ───\n" + }, + { + "id": "self-test-verdict-reports-narrowed-cases", + "why": "A narrowed case is a real coverage loss and the verdict prints it as one, naming what this host refused and that the SCAN itself is not narrowed. Upstream never narrows, so it prints one line.", + "upstream": " }\n console.log(`\\n✓ check-bash32-floor self-test: ${cases} cases pass.`);\n selfTestReachedVerdict = true;\n", + "ported": " }\n if (narrowed > 0) {\n console.log(\n `\\n✓ check-bash32-floor self-test: ${cases - narrowed} cases pass, ${narrowed} NARROWED.\\n`\n + ` This host's bash refuses ${NARROWING}, so it is at or below the bash 3.2 floor this\\n`\n + ' gate declares and cannot judge a bash-4 construct. Those cases did not run here:\\n'\n + ' a real coverage loss, stated rather than hidden. CI runs bash 5 and runs them.\\n'\n + ' ⛔ The SCAN is not narrowed — `node scripts/check-bash32-floor.mjs` is pure JS and\\n'\n + ' judges this host exactly as it judges CI (objectui#8404).',\n );\n } else {\n console.log(`\\n✓ check-bash32-floor self-test: ${cases} cases pass.`);\n }\n selfTestReachedVerdict = true;\n" + } + ] } ] }