@webpieces/ai-hook-rules 0.4.735 → 0.4.737
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.
- package/README.md +26 -5
- package/package.json +2 -2
- package/src/bin/codex-trust.d.ts +8 -2
- package/src/bin/codex-trust.js +10 -4
- package/src/bin/codex-trust.js.map +1 -1
- package/src/bin/hook-registration.d.ts +49 -23
- package/src/bin/hook-registration.js +80 -6
- package/src/bin/hook-registration.js.map +1 -1
- package/src/bin/neighbour-hooks.d.ts +42 -0
- package/src/bin/neighbour-hooks.js +177 -0
- package/src/bin/neighbour-hooks.js.map +1 -0
- package/src/bin/settings-shape.d.ts +37 -0
- package/src/bin/settings-shape.js +17 -0
- package/src/bin/settings-shape.js.map +1 -0
- package/src/bin/setup.d.ts +2 -1
- package/src/bin/setup.js.map +1 -1
- package/src/bin/shim-deny-reason.js +19 -1
- package/src/bin/shim-deny-reason.js.map +1 -1
- package/src/bin/upgrade-shim.js +14 -0
- package/src/bin/upgrade-shim.js.map +1 -1
- package/src/core/l0-matrix.js +3 -2
- package/src/core/l0-matrix.js.map +1 -1
- package/src/core/l0-tooling-doc.js +1 -1
- package/src/core/l0-tooling-doc.js.map +1 -1
- package/src/index.d.ts +0 -1
- package/src/index.js +1 -8
- package/src/index.js.map +1 -1
- package/src/bin/codex-guard-presence.d.ts +0 -112
- package/src/bin/codex-guard-presence.js +0 -190
- package/src/bin/codex-guard-presence.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"l0-matrix.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/l0-matrix.ts"],"names":[],"mappings":"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","sourcesContent":["import { CONFIG_FILENAME, writeTemplate } from '@webpieces/rules-config';\n\nimport {\n ADD_HOOK_PKG_CMD, CHECKOUT_MAIN_PULL_CMD, HOOK_PKG, INSTALL_HOOKS_CMD, L0AllowEntry, L0Call,\n L0_ALLOWLIST, RECOVERY_CMD, RESTORE_SHIM_CMD, SHIM_MARKER, UPGRADE_SHIM_CMD, WORKSPACE_MANIFEST,\n renderShim,\n} from '../bin/shim';\nimport { CODEX_READ_STILL_ALLOWED } from '../bin/l0-codex-read';\nimport { ENV_SURFACE, HARNESS_REGISTRATIONS, HarnessRegistration } from '../bin/hook-registration';\nimport { shimStaleDenyReason } from '../bin/shim-deny-reason';\nimport {\n L0_FAULT_BIN_BROKEN, L0_FAULT_BIN_MISSING, L0_FAULT_CONFIG_MISSING, L0_FAULT_CONFIG_OUT_OF_SYNC,\n L0_FAULT_DRIFT, L0_FAULT_NAMES, L0_FAULT_SHIM_STALE, L0_FAULT_UNDECLARED, L0FaultCode,\n L0_ROW_ALLOWLISTED, L0_ROW_BLOCKED, l0GuardHeader, l0MatrixCitation,\n} from './l0-fault-codes';\nimport { toError } from './to-error';\n\n// ---------------------------------------------------------------------------\n// L0 — the TOOLING-INTEGRITY layer, as data.\n//\n// L0 is the outermost guard: 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 faults are\n// enumerated in L0_FAULTS below and — drawn as a decision matrix — have NO genuine second dimension:\n//\n// fault present AND call not on the allowlist -> BLOCK(messageFor(fault))\n//\n// so the only thing that varies per fault is the MESSAGE. This module holds the fault table and\n// renders it, together with L0_ALLOWLIST (../bin/shim), into webpieces.guard-matrix.md — the doc the\n// deny messages point the AI at. Doc and code come from the SAME arrays, so they cannot drift.\n// ---------------------------------------------------------------------------\n\n/**\n * The doc L0's deny messages point at. Lives in @webpieces/rules-config/templates alongside the others,\n * and is written to <root>/.webpieces/instruct-ai/ lazily, only on an L0 BLOCK.\n *\n * That generated doc is the AUTHORITY for the fault table and the allowlist (same arrays, cannot\n * drift). guards/L0-tooling.md is the hand-written companion: it adds L0's evaluation\n * ORDER, the use cases and the known gaps, and it documents L1, none of which are rendered from code.\n * Change L0_FAULTS or L0_ALLOWLIST and the generated doc follows automatically — guards/L0-tooling.md does\n * not, so update it in the same PR.\n */\nexport const GUARD_MATRIX_DOC = 'webpieces.guard-matrix.md';\n\n/**\n * One CURE for a fault: the exact call, plus the `mention` that must appear in that fault's deny text.\n *\n * Both halves are asserted (l0-matrix.spec.ts): the call must be accepted by isAllowed(), and the deny\n * message must actually name it. That pairing is the anti-deadlock invariant — a message that\n * prescribes a command the allowlist rejects is exactly the shape of the three deadlocks\n * `.claude/rules/*.md` records, and it is how the dead `wp-setup-ai-hooks` bin in the config-missing text was caught.\n */\nexport class L0Cure {\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n readonly mention: string,\n readonly call: L0Call,\n /**\n * The one to reach for first when a fault has several. Exactly one cure per fault carries it,\n * so the rendered Fix section never asks the reader to choose between equals.\n */\n readonly preferred: boolean,\n /**\n * WHEN to pick this cure over its siblings — the sentence that makes a list of commands\n * actionable instead of a menu (\"when the PIN is the stale side\", not \"an alternative\").\n */\n readonly discriminator: string,\n ) {}\n\n /** A Bash cure renders as a literal command; a tool-shaped one renders as the edit it stands for. */\n isCommand(): boolean {\n return this.call.toolName === 'Bash';\n }\n}\n\n/** One L0 fault. Data-only → a class, per CLAUDE.md. */\nexport class L0Fault {\n constructor(\n /**\n * The codebook's letter — typed as the UNION of every declared code, not `string`, so\n * `L0_FAULT_NAMES[code]` is total and neither this module nor the deny builders need a\n * `?? 'unknown'` fallback. A fault added without a name fails to compile.\n */\n readonly code: L0FaultCode,\n readonly name: string,\n readonly detectedBy: string,\n readonly enforcedIn: string,\n readonly cures: readonly L0Cure[],\n /**\n * The artifact carrying this fault's deny text. For S/C/Y that is the deny string itself; for\n * D/X/U/K the text is built in POSIX sh inside the rendered shim, so it is the rendered shim —\n * the same bytes the consumer runs, which is what the mention assertion needs to search.\n */\n readonly denyText: string,\n ) {}\n}\n\n// The deny for fault C, and the ONLY message L0 has for a repo with no webpieces.config.json at all.\n// Moved here from runner.ts so the fault table and the runner cannot state different cures.\n//\n// It used to name `./node_modules/.bin/wp-setup-ai-hooks` — a bin that HAS NOT EXISTED since it was\n// renamed to wp-install-ai-hooks. So the one command this deny prescribed was (a) not installable and\n// (b) not on the L0 allowlist in that spelling, i.e. the AI was handed a cure it could neither run nor\n// get past the guard. Now it names the installer that actually seeds the config AND is entry 8 of the\n// allowlist, and it says out loud that writing the config yourself is allowed through.\n//\n// ORDERING (2026-08-02): writing the file yourself now LEADS. The bare installer used to be OPTION 1,\n// but it seeds the config and then PROMPTS twice for a hook target, which hangs a non-interactive\n// agent. Writing the file is the one cure that always works, and it is the same cure every other config\n// problem has (see the config-validation invariant in guards/L0-tooling.md): the validator reports every\n// error at once, so the write/validate loop converges in a couple of passes.\nexport const CONFIG_MISSING_REPORT = [\n `❌ webpieces ai-hooks blocked this call: ${CONFIG_FILENAME} not found.`,\n '',\n l0GuardHeader(L0_FAULT_CONFIG_MISSING, '1 violation'),\n ` ${CONFIG_FILENAME}`,\n ' → the webpieces guards cannot run without it, so every OTHER tool call is blocked.',\n ` → ${l0MatrixCitation(L0_FAULT_CONFIG_MISSING)}`,\n '',\n 'Still allowed while this block is up:',\n ` - any Read, and ${CODEX_READ_STILL_ALLOWED}`,\n ` - any Write/Edit whose target is ${CONFIG_FILENAME}`,\n ' - every command on the L0 allowlist, including the Fix Options below',\n ' THIS IS NOT A DEADLOCK - run one YOURSELF now; do not hand it back to the human.',\n '',\n ` Fix Option 1: (preferred) it needs no other tool and it never prompts - create ${CONFIG_FILENAME}`,\n ' yourself. The validator reports EVERY missing/invalid entry at once (each with the snippet to',\n ' paste), so a minimal first draft converges in about two passes.',\n ' Fix Option 2: pick this ONLY at an interactive terminal where you can answer its two prompts - it',\n ' goes on to wire the Claude Code hooks and asks for a target twice, which hangs a non-interactive',\n ' session.',\n ' run EXACTLY: `pnpm exec wp-install-ai-hooks`',\n '',\n 'Do not append anything to the option you pick — the allowlist is anchored to the whole command.',\n].join('\\n');\n\n// The HEADER of the fault-Y deny (built out in runner.checkConfigSync, which appends the per-rule\n// detail as the `[…]` block's offenders). Kept here so the fault table quotes the same text the runner\n// emits, and so Y opens with the same `[guard-name] (layer=L0 fault=Y row=3)` coordinates as every\n// other L0 fault — that triple is what joins the deny to the audit line and to the matrix row.\nexport const CONFIG_OUT_OF_SYNC_HEADER = [\n `❌ webpieces ai-hooks blocked this call: ${CONFIG_FILENAME} is out of sync.`,\n '',\n l0GuardHeader(L0_FAULT_CONFIG_OUT_OF_SYNC, '1 violation'),\n ` new built-in rules are present that have no entry in ${CONFIG_FILENAME}`,\n ` → ${l0MatrixCitation(L0_FAULT_CONFIG_OUT_OF_SYNC)}`,\n].join('\\n');\n\n// Writing/repairing the file yourself. PREFERRED for both config faults, per the config-validation\n// invariant in guards/L0-tooling.md: every config problem cures to \"make the file right\", the validator\n// reports all errors at once so the loop converges in a couple of passes, and allowlist entry 2 permits\n// this edit unconditionally. (That section is the authority — do not restate its reasoning here.)\nconst CONFIG_WRITE_CURE = new L0Cure(\n CONFIG_FILENAME, new L0Call('Edit', '', `/repo/${CONFIG_FILENAME}`), true,\n 'this fault fires at all — it is the only cure that needs no other tool, and it is never denied',\n);\n\n// Cure calls are spelled exactly as the deny messages spell them, so the mention assertion is a real\n// string search rather than a paraphrase.\n// webpieces-disable no-function-outside-class -- pure constructor helper for the L0_FAULTS literal below, in this data module\nfunction bashCure(command: string, preferred: boolean, discriminator: string): L0Cure {\n return new L0Cure(command, new L0Call('Bash', command, ''), preferred, discriminator);\n}\n\n/**\n * THE L0 faults, in first-match-wins order. D/X/U/K are decided in POSIX sh BEFORE the bin runs (a\n * stale, missing or broken validator cannot be trusted to validate itself); S/C/Y are decided inside\n * the bin, in JS. One model, two enforcement points.\n */\nexport const L0_FAULTS: readonly L0Fault[] = [\n new L0Fault(L0_FAULT_DRIFT, 'version drift — root package.json pin != installed version',\n 'sh, before the bin runs', 'sh',\n [\n // `pnpm install` clears D in BOTH directions — it makes installed == pin by definition — so\n // it is always the preferred cure. The direction only decides whether the PIN is the version\n // you WANT, which is what the second cure is for.\n bashCure('pnpm install', true,\n 'node_modules is OLDER than the pin, OR you are on a feature branch and want YOUR '\n + 'branch pin (usually the case) — it always clears the drift'),\n // The on-main sync is spelled `git checkout main && git pull origin main` and NOT `git pull`:\n // a raw pull on a FEATURE branch merges main into it and destroys the fork point, so it is\n // no longer on the L0 allowlist at all (see CHECKOUT_MAIN_PULL_BODY_ERE). This spelling ends\n // ON main, which is why it is safe from any branch — and it is a no-op checkout when you are\n // already there.\n //\n // And it is RAW GIT on purpose, where the workflow guards now say `pnpm wp-sync-main`\n // instead. The fault being cured here is `node_modules` disagreeing with the pin, so\n // `node_modules` is the untrustworthy thing — and every `pnpm wp-*` bin resolves through it.\n // An L0 cure may never be a command that has to load the package it is repairing. See the\n // long note on CHECKOUT_MAIN_PULL_BODY_ERE in bin/l0-allowlist.ts.\n bashCure(CHECKOUT_MAIN_PULL_CMD, false,\n 'node_modules is NEWER than the pin AND you are on main — the PIN is the stale side, so '\n + 'sync first and install second; a bare install would downgrade you'),\n ], renderShim()),\n new L0Fault(L0_FAULT_BIN_MISSING, 'guard bin missing (fresh clone / new worktree / package removed)',\n 'sh, before the bin runs', 'sh',\n [bashCure('pnpm install', true,\n 'this fault fires at all — nothing is installed in THIS tree, and a new git worktree '\n + 'copies no node_modules')],\n renderShim()),\n // U is X with the ONE input that inverts X's cure, which is why it is a separate fault and not a\n // sentence inside X's message: a BARE `pnpm install` is not a weaker fix here, it is a PROVABLE\n // no-op, and an agent that trusts the X text will run it until it gives up. See the ADD_HOOK_PKG\n // entry in l0-allowlist.ts for the incident.\n //\n // The ORDER of these three is the correction bought by a second incident. `pnpm add` used to be the\n // preferred cure, and it is the one cure that CANNOT be committed: a direct root dependency on\n // HOOK_PKG violates the umbrella rule (the root manifest depends on the umbrella ALONE), and the\n // message never said to revert it — so it was followed, and the workaround landed in a real\n // package.json. The package normally arrives WITH the umbrella, so a tree that is merely BEHIND is\n // the common cause and syncing is the cure that leaves a committable tree. The add stays last,\n // reachable, and explicitly labelled as a session unblock rather than a fix.\n new L0Fault(L0_FAULT_UNDECLARED, `guard bin missing AND ${HOOK_PKG} is not declared in package.json`,\n 'sh, before the bin runs', 'sh',\n [\n bashCure(CHECKOUT_MAIN_PULL_CMD, true,\n `${HOOK_PKG} arrives WITH the umbrella, so a tree that is behind explains this without `\n + 'anything being mis-declared — sync first, then install'),\n bashCure('pnpm install', false,\n 'the sync (or a raised catalog pin in ' + WORKSPACE_MANIFEST + ', which is editable '\n + 'while the block is up) gave the installer something new to do — a BARE install with '\n + 'nothing changed reports \"Lockfile is up to date\" and leaves the tree as broken as it '\n + 'found it'),\n bashCure(ADD_HOOK_PKG_CMD, false,\n 'nothing else worked and you need this session back — it is a session unblock, NOT a '\n + `fix: revert it with 'pnpm remove ${HOOK_PKG}' before committing, because a direct `\n + 'root dependency on it violates the umbrella rule'),\n ],\n renderShim()),\n new L0Fault(L0_FAULT_BIN_BROKEN, 'guard bin present but CRASHED (exit code not 0 or 2 — corrupt node_modules)',\n 'sh, before the bin runs', 'sh',\n [bashCure(RECOVERY_CMD, true,\n 'this fault fires at all — a BARE pnpm install SKIPS the corrupt package, because pnpm sees '\n + 'the right version on disk and considers it installed; only the delete forces a rewrite')],\n renderShim()),\n // S covers the WHOLE managed hook surface, not just the shim: the committed ai-hook.sh, the\n // .claude/settings.json entries that register them AND the managed `env` entry that pins the Bash\n // cwd so the hooks resolve identically for every subagent. They only work as a set — a settings file left\n // on a superseded form silently changes who governs, re-pinning every tree to the\n // primary's release — and nothing validated the registration at all before it joined this fault.\n new L0Fault(L0_FAULT_SHIM_STALE, 'a webpieces-managed hook file, one of the harness hook registrations (.claude/settings.json, .codex/hooks.json) or the managed env entry does not match this release',\n 'the guard bin', 'JS',\n [\n // wp-upgrade-shim leads because it is the ONLY cure that repairs EVERY managed surface, and\n // it is still surgical: it rewrites ai-hook.sh, each harness's registration and the env entry\n // and touches no config, and it imports only fs/path so it runs on a tree too broken to load\n // the rule engine. The INSTALLER is deliberately NOT a cure here: it also migrates the config\n // and prompts for a target twice, which hangs a non-interactive agent.\n bashCure(UPGRADE_SHIM_CMD, true,\n 'this fault fires at all — it is the only cure that repairs EVERY managed surface '\n + '(ai-hook.sh, each harness hook registration, and the Claude settings env entry), and it also '\n + 'deletes the retired guarantee-root.sh and any entry still naming it, and it '\n + 'touches no config; needs installed @webpieces/ai-hook-rules 0.4.408 or newer'),\n // 2026-07-21: the version gap below caused a real \"command not found\" deadlock.\n bashCure(RESTORE_SHIM_CMD, false,\n 'the installed @webpieces/ai-hook-rules is OLDER than 0.4.408, so wp-upgrade-shim does '\n + 'not exist yet — it is PARTIAL (it repairs ai-hook.sh and NOTHING else), so upgrade '\n + '@webpieces afterwards and run Option 1 to finish'),\n ],\n // The SAMPLE deny renders EVERY managed surface, built from HARNESS_REGISTRATIONS rather than\n // a hand-written trio — a doc that shows a three-surface deny while the guard can report four\n // is exactly the drift this generated-doc arrangement exists to make impossible.\n shimStaleDenyReason('', '', [\n SHIM_MARKER,\n ...HARNESS_REGISTRATIONS.map((h: HarnessRegistration): string => h.registrationSurface),\n ENV_SURFACE,\n ], false)),\n new L0Fault(L0_FAULT_CONFIG_MISSING, `${CONFIG_FILENAME} missing`,\n 'the guard bin', 'JS',\n [\n CONFIG_WRITE_CURE,\n // Kept, but demoted: it seeds the file and then PROMPTS twice for a hook target.\n bashCure(INSTALL_HOOKS_CMD, false,\n 'you are at an INTERACTIVE terminal and can answer its two hook-target prompts'),\n ], CONFIG_MISSING_REPORT),\n new L0Fault(L0_FAULT_CONFIG_OUT_OF_SYNC, `a loaded rule has no ${CONFIG_FILENAME} key`,\n 'the guard bin', 'JS',\n [CONFIG_WRITE_CURE], CONFIG_OUT_OF_SYNC_HEADER),\n];\n\n/**\n * One fault's FIX section, rendered from its `cures` array — literal commands only, never prose.\n *\n * This is the half that used to live in hand-written docs and drift. The three fields of L0Cure map\n * onto the three things a blocked reader needs and nothing else: WHAT to type (the call), WHETHER it is\n * the default (preferred), and WHEN to pick a sibling instead (discriminator). A cure with no\n * discriminator would render as a menu of equals, which is how an agent picks the wrong one.\n */\n// webpieces-disable no-function-outside-class -- pure string builder for renderGuardMatrixDoc below, beside the arrays it reads\nfunction renderFixSection(fault: L0Fault): string[] {\n const options = fault.cures.map((cure: L0Cure, i: number): string => {\n // A Bash cure is the command verbatim; a tool-shaped one is the file it edits (allowlist entry 2).\n const literal = cure.isCommand() ? `\\`${cure.call.command}\\`` : `edit \\`${cure.mention}\\` yourself`;\n const label = cure.preferred ? `Option ${i + 1} (preferred)` : `Option ${i + 1}`;\n return `- **${label}**: ${literal} ← pick this when ${cure.discriminator}`;\n });\n return [`### \\`${fault.code}\\` — ${fault.name}`, '', ...options, ''];\n}\n\n/**\n * Render webpieces.guard-matrix.md from L0_FAULTS + L0_ALLOWLIST.\n *\n * A unit test locks the committed template byte-identical to this output, the same way\n * templates/ai-hook.sh is locked to renderShim(). That is what makes the doc assertable instead of\n * aspirational: the table in the doc IS the array the guard consults.\n */\n// webpieces-disable no-function-outside-class -- pure string builder over the two exported arrays, beside them in this module\nexport function renderGuardMatrixDoc(): string {\n return [\n '# webpieces guard matrix — L0 (tooling integrity)',\n '',\n 'GENERATED from `L0_FAULTS` + `L0_ALLOWLIST` in `@webpieces/ai-hook-rules`. Do not hand-edit —',\n 'a unit test locks this file byte-identical to `renderGuardMatrixDoc()`, so the table below is',\n 'the array the guard actually consults, not a description of it.',\n '',\n 'L0 is the OUTERMOST guard 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. If you are',\n 'reading this, one of the faults below fired and named this file.',\n '',\n '## The faults',\n '',\n 'THE JOIN KEYS ARE `guard`, `fault=` and `row=`. Every L0 deny opens',\n '`[<guard>] (layer=L0 fault=<code> row=3, …)`, and every audit line — from BOTH halves of L0, the',\n '`sh` shim and the guard bin — carries `layer=L0 row=<n> fault=<code>`. So one grep lands you in',\n 'the deny, the log line and the row below. The guard names come from `L0_FAULT_NAMES` and the row',\n 'numbers from `L0_ROW_*`, both spelled in exactly one place (`core/l0-fault-codes.ts`).',\n '',\n '| code | guard | fault | detected by | enforced in |',\n '|---|---|---|---|---|',\n ...L0_FAULTS.map((f: L0Fault): string =>\n `| \\`${f.code}\\` | \\`${L0_FAULT_NAMES[f.code]}\\` | ${f.name} | ${f.detectedBy} | ${f.enforcedIn} |`),\n '',\n 'First match wins. `D`/`X`/`U`/`K` are decided in POSIX `sh` inside the committed shim, BEFORE the',\n 'guard bin runs — a stale, missing or broken validator cannot be trusted to validate itself.',\n '',\n '## The fix, per fault',\n '',\n 'Every command below is rendered from that fault\\'s `cures` array and is asserted, by unit test,',\n 'to be accepted by `isAllowed()` — so nothing here can be a command the guard then rejects. Type',\n 'the option you pick EXACTLY as written and run nothing else on that line.',\n '',\n ...L0_FAULTS.flatMap(renderFixSection),\n ...renderMatrixAndAllowlist(),\n ].join('\\n');\n}\n\n/**\n * The second half of the doc: the three-row matrix and the ONE allowlist. Split out of\n * renderGuardMatrixDoc solely to keep it inside the method-line budget — the join order is what makes\n * the two halves one file, so keep them adjacent and keep the byte-lock test as the arbiter.\n */\n// webpieces-disable no-function-outside-class -- second half of renderGuardMatrixDoc's string, beside it in this module\nfunction renderMatrixAndAllowlist(): string[] {\n return [\n '## The matrix',\n '',\n 'L0 has NO genuine second dimension. Every branch reduces to one question:',\n '',\n '| # | fault | on the allowlist? | outcome |',\n '|---|---|---|---|',\n '| 1 | none | — | hand down to the next guard layer |',\n `| ${L0_ROW_ALLOWLISTED} | any | yes | PASS or ALLOW (see the entry) |`,\n `| ${L0_ROW_BLOCKED} | any | no | BLOCK — **only the message varies by fault** |`,\n '',\n `Row ${L0_ROW_BLOCKED} is the only row that blocks, so every L0 deny cites it — \\`row=${L0_ROW_BLOCKED}\\` in the`,\n 'deny header, `row=' + L0_ROW_BLOCKED + '` on the audit line, and this row here. Same numbers as L1 uses for',\n 'its own rows (see `L1_ROWS`), and for the same reason: a row number is IDENTITY, so it is never reused.',\n '',\n 'The tool is not a dimension either: \"any Read\" is an allowlist ENTRY, not a tool check.',\n '',\n '## The allowlist',\n '',\n 'ONE list, consulted identically by every fault. A cure that cannot help a given fault also',\n 'cannot hurt it, and gating each entry on a fault is what produced four real defects (a stale',\n 'shim that denied `pnpm install` and every git sync; faults that denied every Read; a config fault',\n 'that denied `rm -rf node_modules && pnpm install` while allowing a bare `pnpm install`).',\n '',\n '| # | allowed | outcome |',\n '|---|---|---|',\n ...L0_ALLOWLIST.map((e: L0AllowEntry, i: number): string => `| ${i + 1} | ${e.label} | ${e.kind.toUpperCase()} |`),\n '',\n '- **PASS** — L0 has no objection; the call falls THROUGH so the downstream guards still judge it.',\n '- **ALLOW** — terminal; bypasses everything, because a cure must stay reachable even when a',\n ' downstream guard would block it.',\n '',\n 'Every Bash entry is anchored to the WHOLE command. Appending `&& git status` makes it a DIFFERENT',\n 'command and it is rejected again — that is not the guard refusing its own cure.',\n '',\n 'On an UNGATED entry a leading `cd <dir> &&`, a trailing `2>&1` and a pipe into `tail`/`head` are',\n 'tolerated; nothing else is. **The harness-gated read row tolerates NONE of the three** — not the',\n '`cd` prefix, not the redirect, not the pipe — so `cat f 2>&1` and `cat f | head -20` are both',\n 'rejected. That is deliberate, not an oversight: the row means exactly what',\n '`core/shell-read-parity.ts` means by \"this is a read\", and that module treats a pipe or a redirect',\n 'as proof the command is more than one. Type the bare command.',\n '',\n '`git merge` is deliberately NOT on this list. Main is merged ONLY through the 3-point fork merge',\n '(`pnpm wp-start-update`, or `pnpm wp-start-upsert-pr` when a PR is already open).',\n '',\n '## Known asymmetry',\n '',\n 'Under `S`/`C`/`Y` the guard bin IS running, so a PASS really does fall through to the downstream',\n 'guards. Under `D`/`X`/`U`/`K` the bin is never executed, so there is nothing to fall through to and a',\n 'PASS degenerates into a terminal allow — reads are unguarded during those three faults.',\n '',\n '## Widening L0',\n '',\n 'Add an entry to `L0_ALLOWLIST` in `packages/tooling/ai-hook-rules/src/bin/shim.ts`. That array is',\n 'the single source for the JS allowlist, the `grep -E` inside the rendered shim, and this file.',\n '',\n ];\n}\n\n/**\n * Drop the matrix doc where the AI can read it, and return its absolute path ('' if it could not be\n * written). Called from the L0 BLOCK path so the deny can say `READ <path>`.\n *\n * Best-effort by design: this runs while the tree is already known-broken, and a missing template (an\n * @webpieces/rules-config older than this package) must degrade the deny message, never replace it\n * with a crash.\n */\n// webpieces-disable no-function-outside-class -- sibling of renderGuardMatrixDoc in this module\nexport function writeGuardMatrixDoc(workspaceRoot: string): string {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return writeTemplate(workspaceRoot, GUARD_MATRIX_DOC);\n } catch (err: unknown) {\n const error = toError(err);\n void error; // best-effort: no doc → the deny simply omits the pointer\n return '';\n }\n}\n\n/**\n * The `READ <path>` pointer appended to an L0 deny, or '' when the doc could not be written.\n *\n * It opens with a NEWLINE, not a space: the JS-side L0 denies render in the house format now (a header,\n * a `[guard-name]` block, `Fix Option N:` lines), so a pointer glued onto the end of the last line would\n * be the one place the shape broke. A real newline is safe on both call paths — denyJson() JSON.stringifies\n * it, exactly as it does for every multi-line L1 report.\n */\n// webpieces-disable no-function-outside-class -- sibling of writeGuardMatrixDoc in this module\nexport function guardMatrixPointer(docPath: string): string {\n if (docPath === '') return '';\n return `\\nThe full L0 guard matrix - every fault and everything that is allowed through - is at ${docPath}; READ it if you are unsure why this call was blocked.`;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"l0-matrix.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/l0-matrix.ts"],"names":[],"mappings":";;;AAmTA,oDAqCC;AA6ED,kDASC;AAWD,gDAGC;AA5bD,0DAAyE;AAEzE,sCAIqB;AACrB,wDAAgE;AAChE,gEAAmG;AACnG,8DAA8D;AAC9D,qDAI0B;AAC1B,yCAAqC;AAErC,8EAA8E;AAC9E,6CAA6C;AAC7C,EAAE;AACF,uFAAuF;AACvF,kGAAkG;AAClG,qGAAqG;AACrG,EAAE;AACF,gFAAgF;AAChF,EAAE;AACF,gGAAgG;AAChG,qGAAqG;AACrG,+FAA+F;AAC/F,8EAA8E;AAE9E;;;;;;;;;GASG;AACU,QAAA,gBAAgB,GAAG,2BAA2B,CAAC;AAE5D;;;;;;;GAOG;AACH,MAAa,MAAM;IAGF;IACA;IAKA;IAKA;IAbb,yDAAyD;IACzD,YACa,OAAe,EACf,IAAY;IACrB;;;OAGG;IACM,SAAkB;IAC3B;;;OAGG;IACM,aAAqB;QAXrB,YAAO,GAAP,OAAO,CAAQ;QACf,SAAI,GAAJ,IAAI,CAAQ;QAKZ,cAAS,GAAT,SAAS,CAAS;QAKlB,kBAAa,GAAb,aAAa,CAAQ;IAC/B,CAAC;IAEJ,qGAAqG;IACrG,SAAS;QACL,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,KAAK,MAAM,CAAC;IACzC,CAAC;CACJ;AArBD,wBAqBC;AAED,wDAAwD;AACxD,MAAa,OAAO;IAOH;IACA;IACA;IACA;IACA;IAMA;IAhBb;IACI;;;;OAIG;IACM,IAAiB,EACjB,IAAY,EACZ,UAAkB,EAClB,UAAkB,EAClB,KAAwB;IACjC;;;;OAIG;IACM,QAAgB;QAVhB,SAAI,GAAJ,IAAI,CAAa;QACjB,SAAI,GAAJ,IAAI,CAAQ;QACZ,eAAU,GAAV,UAAU,CAAQ;QAClB,eAAU,GAAV,UAAU,CAAQ;QAClB,UAAK,GAAL,KAAK,CAAmB;QAMxB,aAAQ,GAAR,QAAQ,CAAQ;IAC1B,CAAC;CACP;AAnBD,0BAmBC;AAED,qGAAqG;AACrG,4FAA4F;AAC5F,EAAE;AACF,oGAAoG;AACpG,sGAAsG;AACtG,uGAAuG;AACvG,sGAAsG;AACtG,uFAAuF;AACvF,EAAE;AACF,sGAAsG;AACtG,kGAAkG;AAClG,wGAAwG;AACxG,yGAAyG;AACzG,6EAA6E;AAChE,QAAA,qBAAqB,GAAG;IACjC,2CAA2C,8BAAe,aAAa;IACvE,EAAE;IACF,IAAA,8BAAa,EAAC,wCAAuB,EAAE,aAAa,CAAC;IACrD,KAAK,8BAAe,EAAE;IACtB,wFAAwF;IACxF,SAAS,IAAA,iCAAgB,EAAC,wCAAuB,CAAC,EAAE;IACpD,EAAE;IACF,uCAAuC;IACvC,qBAAqB,wCAAwB,EAAE;IAC/C,sCAAsC,8BAAe,EAAE;IACvD,wEAAwE;IACxE,oFAAoF;IACpF,EAAE;IACF,oFAAoF,8BAAe,EAAE;IACrG,mGAAmG;IACnG,qEAAqE;IACrE,qGAAqG;IACrG,sGAAsG;IACtG,cAAc;IACd,kDAAkD;IAClD,EAAE;IACF,iGAAiG;CACpG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb,kGAAkG;AAClG,uGAAuG;AACvG,mGAAmG;AACnG,+FAA+F;AAClF,QAAA,yBAAyB,GAAG;IACrC,2CAA2C,8BAAe,kBAAkB;IAC5E,EAAE;IACF,IAAA,8BAAa,EAAC,4CAA2B,EAAE,aAAa,CAAC;IACzD,0DAA0D,8BAAe,EAAE;IAC3E,SAAS,IAAA,iCAAgB,EAAC,4CAA2B,CAAC,EAAE;CAC3D,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAEb,mGAAmG;AACnG,wGAAwG;AACxG,wGAAwG;AACxG,kGAAkG;AAClG,MAAM,iBAAiB,GAAG,IAAI,MAAM,CAChC,8BAAe,EAAE,IAAI,aAAM,CAAC,MAAM,EAAE,EAAE,EAAE,SAAS,8BAAe,EAAE,CAAC,EAAE,IAAI,EACzE,gGAAgG,CACnG,CAAC;AAEF,qGAAqG;AACrG,0CAA0C;AAC1C,8HAA8H;AAC9H,SAAS,QAAQ,CAAC,OAAe,EAAE,SAAkB,EAAE,aAAqB;IACxE,OAAO,IAAI,MAAM,CAAC,OAAO,EAAE,IAAI,aAAM,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,CAAC,EAAE,SAAS,EAAE,aAAa,CAAC,CAAC;AAC1F,CAAC;AAED;;;;GAIG;AACU,QAAA,SAAS,GAAuB;IACzC,IAAI,OAAO,CAAC,+BAAc,EAAE,4DAA4D,EACpF,yBAAyB,EAAE,IAAI,EAC/B;QACI,4FAA4F;QAC5F,6FAA6F;QAC7F,kDAAkD;QAClD,QAAQ,CAAC,cAAc,EAAE,IAAI,EACzB,mFAAmF;cACjF,4DAA4D,CAAC;QACnE,8FAA8F;QAC9F,2FAA2F;QAC3F,6FAA6F;QAC7F,6FAA6F;QAC7F,iBAAiB;QACjB,EAAE;QACF,sFAAsF;QACtF,qFAAqF;QACrF,6FAA6F;QAC7F,0FAA0F;QAC1F,mEAAmE;QACnE,QAAQ,CAAC,6BAAsB,EAAE,KAAK,EAClC,yFAAyF;cACvF,mEAAmE,CAAC;KAC7E,EAAE,IAAA,iBAAU,GAAE,CAAC;IACpB,IAAI,OAAO,CAAC,qCAAoB,EAAE,kEAAkE,EAChG,yBAAyB,EAAE,IAAI,EAC/B,CAAC,QAAQ,CAAC,cAAc,EAAE,IAAI,EAC1B,sFAAsF;cACpF,wBAAwB,CAAC,CAAC,EAChC,IAAA,iBAAU,GAAE,CAAC;IACjB,iGAAiG;IACjG,gGAAgG;IAChG,iGAAiG;IACjG,6CAA6C;IAC7C,EAAE;IACF,oGAAoG;IACpG,+FAA+F;IAC/F,iGAAiG;IACjG,4FAA4F;IAC5F,mGAAmG;IACnG,+FAA+F;IAC/F,6EAA6E;IAC7E,IAAI,OAAO,CAAC,oCAAmB,EAAE,yBAAyB,eAAQ,kCAAkC,EAChG,yBAAyB,EAAE,IAAI,EAC/B;QACI,QAAQ,CAAC,6BAAsB,EAAE,IAAI,EACjC,GAAG,eAAQ,6EAA6E;cACtF,wDAAwD,CAAC;QAC/D,QAAQ,CAAC,cAAc,EAAE,KAAK,EAC1B,uCAAuC,GAAG,yBAAkB,GAAG,sBAAsB;cACnF,sFAAsF;cACtF,uFAAuF;cACvF,UAAU,CAAC;QACjB,QAAQ,CAAC,uBAAgB,EAAE,KAAK,EAC5B,sFAAsF;cACpF,oCAAoC,eAAQ,wCAAwC;cACpF,kDAAkD,CAAC;KAC5D,EACD,IAAA,iBAAU,GAAE,CAAC;IACjB,IAAI,OAAO,CAAC,oCAAmB,EAAE,6EAA6E,EAC1G,yBAAyB,EAAE,IAAI,EAC/B,CAAC,QAAQ,CAAC,mBAAY,EAAE,IAAI,EACxB,6FAA6F;cAC3F,wFAAwF,CAAC,CAAC,EAChG,IAAA,iBAAU,GAAE,CAAC;IACjB,4FAA4F;IAC5F,kGAAkG;IAClG,0GAA0G;IAC1G,kFAAkF;IAClF,iGAAiG;IACjG,IAAI,OAAO,CAAC,oCAAmB,EAAE,sKAAsK,EACnM,eAAe,EAAE,IAAI,EACrB;QACI,4FAA4F;QAC5F,8FAA8F;QAC9F,6FAA6F;QAC7F,8FAA8F;QAC9F,uEAAuE;QACvE,QAAQ,CAAC,uBAAgB,EAAE,IAAI,EAC3B,mFAAmF;cACjF,mFAAmF;cACnF,mFAAmF;cACnF,8EAA8E;cAC9E,8EAA8E,CAAC;QACrF,gFAAgF;QAChF,QAAQ,CAAC,uBAAgB,EAAE,KAAK,EAC5B,wFAAwF;cACtF,qFAAqF;cACrF,kDAAkD,CAAC;KAC5D;IACD,8FAA8F;IAC9F,8FAA8F;IAC9F,iFAAiF;IACjF,IAAA,sCAAmB,EAAC,EAAE,EAAE,EAAE,EAAE;QACxB,kBAAW;QACX,GAAG,yCAAqB,CAAC,OAAO,CAAC,CAAC,CAAsB,EAAqB,EAAE,CAAC,CAAC,CAAC,CAAC,mBAAmB,EAAE,CAAC,CAAC,gBAAgB,CAAC,CAAC;QAC5H,+BAAW;KACd,EAAE,KAAK,CAAC,CAAC;IACd,IAAI,OAAO,CAAC,wCAAuB,EAAE,GAAG,8BAAe,UAAU,EAC7D,eAAe,EAAE,IAAI,EACrB;QACI,iBAAiB;QACjB,iFAAiF;QACjF,QAAQ,CAAC,wBAAiB,EAAE,KAAK,EAC7B,+EAA+E,CAAC;KACvF,EAAE,6BAAqB,CAAC;IAC7B,IAAI,OAAO,CAAC,4CAA2B,EAAE,wBAAwB,8BAAe,MAAM,EAClF,eAAe,EAAE,IAAI,EACrB,CAAC,iBAAiB,CAAC,EAAE,iCAAyB,CAAC;CACtD,CAAC;AAEF;;;;;;;GAOG;AACH,gIAAgI;AAChI,SAAS,gBAAgB,CAAC,KAAc;IACpC,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAY,EAAE,CAAS,EAAU,EAAE;QAChE,mGAAmG;QACnG,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,OAAO,aAAa,CAAC;QACpG,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACjF,OAAO,OAAO,KAAK,OAAO,OAAO,sBAAsB,IAAI,CAAC,aAAa,EAAE,CAAC;IAChF,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,SAAS,KAAK,CAAC,IAAI,QAAQ,KAAK,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,GAAG,OAAO,EAAE,EAAE,CAAC,CAAC;AACzE,CAAC;AAED;;;;;;GAMG;AACH,8HAA8H;AAC9H,SAAgB,oBAAoB;IAChC,OAAO;QACH,mDAAmD;QACnD,EAAE;QACF,+FAA+F;QAC/F,+FAA+F;QAC/F,iEAAiE;QACjE,EAAE;QACF,8FAA8F;QAC9F,+FAA+F;QAC/F,kEAAkE;QAClE,EAAE;QACF,eAAe;QACf,EAAE;QACF,qEAAqE;QACrE,kGAAkG;QAClG,iGAAiG;QACjG,kGAAkG;QAClG,wFAAwF;QACxF,EAAE;QACF,sDAAsD;QACtD,uBAAuB;QACvB,GAAG,iBAAS,CAAC,GAAG,CAAC,CAAC,CAAU,EAAU,EAAE,CACpC,OAAO,CAAC,CAAC,IAAI,UAAU,+BAAc,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,UAAU,MAAM,CAAC,CAAC,UAAU,IAAI,CAAC;QACxG,EAAE;QACF,mGAAmG;QACnG,6FAA6F;QAC7F,EAAE;QACF,uBAAuB;QACvB,EAAE;QACF,iGAAiG;QACjG,iGAAiG;QACjG,2EAA2E;QAC3E,EAAE;QACF,GAAG,iBAAS,CAAC,OAAO,CAAC,gBAAgB,CAAC;QACtC,GAAG,wBAAwB,EAAE;KAChC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACjB,CAAC;AAED;;;;GAIG;AACH,wHAAwH;AACxH,SAAS,wBAAwB;IAC7B,OAAO;QACH,eAAe;QACf,EAAE;QACF,2EAA2E;QAC3E,EAAE;QACF,6CAA6C;QAC7C,mBAAmB;QACnB,sDAAsD;QACtD,KAAK,mCAAkB,gDAAgD;QACvE,KAAK,+BAAc,8DAA8D;QACjF,EAAE;QACF,OAAO,+BAAc,mEAAmE,+BAAc,WAAW;QACjH,oBAAoB,GAAG,+BAAc,GAAG,qEAAqE;QAC7G,yGAAyG;QACzG,EAAE;QACF,yFAAyF;QACzF,EAAE;QACF,kBAAkB;QAClB,EAAE;QACF,4FAA4F;QAC5F,8FAA8F;QAC9F,mGAAmG;QACnG,0FAA0F;QAC1F,EAAE;QACF,2BAA2B;QAC3B,eAAe;QACf,GAAG,mBAAY,CAAC,GAAG,CAAC,CAAC,CAAe,EAAE,CAAS,EAAU,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC;QAClH,EAAE;QACF,mGAAmG;QACnG,6FAA6F;QAC7F,oCAAoC;QACpC,EAAE;QACF,mGAAmG;QACnG,iFAAiF;QACjF,EAAE;QACF,kGAAkG;QAClG,kGAAkG;QAClG,+FAA+F;QAC/F,4EAA4E;QAC5E,oGAAoG;QACpG,+DAA+D;QAC/D,EAAE;QACF,kGAAkG;QAClG,mFAAmF;QACnF,EAAE;QACF,oBAAoB;QACpB,EAAE;QACF,kGAAkG;QAClG,uGAAuG;QACvG,yFAAyF;QACzF,EAAE;QACF,gBAAgB;QAChB,EAAE;QACF,mGAAmG;QACnG,gGAAgG;QAChG,EAAE;KACL,CAAC;AACN,CAAC;AAED;;;;;;;GAOG;AACH,gGAAgG;AAChG,SAAgB,mBAAmB,CAAC,aAAqB;IACrD,8DAA8D;IAC9D,IAAI,CAAC;QACD,OAAO,IAAA,4BAAa,EAAC,aAAa,EAAE,wBAAgB,CAAC,CAAC;IAC1D,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,IAAA,kBAAO,EAAC,GAAG,CAAC,CAAC;QAC3B,KAAK,KAAK,CAAC,CAAC,0DAA0D;QACtE,OAAO,EAAE,CAAC;IACd,CAAC;AACL,CAAC;AAED;;;;;;;GAOG;AACH,+FAA+F;AAC/F,SAAgB,kBAAkB,CAAC,OAAe;IAC9C,IAAI,OAAO,KAAK,EAAE;QAAE,OAAO,EAAE,CAAC;IAC9B,OAAO,2FAA2F,OAAO,wDAAwD,CAAC;AACtK,CAAC","sourcesContent":["import { CONFIG_FILENAME, writeTemplate } from '@webpieces/rules-config';\n\nimport {\n ADD_HOOK_PKG_CMD, CHECKOUT_MAIN_PULL_CMD, HOOK_PKG, INSTALL_HOOKS_CMD, L0AllowEntry, L0Call,\n L0_ALLOWLIST, RECOVERY_CMD, RESTORE_SHIM_CMD, SHIM_MARKER, UPGRADE_SHIM_CMD, WORKSPACE_MANIFEST,\n renderShim,\n} from '../bin/shim';\nimport { CODEX_READ_STILL_ALLOWED } from '../bin/l0-codex-read';\nimport { ENV_SURFACE, HARNESS_REGISTRATIONS, HarnessRegistration } from '../bin/hook-registration';\nimport { shimStaleDenyReason } from '../bin/shim-deny-reason';\nimport {\n L0_FAULT_BIN_BROKEN, L0_FAULT_BIN_MISSING, L0_FAULT_CONFIG_MISSING, L0_FAULT_CONFIG_OUT_OF_SYNC,\n L0_FAULT_DRIFT, L0_FAULT_NAMES, L0_FAULT_SHIM_STALE, L0_FAULT_UNDECLARED, L0FaultCode,\n L0_ROW_ALLOWLISTED, L0_ROW_BLOCKED, l0GuardHeader, l0MatrixCitation,\n} from './l0-fault-codes';\nimport { toError } from './to-error';\n\n// ---------------------------------------------------------------------------\n// L0 — the TOOLING-INTEGRITY layer, as data.\n//\n// L0 is the outermost guard: 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 faults are\n// enumerated in L0_FAULTS below and — drawn as a decision matrix — have NO genuine second dimension:\n//\n// fault present AND call not on the allowlist -> BLOCK(messageFor(fault))\n//\n// so the only thing that varies per fault is the MESSAGE. This module holds the fault table and\n// renders it, together with L0_ALLOWLIST (../bin/shim), into webpieces.guard-matrix.md — the doc the\n// deny messages point the AI at. Doc and code come from the SAME arrays, so they cannot drift.\n// ---------------------------------------------------------------------------\n\n/**\n * The doc L0's deny messages point at. Lives in @webpieces/rules-config/templates alongside the others,\n * and is written to <root>/.webpieces/instruct-ai/ lazily, only on an L0 BLOCK.\n *\n * That generated doc is the AUTHORITY for the fault table and the allowlist (same arrays, cannot\n * drift). guards/L0-tooling.md is the hand-written companion: it adds L0's evaluation\n * ORDER, the use cases and the known gaps, and it documents L1, none of which are rendered from code.\n * Change L0_FAULTS or L0_ALLOWLIST and the generated doc follows automatically — guards/L0-tooling.md does\n * not, so update it in the same PR.\n */\nexport const GUARD_MATRIX_DOC = 'webpieces.guard-matrix.md';\n\n/**\n * One CURE for a fault: the exact call, plus the `mention` that must appear in that fault's deny text.\n *\n * Both halves are asserted (l0-matrix.spec.ts): the call must be accepted by isAllowed(), and the deny\n * message must actually name it. That pairing is the anti-deadlock invariant — a message that\n * prescribes a command the allowlist rejects is exactly the shape of the three deadlocks\n * `.claude/rules/*.md` records, and it is how the dead `wp-setup-ai-hooks` bin in the config-missing text was caught.\n */\nexport class L0Cure {\n // eslint-disable-next-line @typescript-eslint/max-params\n constructor(\n readonly mention: string,\n readonly call: L0Call,\n /**\n * The one to reach for first when a fault has several. Exactly one cure per fault carries it,\n * so the rendered Fix section never asks the reader to choose between equals.\n */\n readonly preferred: boolean,\n /**\n * WHEN to pick this cure over its siblings — the sentence that makes a list of commands\n * actionable instead of a menu (\"when the PIN is the stale side\", not \"an alternative\").\n */\n readonly discriminator: string,\n ) {}\n\n /** A Bash cure renders as a literal command; a tool-shaped one renders as the edit it stands for. */\n isCommand(): boolean {\n return this.call.toolName === 'Bash';\n }\n}\n\n/** One L0 fault. Data-only → a class, per CLAUDE.md. */\nexport class L0Fault {\n constructor(\n /**\n * The codebook's letter — typed as the UNION of every declared code, not `string`, so\n * `L0_FAULT_NAMES[code]` is total and neither this module nor the deny builders need a\n * `?? 'unknown'` fallback. A fault added without a name fails to compile.\n */\n readonly code: L0FaultCode,\n readonly name: string,\n readonly detectedBy: string,\n readonly enforcedIn: string,\n readonly cures: readonly L0Cure[],\n /**\n * The artifact carrying this fault's deny text. For S/C/Y that is the deny string itself; for\n * D/X/U/K the text is built in POSIX sh inside the rendered shim, so it is the rendered shim —\n * the same bytes the consumer runs, which is what the mention assertion needs to search.\n */\n readonly denyText: string,\n ) {}\n}\n\n// The deny for fault C, and the ONLY message L0 has for a repo with no webpieces.config.json at all.\n// Moved here from runner.ts so the fault table and the runner cannot state different cures.\n//\n// It used to name `./node_modules/.bin/wp-setup-ai-hooks` — a bin that HAS NOT EXISTED since it was\n// renamed to wp-install-ai-hooks. So the one command this deny prescribed was (a) not installable and\n// (b) not on the L0 allowlist in that spelling, i.e. the AI was handed a cure it could neither run nor\n// get past the guard. Now it names the installer that actually seeds the config AND is entry 8 of the\n// allowlist, and it says out loud that writing the config yourself is allowed through.\n//\n// ORDERING (2026-08-02): writing the file yourself now LEADS. The bare installer used to be OPTION 1,\n// but it seeds the config and then PROMPTS twice for a hook target, which hangs a non-interactive\n// agent. Writing the file is the one cure that always works, and it is the same cure every other config\n// problem has (see the config-validation invariant in guards/L0-tooling.md): the validator reports every\n// error at once, so the write/validate loop converges in a couple of passes.\nexport const CONFIG_MISSING_REPORT = [\n `❌ webpieces ai-hooks blocked this call: ${CONFIG_FILENAME} not found.`,\n '',\n l0GuardHeader(L0_FAULT_CONFIG_MISSING, '1 violation'),\n ` ${CONFIG_FILENAME}`,\n ' → the webpieces guards cannot run without it, so every OTHER tool call is blocked.',\n ` → ${l0MatrixCitation(L0_FAULT_CONFIG_MISSING)}`,\n '',\n 'Still allowed while this block is up:',\n ` - any Read, and ${CODEX_READ_STILL_ALLOWED}`,\n ` - any Write/Edit whose target is ${CONFIG_FILENAME}`,\n ' - every command on the L0 allowlist, including the Fix Options below',\n ' THIS IS NOT A DEADLOCK - run one YOURSELF now; do not hand it back to the human.',\n '',\n ` Fix Option 1: (preferred) it needs no other tool and it never prompts - create ${CONFIG_FILENAME}`,\n ' yourself. The validator reports EVERY missing/invalid entry at once (each with the snippet to',\n ' paste), so a minimal first draft converges in about two passes.',\n ' Fix Option 2: pick this ONLY at an interactive terminal where you can answer its two prompts - it',\n ' goes on to wire the Claude Code hooks and asks for a target twice, which hangs a non-interactive',\n ' session.',\n ' run EXACTLY: `pnpm exec wp-install-ai-hooks`',\n '',\n 'Do not append anything to the option you pick — the allowlist is anchored to the whole command.',\n].join('\\n');\n\n// The HEADER of the fault-Y deny (built out in runner.checkConfigSync, which appends the per-rule\n// detail as the `[…]` block's offenders). Kept here so the fault table quotes the same text the runner\n// emits, and so Y opens with the same `[guard-name] (layer=L0 fault=Y row=3)` coordinates as every\n// other L0 fault — that triple is what joins the deny to the audit line and to the matrix row.\nexport const CONFIG_OUT_OF_SYNC_HEADER = [\n `❌ webpieces ai-hooks blocked this call: ${CONFIG_FILENAME} is out of sync.`,\n '',\n l0GuardHeader(L0_FAULT_CONFIG_OUT_OF_SYNC, '1 violation'),\n ` new built-in rules are present that have no entry in ${CONFIG_FILENAME}`,\n ` → ${l0MatrixCitation(L0_FAULT_CONFIG_OUT_OF_SYNC)}`,\n].join('\\n');\n\n// Writing/repairing the file yourself. PREFERRED for both config faults, per the config-validation\n// invariant in guards/L0-tooling.md: every config problem cures to \"make the file right\", the validator\n// reports all errors at once so the loop converges in a couple of passes, and allowlist entry 2 permits\n// this edit unconditionally. (That section is the authority — do not restate its reasoning here.)\nconst CONFIG_WRITE_CURE = new L0Cure(\n CONFIG_FILENAME, new L0Call('Edit', '', `/repo/${CONFIG_FILENAME}`), true,\n 'this fault fires at all — it is the only cure that needs no other tool, and it is never denied',\n);\n\n// Cure calls are spelled exactly as the deny messages spell them, so the mention assertion is a real\n// string search rather than a paraphrase.\n// webpieces-disable no-function-outside-class -- pure constructor helper for the L0_FAULTS literal below, in this data module\nfunction bashCure(command: string, preferred: boolean, discriminator: string): L0Cure {\n return new L0Cure(command, new L0Call('Bash', command, ''), preferred, discriminator);\n}\n\n/**\n * THE L0 faults, in first-match-wins order. D/X/U/K are decided in POSIX sh BEFORE the bin runs (a\n * stale, missing or broken validator cannot be trusted to validate itself); S/C/Y are decided inside\n * the bin, in JS. One model, two enforcement points.\n */\nexport const L0_FAULTS: readonly L0Fault[] = [\n new L0Fault(L0_FAULT_DRIFT, 'version drift — root package.json pin != installed version',\n 'sh, before the bin runs', 'sh',\n [\n // `pnpm install` clears D in BOTH directions — it makes installed == pin by definition — so\n // it is always the preferred cure. The direction only decides whether the PIN is the version\n // you WANT, which is what the second cure is for.\n bashCure('pnpm install', true,\n 'node_modules is OLDER than the pin, OR you are on a feature branch and want YOUR '\n + 'branch pin (usually the case) — it always clears the drift'),\n // The on-main sync is spelled `git checkout main && git pull origin main` and NOT `git pull`:\n // a raw pull on a FEATURE branch merges main into it and destroys the fork point, so it is\n // no longer on the L0 allowlist at all (see CHECKOUT_MAIN_PULL_BODY_ERE). This spelling ends\n // ON main, which is why it is safe from any branch — and it is a no-op checkout when you are\n // already there.\n //\n // And it is RAW GIT on purpose, where the workflow guards now say `pnpm wp-sync-main`\n // instead. The fault being cured here is `node_modules` disagreeing with the pin, so\n // `node_modules` is the untrustworthy thing — and every `pnpm wp-*` bin resolves through it.\n // An L0 cure may never be a command that has to load the package it is repairing. See the\n // long note on CHECKOUT_MAIN_PULL_BODY_ERE in bin/l0-allowlist.ts.\n bashCure(CHECKOUT_MAIN_PULL_CMD, false,\n 'node_modules is NEWER than the pin AND you are on main — the PIN is the stale side, so '\n + 'sync first and install second; a bare install would downgrade you'),\n ], renderShim()),\n new L0Fault(L0_FAULT_BIN_MISSING, 'guard bin missing (fresh clone / new worktree / package removed)',\n 'sh, before the bin runs', 'sh',\n [bashCure('pnpm install', true,\n 'this fault fires at all — nothing is installed in THIS tree, and a new git worktree '\n + 'copies no node_modules')],\n renderShim()),\n // U is X with the ONE input that inverts X's cure, which is why it is a separate fault and not a\n // sentence inside X's message: a BARE `pnpm install` is not a weaker fix here, it is a PROVABLE\n // no-op, and an agent that trusts the X text will run it until it gives up. See the ADD_HOOK_PKG\n // entry in l0-allowlist.ts for the incident.\n //\n // The ORDER of these three is the correction bought by a second incident. `pnpm add` used to be the\n // preferred cure, and it is the one cure that CANNOT be committed: a direct root dependency on\n // HOOK_PKG violates the umbrella rule (the root manifest depends on the umbrella ALONE), and the\n // message never said to revert it — so it was followed, and the workaround landed in a real\n // package.json. The package normally arrives WITH the umbrella, so a tree that is merely BEHIND is\n // the common cause and syncing is the cure that leaves a committable tree. The add stays last,\n // reachable, and explicitly labelled as a session unblock rather than a fix.\n new L0Fault(L0_FAULT_UNDECLARED, `guard bin missing AND ${HOOK_PKG} is not declared in package.json`,\n 'sh, before the bin runs', 'sh',\n [\n bashCure(CHECKOUT_MAIN_PULL_CMD, true,\n `${HOOK_PKG} arrives WITH the umbrella, so a tree that is behind explains this without `\n + 'anything being mis-declared — sync first, then install'),\n bashCure('pnpm install', false,\n 'the sync (or a raised catalog pin in ' + WORKSPACE_MANIFEST + ', which is editable '\n + 'while the block is up) gave the installer something new to do — a BARE install with '\n + 'nothing changed reports \"Lockfile is up to date\" and leaves the tree as broken as it '\n + 'found it'),\n bashCure(ADD_HOOK_PKG_CMD, false,\n 'nothing else worked and you need this session back — it is a session unblock, NOT a '\n + `fix: revert it with 'pnpm remove ${HOOK_PKG}' before committing, because a direct `\n + 'root dependency on it violates the umbrella rule'),\n ],\n renderShim()),\n new L0Fault(L0_FAULT_BIN_BROKEN, 'guard bin present but CRASHED (exit code not 0 or 2 — corrupt node_modules)',\n 'sh, before the bin runs', 'sh',\n [bashCure(RECOVERY_CMD, true,\n 'this fault fires at all — a BARE pnpm install SKIPS the corrupt package, because pnpm sees '\n + 'the right version on disk and considers it installed; only the delete forces a rewrite')],\n renderShim()),\n // S covers the WHOLE managed hook surface, not just the shim: the committed ai-hook.sh, the\n // .claude/settings.json entries that register them AND the managed `env` entry that pins the Bash\n // cwd so the hooks resolve identically for every subagent. They only work as a set — a settings file left\n // on a superseded form silently changes who governs, re-pinning every tree to the\n // primary's release — and nothing validated the registration at all before it joined this fault.\n new L0Fault(L0_FAULT_SHIM_STALE, 'a webpieces-managed hook file, one of the harness hook registrations (.claude/settings.json, .codex/hooks.json) or the managed env entry does not match this release',\n 'the guard bin', 'JS',\n [\n // wp-upgrade-shim leads because it is the ONLY cure that repairs EVERY managed surface, and\n // it is still surgical: it rewrites ai-hook.sh, each harness's registration and the env entry\n // and touches no config, and it imports only fs/path so it runs on a tree too broken to load\n // the rule engine. The INSTALLER is deliberately NOT a cure here: it also migrates the config\n // and prompts for a target twice, which hangs a non-interactive agent.\n bashCure(UPGRADE_SHIM_CMD, true,\n 'this fault fires at all — it is the only cure that repairs EVERY managed surface '\n + '(ai-hook.sh, each harness hook registration, the anchoring of the neighbour hook '\n + 'commands registered beside ours, and the Claude settings env entry), and it also '\n + 'deletes the retired guarantee-root.sh and any entry still naming it, and it '\n + 'touches no config; needs installed @webpieces/ai-hook-rules 0.4.408 or newer'),\n // 2026-07-21: the version gap below caused a real \"command not found\" deadlock.\n bashCure(RESTORE_SHIM_CMD, false,\n 'the installed @webpieces/ai-hook-rules is OLDER than 0.4.408, so wp-upgrade-shim does '\n + 'not exist yet — it is PARTIAL (it repairs ai-hook.sh and NOTHING else), so upgrade '\n + '@webpieces afterwards and run Option 1 to finish'),\n ],\n // The SAMPLE deny renders EVERY managed surface, built from HARNESS_REGISTRATIONS rather than\n // a hand-written trio — a doc that shows a three-surface deny while the guard can report four\n // is exactly the drift this generated-doc arrangement exists to make impossible.\n shimStaleDenyReason('', '', [\n SHIM_MARKER,\n ...HARNESS_REGISTRATIONS.flatMap((h: HarnessRegistration): readonly string[] => [h.registrationSurface, h.neighbourSurface]),\n ENV_SURFACE,\n ], false)),\n new L0Fault(L0_FAULT_CONFIG_MISSING, `${CONFIG_FILENAME} missing`,\n 'the guard bin', 'JS',\n [\n CONFIG_WRITE_CURE,\n // Kept, but demoted: it seeds the file and then PROMPTS twice for a hook target.\n bashCure(INSTALL_HOOKS_CMD, false,\n 'you are at an INTERACTIVE terminal and can answer its two hook-target prompts'),\n ], CONFIG_MISSING_REPORT),\n new L0Fault(L0_FAULT_CONFIG_OUT_OF_SYNC, `a loaded rule has no ${CONFIG_FILENAME} key`,\n 'the guard bin', 'JS',\n [CONFIG_WRITE_CURE], CONFIG_OUT_OF_SYNC_HEADER),\n];\n\n/**\n * One fault's FIX section, rendered from its `cures` array — literal commands only, never prose.\n *\n * This is the half that used to live in hand-written docs and drift. The three fields of L0Cure map\n * onto the three things a blocked reader needs and nothing else: WHAT to type (the call), WHETHER it is\n * the default (preferred), and WHEN to pick a sibling instead (discriminator). A cure with no\n * discriminator would render as a menu of equals, which is how an agent picks the wrong one.\n */\n// webpieces-disable no-function-outside-class -- pure string builder for renderGuardMatrixDoc below, beside the arrays it reads\nfunction renderFixSection(fault: L0Fault): string[] {\n const options = fault.cures.map((cure: L0Cure, i: number): string => {\n // A Bash cure is the command verbatim; a tool-shaped one is the file it edits (allowlist entry 2).\n const literal = cure.isCommand() ? `\\`${cure.call.command}\\`` : `edit \\`${cure.mention}\\` yourself`;\n const label = cure.preferred ? `Option ${i + 1} (preferred)` : `Option ${i + 1}`;\n return `- **${label}**: ${literal} ← pick this when ${cure.discriminator}`;\n });\n return [`### \\`${fault.code}\\` — ${fault.name}`, '', ...options, ''];\n}\n\n/**\n * Render webpieces.guard-matrix.md from L0_FAULTS + L0_ALLOWLIST.\n *\n * A unit test locks the committed template byte-identical to this output, the same way\n * templates/ai-hook.sh is locked to renderShim(). That is what makes the doc assertable instead of\n * aspirational: the table in the doc IS the array the guard consults.\n */\n// webpieces-disable no-function-outside-class -- pure string builder over the two exported arrays, beside them in this module\nexport function renderGuardMatrixDoc(): string {\n return [\n '# webpieces guard matrix — L0 (tooling integrity)',\n '',\n 'GENERATED from `L0_FAULTS` + `L0_ALLOWLIST` in `@webpieces/ai-hook-rules`. Do not hand-edit —',\n 'a unit test locks this file byte-identical to `renderGuardMatrixDoc()`, so the table below is',\n 'the array the guard actually consults, not a description of it.',\n '',\n 'L0 is the OUTERMOST guard 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. If you are',\n 'reading this, one of the faults below fired and named this file.',\n '',\n '## The faults',\n '',\n 'THE JOIN KEYS ARE `guard`, `fault=` and `row=`. Every L0 deny opens',\n '`[<guard>] (layer=L0 fault=<code> row=3, …)`, and every audit line — from BOTH halves of L0, the',\n '`sh` shim and the guard bin — carries `layer=L0 row=<n> fault=<code>`. So one grep lands you in',\n 'the deny, the log line and the row below. The guard names come from `L0_FAULT_NAMES` and the row',\n 'numbers from `L0_ROW_*`, both spelled in exactly one place (`core/l0-fault-codes.ts`).',\n '',\n '| code | guard | fault | detected by | enforced in |',\n '|---|---|---|---|---|',\n ...L0_FAULTS.map((f: L0Fault): string =>\n `| \\`${f.code}\\` | \\`${L0_FAULT_NAMES[f.code]}\\` | ${f.name} | ${f.detectedBy} | ${f.enforcedIn} |`),\n '',\n 'First match wins. `D`/`X`/`U`/`K` are decided in POSIX `sh` inside the committed shim, BEFORE the',\n 'guard bin runs — a stale, missing or broken validator cannot be trusted to validate itself.',\n '',\n '## The fix, per fault',\n '',\n 'Every command below is rendered from that fault\\'s `cures` array and is asserted, by unit test,',\n 'to be accepted by `isAllowed()` — so nothing here can be a command the guard then rejects. Type',\n 'the option you pick EXACTLY as written and run nothing else on that line.',\n '',\n ...L0_FAULTS.flatMap(renderFixSection),\n ...renderMatrixAndAllowlist(),\n ].join('\\n');\n}\n\n/**\n * The second half of the doc: the three-row matrix and the ONE allowlist. Split out of\n * renderGuardMatrixDoc solely to keep it inside the method-line budget — the join order is what makes\n * the two halves one file, so keep them adjacent and keep the byte-lock test as the arbiter.\n */\n// webpieces-disable no-function-outside-class -- second half of renderGuardMatrixDoc's string, beside it in this module\nfunction renderMatrixAndAllowlist(): string[] {\n return [\n '## The matrix',\n '',\n 'L0 has NO genuine second dimension. Every branch reduces to one question:',\n '',\n '| # | fault | on the allowlist? | outcome |',\n '|---|---|---|---|',\n '| 1 | none | — | hand down to the next guard layer |',\n `| ${L0_ROW_ALLOWLISTED} | any | yes | PASS or ALLOW (see the entry) |`,\n `| ${L0_ROW_BLOCKED} | any | no | BLOCK — **only the message varies by fault** |`,\n '',\n `Row ${L0_ROW_BLOCKED} is the only row that blocks, so every L0 deny cites it — \\`row=${L0_ROW_BLOCKED}\\` in the`,\n 'deny header, `row=' + L0_ROW_BLOCKED + '` on the audit line, and this row here. Same numbers as L1 uses for',\n 'its own rows (see `L1_ROWS`), and for the same reason: a row number is IDENTITY, so it is never reused.',\n '',\n 'The tool is not a dimension either: \"any Read\" is an allowlist ENTRY, not a tool check.',\n '',\n '## The allowlist',\n '',\n 'ONE list, consulted identically by every fault. A cure that cannot help a given fault also',\n 'cannot hurt it, and gating each entry on a fault is what produced four real defects (a stale',\n 'shim that denied `pnpm install` and every git sync; faults that denied every Read; a config fault',\n 'that denied `rm -rf node_modules && pnpm install` while allowing a bare `pnpm install`).',\n '',\n '| # | allowed | outcome |',\n '|---|---|---|',\n ...L0_ALLOWLIST.map((e: L0AllowEntry, i: number): string => `| ${i + 1} | ${e.label} | ${e.kind.toUpperCase()} |`),\n '',\n '- **PASS** — L0 has no objection; the call falls THROUGH so the downstream guards still judge it.',\n '- **ALLOW** — terminal; bypasses everything, because a cure must stay reachable even when a',\n ' downstream guard would block it.',\n '',\n 'Every Bash entry is anchored to the WHOLE command. Appending `&& git status` makes it a DIFFERENT',\n 'command and it is rejected again — that is not the guard refusing its own cure.',\n '',\n 'On an UNGATED entry a leading `cd <dir> &&`, a trailing `2>&1` and a pipe into `tail`/`head` are',\n 'tolerated; nothing else is. **The harness-gated read row tolerates NONE of the three** — not the',\n '`cd` prefix, not the redirect, not the pipe — so `cat f 2>&1` and `cat f | head -20` are both',\n 'rejected. That is deliberate, not an oversight: the row means exactly what',\n '`core/shell-read-parity.ts` means by \"this is a read\", and that module treats a pipe or a redirect',\n 'as proof the command is more than one. Type the bare command.',\n '',\n '`git merge` is deliberately NOT on this list. Main is merged ONLY through the 3-point fork merge',\n '(`pnpm wp-start-update`, or `pnpm wp-start-upsert-pr` when a PR is already open).',\n '',\n '## Known asymmetry',\n '',\n 'Under `S`/`C`/`Y` the guard bin IS running, so a PASS really does fall through to the downstream',\n 'guards. Under `D`/`X`/`U`/`K` the bin is never executed, so there is nothing to fall through to and a',\n 'PASS degenerates into a terminal allow — reads are unguarded during those three faults.',\n '',\n '## Widening L0',\n '',\n 'Add an entry to `L0_ALLOWLIST` in `packages/tooling/ai-hook-rules/src/bin/shim.ts`. That array is',\n 'the single source for the JS allowlist, the `grep -E` inside the rendered shim, and this file.',\n '',\n ];\n}\n\n/**\n * Drop the matrix doc where the AI can read it, and return its absolute path ('' if it could not be\n * written). Called from the L0 BLOCK path so the deny can say `READ <path>`.\n *\n * Best-effort by design: this runs while the tree is already known-broken, and a missing template (an\n * @webpieces/rules-config older than this package) must degrade the deny message, never replace it\n * with a crash.\n */\n// webpieces-disable no-function-outside-class -- sibling of renderGuardMatrixDoc in this module\nexport function writeGuardMatrixDoc(workspaceRoot: string): string {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return writeTemplate(workspaceRoot, GUARD_MATRIX_DOC);\n } catch (err: unknown) {\n const error = toError(err);\n void error; // best-effort: no doc → the deny simply omits the pointer\n return '';\n }\n}\n\n/**\n * The `READ <path>` pointer appended to an L0 deny, or '' when the doc could not be written.\n *\n * It opens with a NEWLINE, not a space: the JS-side L0 denies render in the house format now (a header,\n * a `[guard-name]` block, `Fix Option N:` lines), so a pointer glued onto the end of the last line would\n * be the one place the shape broke. A real newline is safe on both call paths — denyJson() JSON.stringifies\n * it, exactly as it does for every multi-line L1 report.\n */\n// webpieces-disable no-function-outside-class -- sibling of writeGuardMatrixDoc in this module\nexport function guardMatrixPointer(docPath: string): string {\n if (docPath === '') return '';\n return `\\nThe full L0 guard matrix - every fault and everything that is allowed through - is at ${docPath}; READ it if you are unsure why this call was blocked.`;\n}\n"]}
|
|
@@ -178,7 +178,7 @@ class L0ToolingDoc {
|
|
|
178
178
|
managedSurface() {
|
|
179
179
|
const surfaces = [
|
|
180
180
|
hook_registration_1.SHIM_SURFACE,
|
|
181
|
-
...hook_registration_1.HARNESS_REGISTRATIONS.
|
|
181
|
+
...hook_registration_1.HARNESS_REGISTRATIONS.flatMap((h) => [h.registrationSurface, h.neighbourSurface]),
|
|
182
182
|
hook_registration_1.ENV_SURFACE,
|
|
183
183
|
];
|
|
184
184
|
const registrations = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"l0-tooling-doc.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/l0-tooling-doc.ts"],"names":[],"mappings":"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","sourcesContent":["import { CONFIG_FILENAME, LOGS_STATE_DIR, WEBPIECES_TMP_DIR, WORKTREE_STATE_DIR } from '@webpieces/rules-config';\n\nimport {\n L0AllowEntry, L0_ALLOWLIST, RESTORE_SHIM_CMD, SHIM_LOG_FIELDS, SHIM_LOG_VERDICTS, ShimLogField,\n ShimLogVerdict, UPGRADE_SHIM_CMD,\n} from '../bin/shim';\nimport {\n ENV_SURFACE, GUARDS_BIN, RULES_BIN, SHIM_SURFACE, HARNESS_REGISTRATIONS, HarnessRegistration,\n} from '../bin/hook-registration';\nimport {\n L0FaultCode, L0_FAULT_NAMES, L0_JS_FAULT_CODES, L0_LAYER, L0_ROW_ALLOWLISTED, L0_ROW_BLOCKED,\n L0_ROW_HANDED_DOWN, L0_SH_FAULT_CODES,\n} from './l0-fault-codes';\nimport {\n CALLS_STREAM, L0_SHIM_STREAM, L1_LOCATION_STREAM, L2_DECISIONS_STREAM, REJECTIONS_STREAM,\n} from './log-streams';\nimport { L0Cure, L0Fault, L0_FAULTS } from './l0-matrix';\n\n// ---------------------------------------------------------------------------\n// THE GENERATED HALF OF guards/L0-tooling.md.\n//\n// That file is the largest guard doc in the repo and, unlike its two siblings, it was hand-written from\n// end to end — so it went stale twice in one session: it described FOUR managed surfaces after there\n// were three, and a 7-field audit line after `shim=`/`bin=`/`layer=`/`row=` had joined it.\n//\n// The split, and WHY it is a split rather than a whole-file renderer:\n//\n// GENERATED (here) anything that is a COORDINATE of the code — the fault codes and their guard\n// names, the cures, the three matrix rows, the allowlist, the managed surfaces,\n// the audit-line fields and the verdict vocabulary. Every one of those is\n// already an array somewhere; a doc that re-types them is a second spelling.\n// HAND-WRITTEN (there) the incident histories, the config-validation invariant, the known gaps, the\n// worked example. That is argument, not data, and a renderer would mangle it.\n//\n// The section is spliced BETWEEN two literal markers, the pattern renderL1Doc() already uses for its\n// prose/table interleave, so the prose surrounds the tables instead of being swallowed by them.\n// `pnpm guards:generate` rewrites the block and `l0-tooling-doc.spec.ts` locks it byte-for-byte.\n//\n// EVERY COMMAND PRINTED HERE COMES FROM A CONSTANT, never a retyped literal: the L0 allowlist matches\n// WHOLE command strings, so a paraphrased cure in a doc is an unrunnable cure. The spec re-asserts that\n// by running isAllowed() over every command this renderer prints.\n// ---------------------------------------------------------------------------\n\n/** Opening marker of the generated block, as it appears in guards/L0-tooling.md. */\nexport const L0_DOC_BEGIN = '<!-- BEGIN GENERATED — L0ToolingDoc.render() in ai-hook-rules/src/core/l0-tooling-doc.ts; run `pnpm guards:generate` -->';\n\n/** Closing marker. Everything between the two is machine-owned; everything outside is prose. */\nexport const L0_DOC_END = '<!-- END GENERATED — hand-written prose resumes here -->';\n\n/**\n * The generated section of guards/L0-tooling.md, and the splice that puts it there.\n *\n * A class rather than a family of module functions (see l1-doc.ts, which predates the rule): the\n * renderer, the extractor and the splicer are one unit, and the spec drives all three.\n */\nexport class L0ToolingDoc {\n /** The whole generated block, WITHOUT the markers — those belong to the file, not to the renderer. */\n render(): string {\n return [\n ...this.preamble(),\n ...this.faultTable(),\n ...this.fixTable(),\n ...this.matrixTable(),\n ...this.allowlistTable(),\n ...this.managedSurface(),\n ...this.auditLine(),\n ].join('\\n');\n }\n\n /**\n * The generated text of `doc`, exactly as committed. Throws when a marker is missing or doubled —\n * a silently-unspliced doc is the drift this whole arrangement exists to end.\n */\n extract(doc: string): string {\n const opens = doc.split(L0_DOC_BEGIN).length - 1;\n const closes = doc.split(L0_DOC_END).length - 1;\n if (opens !== 1 || closes !== 1) {\n throw new Error(`guards/L0-tooling.md must carry exactly one BEGIN/END marker pair, found ${String(opens)}/${String(closes)}`);\n }\n const afterBegin = doc.slice(doc.indexOf(L0_DOC_BEGIN) + L0_DOC_BEGIN.length);\n return afterBegin.slice(0, afterBegin.indexOf(L0_DOC_END)).replace(/^\\n/, '').replace(/\\n$/, '');\n }\n\n /** `doc` with the generated block replaced by today's render. Preserves every byte outside it. */\n splice(doc: string): string {\n const head = doc.slice(0, doc.indexOf(L0_DOC_BEGIN) + L0_DOC_BEGIN.length);\n const tail = doc.slice(doc.indexOf(L0_DOC_END));\n // extract() is called for its VALIDATION — one marker pair — before anything is rewritten.\n this.extract(doc);\n return `${head}\\n${this.render()}\\n${tail}`;\n }\n\n /** A markdown cell: a literal `|` inside a value would end the column. */\n private cell(value: string): string {\n return value.split('|').join('\\\\|');\n }\n\n private preamble(): string[] {\n return [\n '> **GENERATED — do not hand-edit between the markers.** Rendered by `L0ToolingDoc.render()`',\n '> (`ai-hook-rules/src/core/l0-tooling-doc.ts`) from `L0_FAULTS`, `L0_ALLOWLIST`, the managed-surface',\n '> constants and `SHIM_LOG_FIELDS` — the same arrays the guard consults. `pnpm guards:generate`',\n '> rewrites it; `l0-tooling-doc.spec.ts` locks it byte-for-byte. The prose outside the markers is',\n '> hand-written and stays that way.',\n '',\n ];\n }\n\n private faultTable(): string[] {\n return [\n '### The faults',\n '',\n '| code | guard name | fault | detected by | enforced in |',\n '|---|---|---|---|---|',\n ...L0_FAULTS.map((f: L0Fault): string =>\n `| \\`${f.code}\\` | \\`${L0_FAULT_NAMES[f.code as L0FaultCode]}\\` | ${this.cell(f.name)} | ${f.detectedBy} | ${f.enforcedIn} |`),\n '',\n `First match wins. \\`${L0_SH_FAULT_CODES.join('`/`')}\\` are decided in POSIX \\`sh\\` inside the committed shim, BEFORE`,\n `the guard bin runs — a stale, missing or broken validator cannot be trusted to validate itself.`,\n `\\`${L0_JS_FAULT_CODES.join('`/`')}\\` are decided inside the bin, in JS.`,\n '',\n ];\n }\n\n /**\n * The cures, one row per option. LITERAL commands only, rendered from `L0_FAULTS[].cures` — the same\n * array `webpieces.guard-matrix.md` renders its Fix sections from, so the two can never prescribe\n * different commands for one fault.\n */\n private fixTable(): string[] {\n const rows: string[] = [];\n for (const fault of L0_FAULTS) {\n fault.cures.forEach((cure: L0Cure, i: number): void => {\n const literal = cure.isCommand() ? `\\`${cure.call.command}\\`` : `edit \\`${cure.mention}\\` yourself`;\n const option = `${String(i + 1)}${cure.preferred ? ' (preferred)' : ''}`;\n rows.push(`| \\`${fault.code}\\` | ${option} | ${this.cell(literal)} | ${this.cell(cure.discriminator)} |`);\n });\n }\n return [\n '### The fix, per fault — type the option EXACTLY as written, and run nothing else on that line',\n '',\n '| fault | option | run EXACTLY | pick this when |',\n '|---|---|---|---|',\n ...rows,\n '',\n ];\n }\n\n private matrixTable(): string[] {\n return [\n '### The matrix — three rows, and the fault only picks the MESSAGE',\n '',\n `| row | fault | on the allowlist? | outcome | logged as |`,\n '|---|---|---|---|---|',\n `| ${L0_ROW_HANDED_DOWN} | none | — | hand down to the next guard layer | \\`layer=${L0_LAYER} row=${L0_ROW_HANDED_DOWN}\\` |`,\n `| ${L0_ROW_ALLOWLISTED} | any | yes | PASS or ALLOW (see the entry) | \\`layer=${L0_LAYER} row=${L0_ROW_ALLOWLISTED}\\` |`,\n `| ${L0_ROW_BLOCKED} | any | no | BLOCK — **only the message varies by fault** | \\`layer=${L0_LAYER} row=${L0_ROW_BLOCKED}\\` |`,\n '',\n 'The tool is not a dimension either: \"any Read\" is an allowlist ENTRY, not a tool check. Those are',\n 'the same coordinates every L0 deny opens with, so a deny, a log line and this table join by eye.',\n '',\n ];\n }\n\n private allowlistTable(): string[] {\n return [\n '### The allowlist — ONE list, consulted identically by every fault',\n '',\n '| # | allowed | outcome | bypasses L1 on a HEALTHY tree? |',\n '|---|---|---|---|',\n ...L0_ALLOWLIST.map((e: L0AllowEntry, i: number): string =>\n `| ${String(i + 1)} | ${this.cell(e.label)} | ${e.kind.toUpperCase()} | ${e.cure ? 'yes — it REPAIRS the tooling' : 'no — it repairs nothing, so L1 still judges it'} |`),\n '',\n '- **PASS** — L0 has no objection; the call falls THROUGH so downstream guards still judge it.',\n '- **ALLOW** — terminal; bypasses everything, because a cure must stay reachable even when a',\n ' downstream guard would block it.',\n '',\n 'Every Bash entry is anchored to the WHOLE command. Appending `&& git status` makes it a DIFFERENT',\n 'command and it is rejected again — that is not the guard refusing its own cure.',\n '',\n 'On an UNGATED entry a leading `cd <dir> &&`, a trailing `2>&1` and a pipe into `tail`/`head` are',\n 'tolerated; nothing else. **The harness-gated read row tolerates NONE of the three** — not the',\n '`cd` prefix, not the redirect, not the pipe — so `cat f 2>&1` and `cat f | head -20` are both',\n 'rejected. That is deliberate, not an oversight: the row means exactly what',\n '`core/shell-read-parity.ts` means by \"this is a read\", and that module treats a pipe or a',\n 'redirect as proof the command is more than one. Type the bare command.',\n '',\n '`git merge` and a **bare** `git pull` are both deliberately absent — see \"The git-sync split\"',\n 'below for why the one safe pull spelling is on the list and the bare one is not. Main is merged',\n 'ONLY through the 3-point fork merge (`pnpm wp-start-update`, or `pnpm wp-start-upsert-pr` when a',\n 'PR is already open).',\n '',\n ];\n }\n\n /**\n * Fault `S`'s subject: the managed things, and every registration rendered from the harness's own\n * `shimCommand()` — which is what makes \"they are ABSOLUTE\" a fact this doc cannot get wrong, and\n * what keeps it from describing Claude Code's matcher as if it were Codex's.\n */\n private managedSurface(): string[] {\n const surfaces = [\n SHIM_SURFACE,\n ...HARNESS_REGISTRATIONS.map((h: HarnessRegistration): string => h.registrationSurface),\n ENV_SURFACE,\n ];\n const registrations: string[] = [];\n for (const harness of HARNESS_REGISTRATIONS) {\n registrations.push(`# ${harness.label}`);\n registrations.push(harness.shimCommand(GUARDS_BIN));\n registrations.push(harness.shimCommand(RULES_BIN));\n }\n return [\n `### The managed hook surface — what fault \\`S\\` compares (${String(surfaces.length)} things, one set)`,\n '',\n '| # | surface |',\n '|---|---|',\n // The shim is a bare PATH and the others are prose naming a file, so only the first is\n // wrapped in code ticks — the same rendering this table has always had, now generated.\n ...surfaces.map((surface: string, i: number): string =>\n `| ${String(i + 1)} | ${i === 0 ? `\\`${surface}\\`` : this.cell(surface)} |`),\n '',\n 'The registration is TWO PreToolUse entries per harness, and all of them are ABSOLUTE — they',\n 'resolve from any cwd:',\n '',\n '```',\n ...registrations,\n '```',\n '',\n `\\`${UPGRADE_SHIM_CMD}\\` repairs all ${String(surfaces.length)}. \\`${RESTORE_SHIM_CMD}\\``,\n `repairs \\`${SHIM_SURFACE}\\` and nothing else, so it is the fallback for an installed release too old`,\n 'to carry the first.',\n '',\n ];\n }\n\n /**\n * The audit line, rendered from `SHIM_LOG_FIELDS` + `SHIM_LOG_VERDICTS`. The optional field prints\n * in brackets because that is exactly what it is: `bin=` appears only when it differs from `shim=`.\n */\n private auditLine(): string[] {\n const shape = SHIM_LOG_FIELDS.map((f: ShimLogField): string => (f.optional ? `[${f.label}]` : f.label)).join(' ');\n return [\n '### The L0 audit line — one tab-separated line per tool call',\n '',\n '```',\n shape,\n '```',\n '',\n '| # | field | means |',\n '|---|---|---|',\n ...SHIM_LOG_FIELDS.map((f: ShimLogField, i: number): string =>\n `| ${String(i + 1)} | \\`${this.cell(f.label)}\\` | ${this.cell(f.means)} |`),\n '',\n '| verdict | means |',\n '|---|---|',\n ...SHIM_LOG_VERDICTS.map((v: ShimLogVerdict): string => `| \\`${v.label}\\` | ${this.cell(v.means)} |`),\n '',\n ...this.logPaths(),\n ];\n }\n\n private logPaths(): string[] {\n const stream = `${LOGS_STATE_DIR}/${L0_SHIM_STREAM}/<session>-<agent|coordinator>-<binName>.log`;\n return [\n 'It lands in the log directory of the tree the CALL was made in, centralized under the primary clone',\n 'so that removing a worktree does not take its audit trail with it:',\n '',\n '```',\n `<primary>/${WEBPIECES_TMP_DIR}/${WORKTREE_STATE_DIR}/<tree>/${stream}`,\n `<primary>/${WEBPIECES_TMP_DIR}/${stream} # from the primary clone itself`,\n '```',\n '',\n `The binary stamps the same \\`layer=\\`/\\`row=\\`/\\`fault=\\` fields onto its OWN streams —`,\n `\\`${L1_LOCATION_STREAM}/\\`, \\`${L2_DECISIONS_STREAM}/\\`, \\`${CALLS_STREAM}/\\` and \\`${REJECTIONS_STREAM}/\\` under the same`,\n `\\`${LOGS_STATE_DIR}/\\` — so one grep spans the whole trail. A \\`${CONFIG_FILENAME}\\` fault (\\`C\\`/\\`Y\\`) is`,\n 'therefore visible there and never on an `L0-shim` line, which only ever carries the `sh`-side codes.',\n '',\n ];\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"l0-tooling-doc.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/l0-tooling-doc.ts"],"names":[],"mappings":"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","sourcesContent":["import { CONFIG_FILENAME, LOGS_STATE_DIR, WEBPIECES_TMP_DIR, WORKTREE_STATE_DIR } from '@webpieces/rules-config';\n\nimport {\n L0AllowEntry, L0_ALLOWLIST, RESTORE_SHIM_CMD, SHIM_LOG_FIELDS, SHIM_LOG_VERDICTS, ShimLogField,\n ShimLogVerdict, UPGRADE_SHIM_CMD,\n} from '../bin/shim';\nimport {\n ENV_SURFACE, GUARDS_BIN, RULES_BIN, SHIM_SURFACE, HARNESS_REGISTRATIONS, HarnessRegistration,\n} from '../bin/hook-registration';\nimport {\n L0FaultCode, L0_FAULT_NAMES, L0_JS_FAULT_CODES, L0_LAYER, L0_ROW_ALLOWLISTED, L0_ROW_BLOCKED,\n L0_ROW_HANDED_DOWN, L0_SH_FAULT_CODES,\n} from './l0-fault-codes';\nimport {\n CALLS_STREAM, L0_SHIM_STREAM, L1_LOCATION_STREAM, L2_DECISIONS_STREAM, REJECTIONS_STREAM,\n} from './log-streams';\nimport { L0Cure, L0Fault, L0_FAULTS } from './l0-matrix';\n\n// ---------------------------------------------------------------------------\n// THE GENERATED HALF OF guards/L0-tooling.md.\n//\n// That file is the largest guard doc in the repo and, unlike its two siblings, it was hand-written from\n// end to end — so it went stale twice in one session: it described FOUR managed surfaces after there\n// were three, and a 7-field audit line after `shim=`/`bin=`/`layer=`/`row=` had joined it.\n//\n// The split, and WHY it is a split rather than a whole-file renderer:\n//\n// GENERATED (here) anything that is a COORDINATE of the code — the fault codes and their guard\n// names, the cures, the three matrix rows, the allowlist, the managed surfaces,\n// the audit-line fields and the verdict vocabulary. Every one of those is\n// already an array somewhere; a doc that re-types them is a second spelling.\n// HAND-WRITTEN (there) the incident histories, the config-validation invariant, the known gaps, the\n// worked example. That is argument, not data, and a renderer would mangle it.\n//\n// The section is spliced BETWEEN two literal markers, the pattern renderL1Doc() already uses for its\n// prose/table interleave, so the prose surrounds the tables instead of being swallowed by them.\n// `pnpm guards:generate` rewrites the block and `l0-tooling-doc.spec.ts` locks it byte-for-byte.\n//\n// EVERY COMMAND PRINTED HERE COMES FROM A CONSTANT, never a retyped literal: the L0 allowlist matches\n// WHOLE command strings, so a paraphrased cure in a doc is an unrunnable cure. The spec re-asserts that\n// by running isAllowed() over every command this renderer prints.\n// ---------------------------------------------------------------------------\n\n/** Opening marker of the generated block, as it appears in guards/L0-tooling.md. */\nexport const L0_DOC_BEGIN = '<!-- BEGIN GENERATED — L0ToolingDoc.render() in ai-hook-rules/src/core/l0-tooling-doc.ts; run `pnpm guards:generate` -->';\n\n/** Closing marker. Everything between the two is machine-owned; everything outside is prose. */\nexport const L0_DOC_END = '<!-- END GENERATED — hand-written prose resumes here -->';\n\n/**\n * The generated section of guards/L0-tooling.md, and the splice that puts it there.\n *\n * A class rather than a family of module functions (see l1-doc.ts, which predates the rule): the\n * renderer, the extractor and the splicer are one unit, and the spec drives all three.\n */\nexport class L0ToolingDoc {\n /** The whole generated block, WITHOUT the markers — those belong to the file, not to the renderer. */\n render(): string {\n return [\n ...this.preamble(),\n ...this.faultTable(),\n ...this.fixTable(),\n ...this.matrixTable(),\n ...this.allowlistTable(),\n ...this.managedSurface(),\n ...this.auditLine(),\n ].join('\\n');\n }\n\n /**\n * The generated text of `doc`, exactly as committed. Throws when a marker is missing or doubled —\n * a silently-unspliced doc is the drift this whole arrangement exists to end.\n */\n extract(doc: string): string {\n const opens = doc.split(L0_DOC_BEGIN).length - 1;\n const closes = doc.split(L0_DOC_END).length - 1;\n if (opens !== 1 || closes !== 1) {\n throw new Error(`guards/L0-tooling.md must carry exactly one BEGIN/END marker pair, found ${String(opens)}/${String(closes)}`);\n }\n const afterBegin = doc.slice(doc.indexOf(L0_DOC_BEGIN) + L0_DOC_BEGIN.length);\n return afterBegin.slice(0, afterBegin.indexOf(L0_DOC_END)).replace(/^\\n/, '').replace(/\\n$/, '');\n }\n\n /** `doc` with the generated block replaced by today's render. Preserves every byte outside it. */\n splice(doc: string): string {\n const head = doc.slice(0, doc.indexOf(L0_DOC_BEGIN) + L0_DOC_BEGIN.length);\n const tail = doc.slice(doc.indexOf(L0_DOC_END));\n // extract() is called for its VALIDATION — one marker pair — before anything is rewritten.\n this.extract(doc);\n return `${head}\\n${this.render()}\\n${tail}`;\n }\n\n /** A markdown cell: a literal `|` inside a value would end the column. */\n private cell(value: string): string {\n return value.split('|').join('\\\\|');\n }\n\n private preamble(): string[] {\n return [\n '> **GENERATED — do not hand-edit between the markers.** Rendered by `L0ToolingDoc.render()`',\n '> (`ai-hook-rules/src/core/l0-tooling-doc.ts`) from `L0_FAULTS`, `L0_ALLOWLIST`, the managed-surface',\n '> constants and `SHIM_LOG_FIELDS` — the same arrays the guard consults. `pnpm guards:generate`',\n '> rewrites it; `l0-tooling-doc.spec.ts` locks it byte-for-byte. The prose outside the markers is',\n '> hand-written and stays that way.',\n '',\n ];\n }\n\n private faultTable(): string[] {\n return [\n '### The faults',\n '',\n '| code | guard name | fault | detected by | enforced in |',\n '|---|---|---|---|---|',\n ...L0_FAULTS.map((f: L0Fault): string =>\n `| \\`${f.code}\\` | \\`${L0_FAULT_NAMES[f.code as L0FaultCode]}\\` | ${this.cell(f.name)} | ${f.detectedBy} | ${f.enforcedIn} |`),\n '',\n `First match wins. \\`${L0_SH_FAULT_CODES.join('`/`')}\\` are decided in POSIX \\`sh\\` inside the committed shim, BEFORE`,\n `the guard bin runs — a stale, missing or broken validator cannot be trusted to validate itself.`,\n `\\`${L0_JS_FAULT_CODES.join('`/`')}\\` are decided inside the bin, in JS.`,\n '',\n ];\n }\n\n /**\n * The cures, one row per option. LITERAL commands only, rendered from `L0_FAULTS[].cures` — the same\n * array `webpieces.guard-matrix.md` renders its Fix sections from, so the two can never prescribe\n * different commands for one fault.\n */\n private fixTable(): string[] {\n const rows: string[] = [];\n for (const fault of L0_FAULTS) {\n fault.cures.forEach((cure: L0Cure, i: number): void => {\n const literal = cure.isCommand() ? `\\`${cure.call.command}\\`` : `edit \\`${cure.mention}\\` yourself`;\n const option = `${String(i + 1)}${cure.preferred ? ' (preferred)' : ''}`;\n rows.push(`| \\`${fault.code}\\` | ${option} | ${this.cell(literal)} | ${this.cell(cure.discriminator)} |`);\n });\n }\n return [\n '### The fix, per fault — type the option EXACTLY as written, and run nothing else on that line',\n '',\n '| fault | option | run EXACTLY | pick this when |',\n '|---|---|---|---|',\n ...rows,\n '',\n ];\n }\n\n private matrixTable(): string[] {\n return [\n '### The matrix — three rows, and the fault only picks the MESSAGE',\n '',\n `| row | fault | on the allowlist? | outcome | logged as |`,\n '|---|---|---|---|---|',\n `| ${L0_ROW_HANDED_DOWN} | none | — | hand down to the next guard layer | \\`layer=${L0_LAYER} row=${L0_ROW_HANDED_DOWN}\\` |`,\n `| ${L0_ROW_ALLOWLISTED} | any | yes | PASS or ALLOW (see the entry) | \\`layer=${L0_LAYER} row=${L0_ROW_ALLOWLISTED}\\` |`,\n `| ${L0_ROW_BLOCKED} | any | no | BLOCK — **only the message varies by fault** | \\`layer=${L0_LAYER} row=${L0_ROW_BLOCKED}\\` |`,\n '',\n 'The tool is not a dimension either: \"any Read\" is an allowlist ENTRY, not a tool check. Those are',\n 'the same coordinates every L0 deny opens with, so a deny, a log line and this table join by eye.',\n '',\n ];\n }\n\n private allowlistTable(): string[] {\n return [\n '### The allowlist — ONE list, consulted identically by every fault',\n '',\n '| # | allowed | outcome | bypasses L1 on a HEALTHY tree? |',\n '|---|---|---|---|',\n ...L0_ALLOWLIST.map((e: L0AllowEntry, i: number): string =>\n `| ${String(i + 1)} | ${this.cell(e.label)} | ${e.kind.toUpperCase()} | ${e.cure ? 'yes — it REPAIRS the tooling' : 'no — it repairs nothing, so L1 still judges it'} |`),\n '',\n '- **PASS** — L0 has no objection; the call falls THROUGH so downstream guards still judge it.',\n '- **ALLOW** — terminal; bypasses everything, because a cure must stay reachable even when a',\n ' downstream guard would block it.',\n '',\n 'Every Bash entry is anchored to the WHOLE command. Appending `&& git status` makes it a DIFFERENT',\n 'command and it is rejected again — that is not the guard refusing its own cure.',\n '',\n 'On an UNGATED entry a leading `cd <dir> &&`, a trailing `2>&1` and a pipe into `tail`/`head` are',\n 'tolerated; nothing else. **The harness-gated read row tolerates NONE of the three** — not the',\n '`cd` prefix, not the redirect, not the pipe — so `cat f 2>&1` and `cat f | head -20` are both',\n 'rejected. That is deliberate, not an oversight: the row means exactly what',\n '`core/shell-read-parity.ts` means by \"this is a read\", and that module treats a pipe or a',\n 'redirect as proof the command is more than one. Type the bare command.',\n '',\n '`git merge` and a **bare** `git pull` are both deliberately absent — see \"The git-sync split\"',\n 'below for why the one safe pull spelling is on the list and the bare one is not. Main is merged',\n 'ONLY through the 3-point fork merge (`pnpm wp-start-update`, or `pnpm wp-start-upsert-pr` when a',\n 'PR is already open).',\n '',\n ];\n }\n\n /**\n * Fault `S`'s subject: the managed things, and every registration rendered from the harness's own\n * `shimCommand()` — which is what makes \"they are ABSOLUTE\" a fact this doc cannot get wrong, and\n * what keeps it from describing Claude Code's matcher as if it were Codex's.\n */\n private managedSurface(): string[] {\n const surfaces = [\n SHIM_SURFACE,\n ...HARNESS_REGISTRATIONS.flatMap((h: HarnessRegistration): readonly string[] => [h.registrationSurface, h.neighbourSurface]),\n ENV_SURFACE,\n ];\n const registrations: string[] = [];\n for (const harness of HARNESS_REGISTRATIONS) {\n registrations.push(`# ${harness.label}`);\n registrations.push(harness.shimCommand(GUARDS_BIN));\n registrations.push(harness.shimCommand(RULES_BIN));\n }\n return [\n `### The managed hook surface — what fault \\`S\\` compares (${String(surfaces.length)} things, one set)`,\n '',\n '| # | surface |',\n '|---|---|',\n // The shim is a bare PATH and the others are prose naming a file, so only the first is\n // wrapped in code ticks — the same rendering this table has always had, now generated.\n ...surfaces.map((surface: string, i: number): string =>\n `| ${String(i + 1)} | ${i === 0 ? `\\`${surface}\\`` : this.cell(surface)} |`),\n '',\n 'The registration is TWO PreToolUse entries per harness, and all of them are ABSOLUTE — they',\n 'resolve from any cwd:',\n '',\n '```',\n ...registrations,\n '```',\n '',\n `\\`${UPGRADE_SHIM_CMD}\\` repairs all ${String(surfaces.length)}. \\`${RESTORE_SHIM_CMD}\\``,\n `repairs \\`${SHIM_SURFACE}\\` and nothing else, so it is the fallback for an installed release too old`,\n 'to carry the first.',\n '',\n ];\n }\n\n /**\n * The audit line, rendered from `SHIM_LOG_FIELDS` + `SHIM_LOG_VERDICTS`. The optional field prints\n * in brackets because that is exactly what it is: `bin=` appears only when it differs from `shim=`.\n */\n private auditLine(): string[] {\n const shape = SHIM_LOG_FIELDS.map((f: ShimLogField): string => (f.optional ? `[${f.label}]` : f.label)).join(' ');\n return [\n '### The L0 audit line — one tab-separated line per tool call',\n '',\n '```',\n shape,\n '```',\n '',\n '| # | field | means |',\n '|---|---|---|',\n ...SHIM_LOG_FIELDS.map((f: ShimLogField, i: number): string =>\n `| ${String(i + 1)} | \\`${this.cell(f.label)}\\` | ${this.cell(f.means)} |`),\n '',\n '| verdict | means |',\n '|---|---|',\n ...SHIM_LOG_VERDICTS.map((v: ShimLogVerdict): string => `| \\`${v.label}\\` | ${this.cell(v.means)} |`),\n '',\n ...this.logPaths(),\n ];\n }\n\n private logPaths(): string[] {\n const stream = `${LOGS_STATE_DIR}/${L0_SHIM_STREAM}/<session>-<agent|coordinator>-<binName>.log`;\n return [\n 'It lands in the log directory of the tree the CALL was made in, centralized under the primary clone',\n 'so that removing a worktree does not take its audit trail with it:',\n '',\n '```',\n `<primary>/${WEBPIECES_TMP_DIR}/${WORKTREE_STATE_DIR}/<tree>/${stream}`,\n `<primary>/${WEBPIECES_TMP_DIR}/${stream} # from the primary clone itself`,\n '```',\n '',\n `The binary stamps the same \\`layer=\\`/\\`row=\\`/\\`fault=\\` fields onto its OWN streams —`,\n `\\`${L1_LOCATION_STREAM}/\\`, \\`${L2_DECISIONS_STREAM}/\\`, \\`${CALLS_STREAM}/\\` and \\`${REJECTIONS_STREAM}/\\` under the same`,\n `\\`${LOGS_STATE_DIR}/\\` — so one grep spans the whole trail. A \\`${CONFIG_FILENAME}\\` fault (\\`C\\`/\\`Y\\`) is`,\n 'therefore visible there and never on an `L0-shim` line, which only ever carries the `sh`-side codes.',\n '',\n ];\n }\n}\n"]}
|
package/src/index.d.ts
CHANGED
|
@@ -26,4 +26,3 @@ export { run } from './core/runner';
|
|
|
26
26
|
export { stripTsNoise } from './core/strip-ts-noise';
|
|
27
27
|
export { parseDirectives, DirectiveIndex, createIsLineDisabled } from './core/disable-directives';
|
|
28
28
|
export { formatReport, ReportSubject, WRITE_SUBJECT, READ_SUBJECT, BASH_SUBJECT } from './core/report';
|
|
29
|
-
export { CodexGuardPresence, CodexSessionDetector, GuardPresenceVerdict } from './bin/codex-guard-presence';
|
package/src/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.WRITE_SUBJECT = exports.ReportSubject = exports.formatReport = exports.createIsLineDisabled = exports.DirectiveIndex = exports.parseDirectives = exports.stripTsNoise = exports.run = exports.FeatureBranchGuardRule = exports.NoJsFilesRule = exports.RedirectHowToMergeMainRule = exports.PrMergeGuardRule = exports.MergeInProgressGuardRule = exports.PrCreationOrPushGuardRule = exports.BranchCreationGuardRule = exports.NoProcessExitOutsideMainRule = exports.NoSymbolDiTokensRule = exports.ThrowCauseRequiredRule = exports.CatchErrorPatternRule = exports.NoUnmanagedExceptionsRule = exports.RequireReturnTypeRule = exports.NoDestructureRule = exports.ValidateTsInSrcRule = exports.MaxFileLinesRule = exports.NoImplicitAnyRule = exports.NoAnyUnknownRule = exports.CustomRuleAdapter = exports.EmptyRuleConfig = exports.BashRuleBase = exports.FileRuleBase = exports.EditRuleBase = exports.DisableEscape = exports.FixHint = exports.detectAiType = exports.AI_TYPE_TOKEN_SH = exports.AI_TYPE_SH = exports.FileOperation = exports.AgentHookEvent = exports.AI_TYPE_UNKNOWN = exports.AI_TYPES = exports.ResolvedRuleConfig = exports.ResolvedConfig = exports.BlockedResult = exports.RuleGroup = exports.BashContext = exports.FileContext = exports.EditContext = exports.NormalizedToolInput = exports.NormalizedEdit = exports.Violation = void 0;
|
|
4
|
-
exports.
|
|
4
|
+
exports.BASH_SUBJECT = exports.READ_SUBJECT = void 0;
|
|
5
5
|
// Pluggable write-time validation framework for AI coding agents
|
|
6
6
|
var types_1 = require("./core/types");
|
|
7
7
|
Object.defineProperty(exports, "Violation", { enumerable: true, get: function () { return types_1.Violation; } });
|
|
@@ -91,11 +91,4 @@ Object.defineProperty(exports, "ReportSubject", { enumerable: true, get: functio
|
|
|
91
91
|
Object.defineProperty(exports, "WRITE_SUBJECT", { enumerable: true, get: function () { return report_1.WRITE_SUBJECT; } });
|
|
92
92
|
Object.defineProperty(exports, "READ_SUBJECT", { enumerable: true, get: function () { return report_1.READ_SUBJECT; } });
|
|
93
93
|
Object.defineProperty(exports, "BASH_SUBJECT", { enumerable: true, get: function () { return report_1.BASH_SUBJECT; } });
|
|
94
|
-
// Guard-presence attestation. Exported because the GATE that consumes it lives in @webpieces/pr-gate
|
|
95
|
-
// (BuildAffected.runBuildGate) — the check and its refusal belong here, beside the L0 shim stream that
|
|
96
|
-
// is their evidence, and only the throw belongs over there.
|
|
97
|
-
var codex_guard_presence_1 = require("./bin/codex-guard-presence");
|
|
98
|
-
Object.defineProperty(exports, "CodexGuardPresence", { enumerable: true, get: function () { return codex_guard_presence_1.CodexGuardPresence; } });
|
|
99
|
-
Object.defineProperty(exports, "CodexSessionDetector", { enumerable: true, get: function () { return codex_guard_presence_1.CodexSessionDetector; } });
|
|
100
|
-
Object.defineProperty(exports, "GuardPresenceVerdict", { enumerable: true, get: function () { return codex_guard_presence_1.GuardPresenceVerdict; } });
|
|
101
94
|
//# sourceMappingURL=index.js.map
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/ai-hook-rules/src/index.ts"],"names":[],"mappings":";;;;AAAA,iEAAiE;AACjE,sCAOsB;AALlB,kGAAA,SAAS,OAAA;AAAE,uGAAA,cAAc,OAAA;AAAE,4GAAA,mBAAmB,OAAA;AAC9C,oGAAA,WAAW,OAAA;AAAE,oGAAA,WAAW,OAAA;AAAE,oGAAA,WAAW,OAAA;AAErC,kGAAA,SAAS,OAAA;AAAE,sGAAA,aAAa,OAAA;AACxB,uGAAA,cAAc,OAAA;AAAE,2GAAA,kBAAkB,OAAA;AAGtC,oGAAoG;AACpG,oGAAoG;AACpG,yDAAyD;AACzD,kDAAsH;AAArG,uGAAA,QAAQ,OAAA;AAAE,8GAAA,eAAe,OAAA;AAAkB,6GAAA,cAAc,OAAA;AAAE,4GAAA,aAAa,OAAA;AACzF,kDAAkF;AAAzE,uGAAA,UAAU,OAAA;AAAE,6GAAA,gBAAgB,OAAA;AAAE,yGAAA,YAAY,OAAA;AAEnD,iGAAiG;AACjG,yFAAyF;AACzF,gEAAgE;AAChE,4CAAyD;AAAhD,mGAAA,OAAO,OAAA;AAAE,yGAAA,aAAa,OAAA;AAE/B,gGAAgG;AAChG,8CAA6F;AAApF,yGAAA,YAAY,OAAA;AAAE,yGAAA,YAAY,OAAA;AAAE,yGAAA,YAAY,OAAA;AAAE,4GAAA,eAAe,OAAA;AAClE,kEAA+D;AAAtD,wHAAA,iBAAiB,OAAA;AAE1B,+FAA+F;AAC/F,8DAA+D;AAAtD,kHAAA,gBAAgB,OAAA;AACzB,gEAAiE;AAAxD,oHAAA,iBAAiB,OAAA;AAC1B,8DAA+D;AAAtD,kHAAA,gBAAgB,OAAA;AACzB,sEAAsE;AAA7D,yHAAA,mBAAmB,OAAA;AAC5B,8DAAgE;AAAvD,mHAAA,iBAAiB,OAAA;AAC1B,wEAAyE;AAAhE,4HAAA,qBAAqB,OAAA;AAC9B,gFAAiF;AAAxE,oIAAA,yBAAyB,OAAA;AAClC,wEAAyE;AAAhE,4HAAA,qBAAqB,OAAA;AAC9B,0EAA2E;AAAlE,8HAAA,sBAAsB,OAAA;AAC/B,wEAAwE;AAA/D,2HAAA,oBAAoB,OAAA;AAC7B,0FAAyF;AAAhF,4IAAA,4BAA4B,OAAA;AACrC,4EAA6E;AAApE,gIAAA,uBAAuB,OAAA;AAChC,oFAAmF;AAA1E,sIAAA,yBAAyB,OAAA;AAClC,gFAAgF;AAAvE,mIAAA,wBAAwB,OAAA;AACjC,8DAA+D;AAAtD,kHAAA,gBAAgB,OAAA;AACzB,sFAAqF;AAA5E,wIAAA,0BAA0B,OAAA;AACnC,wDAAyD;AAAhD,4GAAA,aAAa,OAAA;AACtB,0EAA2E;AAAlE,8HAAA,sBAAsB,OAAA;AAE/B,wCAAoC;AAA3B,6FAAA,GAAG,OAAA;AACZ,wDAAqD;AAA5C,8GAAA,YAAY,OAAA;AACrB,gEAAkG;AAAzF,qHAAA,eAAe,OAAA;AAAE,oHAAA,cAAc,OAAA;AAAE,0HAAA,oBAAoB,OAAA;AAC9D,wCAAuG;AAA9F,sGAAA,YAAY,OAAA;AAAE,uGAAA,aAAa,OAAA;AAAE,uGAAA,aAAa,OAAA;AAAE,sGAAA,YAAY,OAAA;AAAE,sGAAA,YAAY,OAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/ai-hook-rules/src/index.ts"],"names":[],"mappings":";;;;AAAA,iEAAiE;AACjE,sCAOsB;AALlB,kGAAA,SAAS,OAAA;AAAE,uGAAA,cAAc,OAAA;AAAE,4GAAA,mBAAmB,OAAA;AAC9C,oGAAA,WAAW,OAAA;AAAE,oGAAA,WAAW,OAAA;AAAE,oGAAA,WAAW,OAAA;AAErC,kGAAA,SAAS,OAAA;AAAE,sGAAA,aAAa,OAAA;AACxB,uGAAA,cAAc,OAAA;AAAE,2GAAA,kBAAkB,OAAA;AAGtC,oGAAoG;AACpG,oGAAoG;AACpG,yDAAyD;AACzD,kDAAsH;AAArG,uGAAA,QAAQ,OAAA;AAAE,8GAAA,eAAe,OAAA;AAAkB,6GAAA,cAAc,OAAA;AAAE,4GAAA,aAAa,OAAA;AACzF,kDAAkF;AAAzE,uGAAA,UAAU,OAAA;AAAE,6GAAA,gBAAgB,OAAA;AAAE,yGAAA,YAAY,OAAA;AAEnD,iGAAiG;AACjG,yFAAyF;AACzF,gEAAgE;AAChE,4CAAyD;AAAhD,mGAAA,OAAO,OAAA;AAAE,yGAAA,aAAa,OAAA;AAE/B,gGAAgG;AAChG,8CAA6F;AAApF,yGAAA,YAAY,OAAA;AAAE,yGAAA,YAAY,OAAA;AAAE,yGAAA,YAAY,OAAA;AAAE,4GAAA,eAAe,OAAA;AAClE,kEAA+D;AAAtD,wHAAA,iBAAiB,OAAA;AAE1B,+FAA+F;AAC/F,8DAA+D;AAAtD,kHAAA,gBAAgB,OAAA;AACzB,gEAAiE;AAAxD,oHAAA,iBAAiB,OAAA;AAC1B,8DAA+D;AAAtD,kHAAA,gBAAgB,OAAA;AACzB,sEAAsE;AAA7D,yHAAA,mBAAmB,OAAA;AAC5B,8DAAgE;AAAvD,mHAAA,iBAAiB,OAAA;AAC1B,wEAAyE;AAAhE,4HAAA,qBAAqB,OAAA;AAC9B,gFAAiF;AAAxE,oIAAA,yBAAyB,OAAA;AAClC,wEAAyE;AAAhE,4HAAA,qBAAqB,OAAA;AAC9B,0EAA2E;AAAlE,8HAAA,sBAAsB,OAAA;AAC/B,wEAAwE;AAA/D,2HAAA,oBAAoB,OAAA;AAC7B,0FAAyF;AAAhF,4IAAA,4BAA4B,OAAA;AACrC,4EAA6E;AAApE,gIAAA,uBAAuB,OAAA;AAChC,oFAAmF;AAA1E,sIAAA,yBAAyB,OAAA;AAClC,gFAAgF;AAAvE,mIAAA,wBAAwB,OAAA;AACjC,8DAA+D;AAAtD,kHAAA,gBAAgB,OAAA;AACzB,sFAAqF;AAA5E,wIAAA,0BAA0B,OAAA;AACnC,wDAAyD;AAAhD,4GAAA,aAAa,OAAA;AACtB,0EAA2E;AAAlE,8HAAA,sBAAsB,OAAA;AAE/B,wCAAoC;AAA3B,6FAAA,GAAG,OAAA;AACZ,wDAAqD;AAA5C,8GAAA,YAAY,OAAA;AACrB,gEAAkG;AAAzF,qHAAA,eAAe,OAAA;AAAE,oHAAA,cAAc,OAAA;AAAE,0HAAA,oBAAoB,OAAA;AAC9D,wCAAuG;AAA9F,sGAAA,YAAY,OAAA;AAAE,uGAAA,aAAa,OAAA;AAAE,uGAAA,aAAa,OAAA;AAAE,sGAAA,YAAY,OAAA;AAAE,sGAAA,YAAY,OAAA","sourcesContent":["// Pluggable write-time validation framework for AI coding agents\nexport {\n ToolKind, RuleScope, RuleOptions, IsLineDisabled,\n Violation, NormalizedEdit, NormalizedToolInput,\n EditContext, FileContext, BashContext,\n Rule, PlainRule,\n RuleGroup, BlockedResult,\n ResolvedConfig, ResolvedRuleConfig,\n} from './core/types';\n\n// The ONE normalized hook event every harness's payload is morphed into, and the discriminator that\n// decides which harness produced it. `AgentHookEvent` sits beside the types above because it is the\n// shape the adapters produce and the hook body consumes.\nexport { AiType, AI_TYPES, AI_TYPE_UNKNOWN, AgentEventKind, AgentHookEvent, FileOperation } from './core/agent-event';\nexport { AI_TYPE_SH, AI_TYPE_TOKEN_SH, detectAiType } from './adapters/detect-ai';\n\n// Structured fix guidance shown in blocked reports (violation + mainMessage + options + escape).\n// `Option` is NOT re-exported here — it has ONE home, `@webpieces/rules-config`, because\n// `RuleFailError` carries the same class. Import it from there.\nexport { FixHint, DisableEscape } from './core/fix-hint';\n\n// Scope-specific rule bases (each extends the shared AbstractRule from @webpieces/rules-config)\nexport { EditRuleBase, FileRuleBase, BashRuleBase, EmptyRuleConfig } from './core/rule-base';\nexport { CustomRuleAdapter } from './core/custom-rule-adapter';\n\n// Built-in rule classes — each constructed with its typed *Config from @webpieces/rules-config\nexport { NoAnyUnknownRule } from './core/rules/no-any-unknown';\nexport { NoImplicitAnyRule } from './core/rules/no-implicit-any';\nexport { MaxFileLinesRule } from './core/rules/max-file-lines';\nexport { ValidateTsInSrcRule } from './core/rules/validate-ts-in-src';\nexport { NoDestructureRule } from './core/rules/no-destructure';\nexport { RequireReturnTypeRule } from './core/rules/require-return-type';\nexport { NoUnmanagedExceptionsRule } from './core/rules/no-unmanaged-exceptions';\nexport { CatchErrorPatternRule } from './core/rules/catch-error-pattern';\nexport { ThrowCauseRequiredRule } from './core/rules/throw-cause-required';\nexport { NoSymbolDiTokensRule } from './core/rules/no-symbol-di-tokens';\nexport { NoProcessExitOutsideMainRule } from './core/rules/no-process-exit-outside-main';\nexport { BranchCreationGuardRule } from './core/rules/branch-creation-guard';\nexport { PrCreationOrPushGuardRule } from './core/rules/pr-creation-or-push-guard';\nexport { MergeInProgressGuardRule } from './core/rules/merge-in-progress-guard';\nexport { PrMergeGuardRule } from './core/rules/pr-merge-guard';\nexport { RedirectHowToMergeMainRule } from './core/rules/redirect-how-to-merge-main';\nexport { NoJsFilesRule } from './core/rules/no-js-files';\nexport { FeatureBranchGuardRule } from './core/rules/feature-branch-guard';\n\nexport { run } from './core/runner';\nexport { stripTsNoise } from './core/strip-ts-noise';\nexport { parseDirectives, DirectiveIndex, createIsLineDisabled } from './core/disable-directives';\nexport { formatReport, ReportSubject, WRITE_SUBJECT, READ_SUBJECT, BASH_SUBJECT } from './core/report';\n\n"]}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { Option } from '@webpieces/rules-config';
|
|
2
|
-
/**
|
|
3
|
-
* GUARD-PRESENCE ATTESTATION — "did the guards actually RUN in this Codex session?"
|
|
4
|
-
*
|
|
5
|
-
* ─── The hole this closes ─────────────────────────────────────────────────────────────────────────
|
|
6
|
-
* Codex prompts before running a hook it has not trusted, and the prompt's third option is
|
|
7
|
-
* `Continue without trusting (hooks won't run)`. One keystroke, no later warning, and the session is
|
|
8
|
-
* fully unguarded for its whole life. Nothing at INSTALL time can see that choice — it is made
|
|
9
|
-
* afterwards, in another process — so `codex-trust.ts` reading `~/.codex/config.toml` is necessary and
|
|
10
|
-
* not sufficient.
|
|
11
|
-
*
|
|
12
|
-
* The same check catches a second, quieter failure with no relation to trust: a matcher that names a
|
|
13
|
-
* tool Codex does not emit. That was the live state of every `.codex/hooks.json` a desktop sync had
|
|
14
|
-
* written — `Write|Edit|MultiEdit` against a harness whose file tool is `apply_patch` — and it produced
|
|
15
|
-
* exactly the same symptom, which is no symptom at all.
|
|
16
|
-
*
|
|
17
|
-
* ─── Why the L0 shim log is the evidence ──────────────────────────────────────────────────────────
|
|
18
|
-
* The L0 shim writes one row per tool call, on EVERY path including the healthy one, before anything
|
|
19
|
-
* else can fail. So a session that produced ZERO rows did not have a guard run — that is the whole
|
|
20
|
-
* inference, and it holds no matter WHY (untrusted, wrong matcher, deleted file, unresolvable path).
|
|
21
|
-
*
|
|
22
|
-
* It is deliberately a count of rows and not a check of any row's content: what is being attested is
|
|
23
|
-
* that the hook EXECUTED, not what it decided.
|
|
24
|
-
*
|
|
25
|
-
* ─── Where it is WIRED ────────────────────────────────────────────────────────────────────────────
|
|
26
|
-
* `BuildAffected.runBuildGate` (@webpieces/pr-gate) calls it before resolving the build command, so ALL
|
|
27
|
-
* THREE build entry points are covered by one call: `wp-build`, stage ② (`wp-review-upsert-pr`) and
|
|
28
|
-
* stage ③ (`wp-finish-upsert-pr`). Attesting at the build is the right place because the build is the
|
|
29
|
-
* moment a session's work is about to be claimed as verified — an unguarded session that never builds
|
|
30
|
-
* has produced nothing to trust.
|
|
31
|
-
*
|
|
32
|
-
* It BLOCKS, and blocking is the whole point. This check was deliberately shipped detached for one
|
|
33
|
-
* release rather than half-wired, because a check that detects and never refuses reads as coverage
|
|
34
|
-
* nobody actually has.
|
|
35
|
-
*/
|
|
36
|
-
/**
|
|
37
|
-
* Is THIS process running inside a Codex session?
|
|
38
|
-
*
|
|
39
|
-
* MEASURED (codex-cli 0.151.0), and the negative half matters as much as the positive: there is NO
|
|
40
|
-
* `CODEX_SESSION_ID`. Reaching for one is the obvious thing to do and it does not exist, so the
|
|
41
|
-
* fingerprints are the three that DO: `CODEX_MANAGED_BY_NPM`, `CODEX_MANAGED_PACKAGE_ROOT`, and a
|
|
42
|
-
* `/.codex/tmp/arg0/` entry on `PATH`.
|
|
43
|
-
*
|
|
44
|
-
* Any one of them is enough. They come from different install shapes, and requiring all three would
|
|
45
|
-
* silently answer "not Codex" — which for a check that BLOCKS on absence of evidence is the dangerous
|
|
46
|
-
* direction: it would turn an unguarded session into an unchecked one.
|
|
47
|
-
*/
|
|
48
|
-
export declare class CodexSessionDetector {
|
|
49
|
-
/** The env keys that identify a Codex-managed process. */
|
|
50
|
-
static readonly ENV_KEYS: readonly string[];
|
|
51
|
-
/** The PATH segment Codex injects for its arg0 shims. */
|
|
52
|
-
static readonly PATH_MARKER = "/.codex/tmp/arg0/";
|
|
53
|
-
isCodexSession(env?: NodeJS.ProcessEnv): boolean;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* The answer, with the sentence a human or an agent reads. Data-only → a class, per CLAUDE.md.
|
|
57
|
-
*
|
|
58
|
-
* `cures` are `Option`s rather than "Fix:" lines inside `reason`, because the ONE top-level handler
|
|
59
|
-
* owns the rendering of a cure list — a hand-numbered list in a string literal is an automatic review
|
|
60
|
-
* reject, and it also means the two cures could not be re-ordered or counted by anything but a human
|
|
61
|
-
* reading prose. Empty on both green paths: there is nothing to fix.
|
|
62
|
-
*/
|
|
63
|
-
export declare class GuardPresenceVerdict {
|
|
64
|
-
/** True ⇒ the caller may proceed. False ⇒ BLOCK: a Codex session ran with no guard rows. */
|
|
65
|
-
readonly ok: boolean;
|
|
66
|
-
/** Why, in one line — always populated, including on the green paths. */
|
|
67
|
-
readonly reason: string;
|
|
68
|
-
/** How many L0 shim rows this tree has for the session under attestation. */
|
|
69
|
-
readonly rows: number;
|
|
70
|
-
/** What to DO about it, for the caller to hand to RuleFailError. Empty when `ok`. */
|
|
71
|
-
readonly cures: readonly Option[];
|
|
72
|
-
constructor(
|
|
73
|
-
/** True ⇒ the caller may proceed. False ⇒ BLOCK: a Codex session ran with no guard rows. */
|
|
74
|
-
ok: boolean,
|
|
75
|
-
/** Why, in one line — always populated, including on the green paths. */
|
|
76
|
-
reason: string,
|
|
77
|
-
/** How many L0 shim rows this tree has for the session under attestation. */
|
|
78
|
-
rows: number,
|
|
79
|
-
/** What to DO about it, for the caller to hand to RuleFailError. Empty when `ok`. */
|
|
80
|
-
cures?: readonly Option[]);
|
|
81
|
-
}
|
|
82
|
-
/**
|
|
83
|
-
* The check itself: in a Codex session, REFUSE when the tree has no L0 shim rows at all.
|
|
84
|
-
*
|
|
85
|
-
* Outside a Codex session it is a no-op that says so, which is what makes it safe to call
|
|
86
|
-
* unconditionally from a shared build path.
|
|
87
|
-
*/
|
|
88
|
-
export declare class CodexGuardPresence {
|
|
89
|
-
private readonly detector;
|
|
90
|
-
constructor(detector: CodexSessionDetector);
|
|
91
|
-
/**
|
|
92
|
-
* `root` is the tree whose `.webpieces` logs are the evidence — the same root every other webpieces
|
|
93
|
-
* writer resolves, so a worktree is attested by its own rows rather than the primary clone's.
|
|
94
|
-
*/
|
|
95
|
-
check(root: string, env?: NodeJS.ProcessEnv): GuardPresenceVerdict;
|
|
96
|
-
/**
|
|
97
|
-
* BOTH cures, always both, in likelihood order.
|
|
98
|
-
*
|
|
99
|
-
* Two unrelated failures produce this one symptom, and they have DIFFERENT fixes — an agent handed
|
|
100
|
-
* only the likelier one will run it, see nothing change, and conclude the check is broken. So the
|
|
101
|
-
* list is fixed rather than guessed at: nothing observable at build time distinguishes "the human
|
|
102
|
-
* declined the trust prompt" from "the matcher names a tool Codex never emits".
|
|
103
|
-
*/
|
|
104
|
-
static readonly CURES: readonly Option[];
|
|
105
|
-
/**
|
|
106
|
-
* The refusal's EVIDENCE — what was measured and where. The cures are Options (above); this is only
|
|
107
|
-
* the finding, so the top-level handler renders the two halves in its own house style.
|
|
108
|
-
*/
|
|
109
|
-
private refusal;
|
|
110
|
-
/** How many `.log` lines the L0 shim stream holds for this tree. Never throws; 0 on any failure. */
|
|
111
|
-
private shimRowCount;
|
|
112
|
-
}
|