@webpieces/ai-hook-rules 0.4.711 → 0.4.713
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/package.json +2 -2
- package/src/adapters/detect-ai.d.ts +5 -4
- package/src/adapters/detect-ai.js +5 -4
- package/src/adapters/detect-ai.js.map +1 -1
- package/src/adapters/hook-core.js +9 -3
- package/src/adapters/hook-core.js.map +1 -1
- package/src/bin/codex-guard-presence.d.ts +86 -0
- package/src/bin/codex-guard-presence.js +150 -0
- package/src/bin/codex-guard-presence.js.map +1 -0
- package/src/bin/codex-trust.d.ts +99 -0
- package/src/bin/codex-trust.js +199 -0
- package/src/bin/codex-trust.js.map +1 -0
- package/src/bin/hook-registration.d.ts +200 -43
- package/src/bin/hook-registration.js +218 -89
- package/src/bin/hook-registration.js.map +1 -1
- package/src/bin/l0-allowlist.d.ts +0 -30
- package/src/bin/l0-allowlist.js +9 -67
- package/src/bin/l0-allowlist.js.map +1 -1
- package/src/bin/l0-decide.d.ts +43 -0
- package/src/bin/l0-decide.js +90 -0
- package/src/bin/l0-decide.js.map +1 -0
- package/src/bin/l0-ignored-tools.d.ts +38 -0
- package/src/bin/l0-ignored-tools.js +44 -0
- package/src/bin/l0-ignored-tools.js.map +1 -0
- package/src/bin/setup-config.d.ts +19 -0
- package/src/bin/setup-config.js +370 -0
- package/src/bin/setup-config.js.map +1 -0
- package/src/bin/setup.d.ts +56 -21
- package/src/bin/setup.js +125 -370
- package/src/bin/setup.js.map +1 -1
- package/src/bin/shim-audit-log.js +10 -1
- package/src/bin/shim-audit-log.js.map +1 -1
- package/src/bin/shim-deny-reason.js +4 -4
- package/src/bin/shim-deny-reason.js.map +1 -1
- package/src/bin/shim.d.ts +2 -0
- package/src/bin/shim.js +21 -1
- package/src/bin/shim.js.map +1 -1
- package/src/bin/upgrade-shim.d.ts +1 -1
- package/src/bin/upgrade-shim.js +12 -8
- package/src/bin/upgrade-shim.js.map +1 -1
- package/src/core/agent-event.d.ts +16 -0
- package/src/core/agent-event.js +17 -1
- package/src/core/agent-event.js.map +1 -1
- package/src/core/ai-type-context.d.ts +36 -0
- package/src/core/ai-type-context.js +45 -0
- package/src/core/ai-type-context.js.map +1 -0
- package/src/core/decision-log.js +9 -0
- package/src/core/decision-log.js.map +1 -1
- package/src/core/l0-matrix.js +14 -6
- package/src/core/l0-matrix.js.map +1 -1
- package/src/core/l0-tooling-doc.d.ts +3 -2
- package/src/core/l0-tooling-doc.js +22 -10
- package/src/core/l0-tooling-doc.js.map +1 -1
- package/src/core/rejection-log.js +5 -1
- package/src/core/rejection-log.js.map +1 -1
- package/src/index.d.ts +1 -1
- package/src/index.js +4 -1
- package/src/index.js.map +1 -1
- package/templates/ai-hook.sh +15 -1
|
@@ -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, REGISTRATION_SURFACE, RULES_BIN, SHIM_SURFACE, shimCommand,\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. A leading `cd <dir> &&`, a trailing `2>&1` and a',\n 'pipe into `tail`/`head` are tolerated; nothing else. 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 '`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 THREE managed things, and the two registrations rendered from\n * `shimCommand()` — which is what makes \"they are ABSOLUTE\" a fact this doc cannot get wrong.\n */\n private managedSurface(): string[] {\n return [\n '### The managed hook surface — what fault `S` compares (THREE things, one set)',\n '',\n '| # | surface |',\n '|---|---|',\n `| 1 | \\`${SHIM_SURFACE}\\` |`,\n `| 2 | ${this.cell(REGISTRATION_SURFACE)} |`,\n `| 3 | ${this.cell(ENV_SURFACE)} |`,\n '',\n 'The registration is TWO PreToolUse entries, and both are ABSOLUTE — they resolve from any cwd:',\n '',\n '```',\n shimCommand(GUARDS_BIN),\n shimCommand(RULES_BIN),\n '```',\n '',\n `\\`${UPGRADE_SHIM_CMD}\\` repairs all three. \\`${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. A leading `cd <dir> &&`, a trailing `2>&1` and a',\n 'pipe into `tail`/`head` are tolerated; nothing else. 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 '`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"]}
|
|
@@ -8,6 +8,7 @@ const path = tslib_1.__importStar(require("path"));
|
|
|
8
8
|
const rules_config_1 = require("@webpieces/rules-config");
|
|
9
9
|
const log_streams_1 = require("./log-streams");
|
|
10
10
|
const log_stream_1 = require("./log-stream");
|
|
11
|
+
const ai_type_context_1 = require("./ai-type-context");
|
|
11
12
|
const to_error_1 = require("./to-error");
|
|
12
13
|
/**
|
|
13
14
|
* The rejection index and its detail files, BOTH inside the `rejections/` stream directory, both
|
|
@@ -68,7 +69,10 @@ function logRejection(toolKind, input, result, cwd) {
|
|
|
68
69
|
// previously landed here as a couple of lines attributed to whatever rule the report happened
|
|
69
70
|
// to cite, with nothing anywhere identifying L0 as the cause. (WHO made the call is answered by
|
|
70
71
|
// the filename, which logStream prefixes with session/agent/hook — not by a column here.)
|
|
71
|
-
|
|
72
|
+
// `ai=` joins the same append-only tail as `fault=`, in the same vocabulary the L0 sh shim and
|
|
73
|
+
// both decision streams use — so `grep ai=codex` spans all five streams. A row from a release
|
|
74
|
+
// that predates the field carries no `ai=` and reads as `unknown`.
|
|
75
|
+
const logLine = `[${timestamp}]\t${toolKind}\t${relativePath}\t[${ruleNames.join(',')}]\t${detailRelPath}\tfault=${result.fault}\tai=${ai_type_context_1.aiTypeContext.forLog()}\n`;
|
|
72
76
|
fs.appendFileSync(logPath, logLine);
|
|
73
77
|
detailPruner.prune(logsDir, MAX_AGE_DAYS);
|
|
74
78
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rejection-log.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/rejection-log.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport { dotWebpieces, RepoRootFinder } from '@webpieces/rules-config';\nimport { REJECTIONS_STREAM } from './log-streams';\n\nimport type { ToolKind, NormalizedToolInput, BlockedResult } from './types';\nimport { logStream } from './log-stream';\nimport { toError } from './to-error';\n\n/**\n * The rejection index and its detail files, BOTH inside the `rejections/` stream directory, both\n * named by the same writer key:\n *\n * logs/rejections/<sid>-<agent>-<hook>.log ← the index (one line per block)\n * logs/rejections/<sid>-<agent>-<hook>/ ← its detail dir, SAME key\n * writeInfo-<epochMs>.md\n *\n * The detail files used to be `hooks/<YYYY-MM-DD>/writeInfo-<epochMs>.md` — a directory keyed only by\n * the DATE, shared by every writer in the tree, written with a raw `writeFileSync`. Two agents blocked\n * in the same millisecond produced the same path and one silently overwrote the other's evidence. The\n * comment that licensed it claimed a per-worktree log has exactly one writer; LogStream's class\n * comment is the measurement that disproves it (parallel PreToolUse hooks, non-isolated subagents and\n * multiple Claude Code windows all share a tree). Naming the directory after the LOG THAT POINTS AT IT\n * gives it the same one-owner-by-construction property the log itself has, and makes the pointer\n * readable: the index line, the directory and the file are one name.\n *\n * RETENTION is therefore filename-based, not directory-date-based: there is no date level left, so\n * `DetailPruner` parses `<epochMs>` out of each `writeInfo-*.md` (no `stat` call) and removes any\n * stream directory left empty. Every delete is `force: true` and failures are swallowed, so concurrent\n * agents racing the same week-old files is harmless.\n */\nconst DETAIL_PREFIX = 'writeInfo-';\nconst DETAIL_SUFFIX = '.md';\nconst DETAIL_RE = /^writeInfo-(\\d+)\\.md$/;\nconst MAX_LOG_BYTES = 512 * 1024; // 512 KB — rotate when exceeded\nconst MAX_AGE_DAYS = 7;\n\nconst RULE_NAME_RE = /^\\[([^\\]]+)\\] \\(/gm;\n\nexport function logRejection(\n toolKind: ToolKind,\n input: NormalizedToolInput,\n result: BlockedResult,\n cwd: string,\n): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // `.webpieces/` lives at the repo root, NOT the AI's cwd — resolve it so a hook fired while\n // the AI is in a subdirectory never scatters a stray `<subdir>/.webpieces` tree.\n const root = new RepoRootFinder().resolveRepoRoot(cwd);\n const now = new Date();\n const timestamp = now.toISOString();\n const epochMs = String(now.getTime());\n\n // LOCAL scope — a rejection is this worktree's event. WHICH writer's event is answered by the\n // stream prefix, on the index AND on its detail directory, so neither can collide.\n const logsDir = dotWebpieces.logsFile(root, REJECTIONS_STREAM);\n // The detail directory is the writer key with NO extension, sitting beside the `.log` that\n // indexes it — so index and details share one owner, one level down inside the stream dir.\n const detailDirName = logStream.writerFile('');\n const detailDir = path.join(logsDir, detailDirName);\n fs.mkdirSync(detailDir, { recursive: true });\n\n const relativePath = computeRelativePath(input.filePath, root);\n const ruleNames = extractRuleNames(result.report);\n const detailFileName = `${DETAIL_PREFIX}${epochMs}${DETAIL_SUFFIX}`;\n // Relative to `rejections/` — the index's own directory — so the pointer resolves from where\n // the reader found the line, and reads as the log's own name plus a file.\n const detailRelPath = `${detailDirName}/${detailFileName}`;\n\n const detail = buildDetailContent(timestamp, toolKind, relativePath, ruleNames, result.report, input);\n fs.writeFileSync(path.join(detailDir, detailFileName), detail);\n\n const logPath = path.join(logsDir, logStream.writerFile('.log'));\n rotateLogFile(logPath, path.join(logsDir, logStream.writerFile('.1.log')));\n\n // APPEND-ONLY, and `fault=` is spelled exactly as the L0 sh log spells it. A fault-S storm\n // previously landed here as a couple of lines attributed to whatever rule the report happened\n // to cite, with nothing anywhere identifying L0 as the cause. (WHO made the call is answered by\n // the filename, which logStream prefixes with session/agent/hook — not by a column here.)\n const logLine = `[${timestamp}]\\t${toolKind}\\t${relativePath}\\t[${ruleNames.join(',')}]\\t${detailRelPath}\\tfault=${result.fault}\\n`;\n fs.appendFileSync(logPath, logLine);\n\n detailPruner.prune(logsDir, MAX_AGE_DAYS);\n } catch (err: unknown) {\n //const error = toError(err);\n void err;\n }\n}\n\nfunction computeRelativePath(filePath: string, cwd: string): string {\n if (filePath.startsWith(cwd)) {\n const rel = filePath.slice(cwd.length);\n if (rel.startsWith('/')) return rel.slice(1);\n return rel;\n }\n return filePath;\n}\n\n/**\n * The rule names a block report cites — every `[<rule-name>] (` header it opens with. Exported because\n * two audit streams need the same answer from the same regex: this file's rejection index, and the\n * `rule=` field the `calls/` stream now carries (see InvocationLog.finish). Two scrapers would be\n * two answers to one question.\n */\n// webpieces-disable no-function-outside-class -- pure regex scraper beside this module's other module-scope helpers; exported so the invocation log and the rejection index scrape rule names with the SAME code.\nexport function extractRuleNames(report: string): string[] {\n const names: string[] = [];\n let match = RULE_NAME_RE.exec(report);\n while (match !== null) {\n names.push(match[1]);\n match = RULE_NAME_RE.exec(report);\n }\n RULE_NAME_RE.lastIndex = 0;\n return names;\n}\n\nfunction buildDetailContent(\n timestamp: string,\n toolKind: ToolKind,\n relativePath: string,\n ruleNames: string[],\n report: string,\n input: NormalizedToolInput,\n): string {\n const lines: string[] = [];\n lines.push('# Hook Rejection Detail');\n lines.push('');\n lines.push(`- **Timestamp:** ${timestamp}`);\n lines.push(`- **Tool:** ${toolKind}`);\n lines.push(`- **File:** ${relativePath}`);\n lines.push(`- **Rules violated:** ${ruleNames.join(', ')}`);\n lines.push('');\n lines.push('## Report');\n lines.push('');\n lines.push('```');\n lines.push(report.trimEnd());\n lines.push('```');\n lines.push('');\n lines.push('## Content Being Written');\n lines.push('');\n\n if (toolKind === 'Write') {\n const content = input.edits.length > 0 ? input.edits[0].newString : '';\n lines.push('```typescript');\n lines.push(content.trimEnd());\n lines.push('```');\n } else {\n for (let i = 0; i < input.edits.length; i += 1) {\n const edit = input.edits[i];\n lines.push(`### Edit ${String(i + 1)} of ${String(input.edits.length)}`);\n lines.push('');\n lines.push('**old_string:**');\n lines.push('```typescript');\n lines.push(edit.oldString.trimEnd());\n lines.push('```');\n lines.push('');\n lines.push('**new_string:**');\n lines.push('```typescript');\n lines.push(edit.newString.trimEnd());\n lines.push('```');\n lines.push('');\n }\n }\n\n return lines.join('\\n') + '\\n';\n}\n\nfunction rotateLogFile(logPath: string, prevPath: string): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const stat = fs.statSync(logPath);\n if (stat.size > MAX_LOG_BYTES) {\n if (fs.existsSync(prevPath)) fs.unlinkSync(prevPath);\n fs.renameSync(logPath, prevPath);\n }\n } catch (err: unknown) {\n //const error = toError(err);\n void err;\n }\n}\n\n/**\n * The 7-day sweep over rejection DETAIL files.\n *\n * A class rather than four module-scope helpers because the four belong together: `prune` is the\n * policy and the other three are the swallow-everything filesystem calls it is built from, none of\n * which means anything on its own.\n *\n * The age comes out of the FILENAME, not out of `stat` — the epoch millis are already in the name, so\n * a sweep costs one `readdir` per stream directory and no syscall per file. (The old scheme could\n * delete a whole `hooks/<YYYY-MM-DD>/` in one recursive `rm` because the date WAS the directory; the\n * directory is now the WRITER, which never expires, so expiry moved down one level.)\n *\n * IDEMPOTENT AND RACE-TOLERANT ON PURPOSE. Several agents sweep the same directory concurrently, so\n * two processes will regularly try to unlink the same week-old file and to rmdir the same emptied\n * directory. `force: true` makes an already-gone target a no-op, an rmdir of a still-populated (or\n * already-removed) directory fails harmlessly, and every error is swallowed: the loser of the race\n * does nothing and reports nothing, which is the correct outcome — the file is gone either way.\n */\nclass DetailPruner {\n prune(logsDir: string, maxAgeDays: number): void {\n const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;\n for (const dirName of this.readdirOrEmpty(logsDir)) {\n const dir = path.join(logsDir, dirName);\n if (!this.isDirectory(dir)) continue;\n const names = this.readdirOrEmpty(dir);\n let removed = 0;\n for (const name of names) {\n const match = DETAIL_RE.exec(name);\n if (match === null) continue;\n if (Number(match[1]) >= cutoff) continue;\n this.removeFile(path.join(dir, name));\n removed += 1;\n }\n // Only a directory we just emptied is a candidate, and the removal is a NON-recursive rmdir\n // on purpose: it fails harmlessly if a concurrent writer dropped a fresh detail in between,\n // so the sweep can never take a file it did not decide was expired.\n if (removed > 0 && removed === names.length) this.removeEmptyDir(dir);\n }\n }\n\n private readdirOrEmpty(dir: string): string[] {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return fs.readdirSync(dir);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return [];\n }\n }\n\n private isDirectory(target: string): boolean {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return fs.statSync(target).isDirectory();\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return false;\n }\n }\n\n // `force: true` is what makes the concurrent sweep harmless: a file another agent already deleted\n // is a no-op rather than an ENOENT.\n private removeFile(target: string): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n fs.rmSync(target, { force: true });\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n }\n }\n\n // NON-recursive by design — see prune(). This must refuse a directory that is not empty.\n private removeEmptyDir(target: string): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n fs.rmdirSync(target);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n }\n }\n}\n\nconst detailPruner = new DetailPruner();\n"]}
|
|
1
|
+
{"version":3,"file":"rejection-log.js","sourceRoot":"","sources":["../../../../../../packages/tooling/ai-hook-rules/src/core/rejection-log.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\n\nimport { dotWebpieces, RepoRootFinder } from '@webpieces/rules-config';\nimport { REJECTIONS_STREAM } from './log-streams';\n\nimport type { ToolKind, NormalizedToolInput, BlockedResult } from './types';\nimport { logStream } from './log-stream';\nimport { aiTypeContext } from './ai-type-context';\nimport { toError } from './to-error';\n\n/**\n * The rejection index and its detail files, BOTH inside the `rejections/` stream directory, both\n * named by the same writer key:\n *\n * logs/rejections/<sid>-<agent>-<hook>.log ← the index (one line per block)\n * logs/rejections/<sid>-<agent>-<hook>/ ← its detail dir, SAME key\n * writeInfo-<epochMs>.md\n *\n * The detail files used to be `hooks/<YYYY-MM-DD>/writeInfo-<epochMs>.md` — a directory keyed only by\n * the DATE, shared by every writer in the tree, written with a raw `writeFileSync`. Two agents blocked\n * in the same millisecond produced the same path and one silently overwrote the other's evidence. The\n * comment that licensed it claimed a per-worktree log has exactly one writer; LogStream's class\n * comment is the measurement that disproves it (parallel PreToolUse hooks, non-isolated subagents and\n * multiple Claude Code windows all share a tree). Naming the directory after the LOG THAT POINTS AT IT\n * gives it the same one-owner-by-construction property the log itself has, and makes the pointer\n * readable: the index line, the directory and the file are one name.\n *\n * RETENTION is therefore filename-based, not directory-date-based: there is no date level left, so\n * `DetailPruner` parses `<epochMs>` out of each `writeInfo-*.md` (no `stat` call) and removes any\n * stream directory left empty. Every delete is `force: true` and failures are swallowed, so concurrent\n * agents racing the same week-old files is harmless.\n */\nconst DETAIL_PREFIX = 'writeInfo-';\nconst DETAIL_SUFFIX = '.md';\nconst DETAIL_RE = /^writeInfo-(\\d+)\\.md$/;\nconst MAX_LOG_BYTES = 512 * 1024; // 512 KB — rotate when exceeded\nconst MAX_AGE_DAYS = 7;\n\nconst RULE_NAME_RE = /^\\[([^\\]]+)\\] \\(/gm;\n\nexport function logRejection(\n toolKind: ToolKind,\n input: NormalizedToolInput,\n result: BlockedResult,\n cwd: string,\n): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n // `.webpieces/` lives at the repo root, NOT the AI's cwd — resolve it so a hook fired while\n // the AI is in a subdirectory never scatters a stray `<subdir>/.webpieces` tree.\n const root = new RepoRootFinder().resolveRepoRoot(cwd);\n const now = new Date();\n const timestamp = now.toISOString();\n const epochMs = String(now.getTime());\n\n // LOCAL scope — a rejection is this worktree's event. WHICH writer's event is answered by the\n // stream prefix, on the index AND on its detail directory, so neither can collide.\n const logsDir = dotWebpieces.logsFile(root, REJECTIONS_STREAM);\n // The detail directory is the writer key with NO extension, sitting beside the `.log` that\n // indexes it — so index and details share one owner, one level down inside the stream dir.\n const detailDirName = logStream.writerFile('');\n const detailDir = path.join(logsDir, detailDirName);\n fs.mkdirSync(detailDir, { recursive: true });\n\n const relativePath = computeRelativePath(input.filePath, root);\n const ruleNames = extractRuleNames(result.report);\n const detailFileName = `${DETAIL_PREFIX}${epochMs}${DETAIL_SUFFIX}`;\n // Relative to `rejections/` — the index's own directory — so the pointer resolves from where\n // the reader found the line, and reads as the log's own name plus a file.\n const detailRelPath = `${detailDirName}/${detailFileName}`;\n\n const detail = buildDetailContent(timestamp, toolKind, relativePath, ruleNames, result.report, input);\n fs.writeFileSync(path.join(detailDir, detailFileName), detail);\n\n const logPath = path.join(logsDir, logStream.writerFile('.log'));\n rotateLogFile(logPath, path.join(logsDir, logStream.writerFile('.1.log')));\n\n // APPEND-ONLY, and `fault=` is spelled exactly as the L0 sh log spells it. A fault-S storm\n // previously landed here as a couple of lines attributed to whatever rule the report happened\n // to cite, with nothing anywhere identifying L0 as the cause. (WHO made the call is answered by\n // the filename, which logStream prefixes with session/agent/hook — not by a column here.)\n // `ai=` joins the same append-only tail as `fault=`, in the same vocabulary the L0 sh shim and\n // both decision streams use — so `grep ai=codex` spans all five streams. A row from a release\n // that predates the field carries no `ai=` and reads as `unknown`.\n const logLine = `[${timestamp}]\\t${toolKind}\\t${relativePath}\\t[${ruleNames.join(',')}]\\t${detailRelPath}\\tfault=${result.fault}\\tai=${aiTypeContext.forLog()}\\n`;\n fs.appendFileSync(logPath, logLine);\n\n detailPruner.prune(logsDir, MAX_AGE_DAYS);\n } catch (err: unknown) {\n //const error = toError(err);\n void err;\n }\n}\n\nfunction computeRelativePath(filePath: string, cwd: string): string {\n if (filePath.startsWith(cwd)) {\n const rel = filePath.slice(cwd.length);\n if (rel.startsWith('/')) return rel.slice(1);\n return rel;\n }\n return filePath;\n}\n\n/**\n * The rule names a block report cites — every `[<rule-name>] (` header it opens with. Exported because\n * two audit streams need the same answer from the same regex: this file's rejection index, and the\n * `rule=` field the `calls/` stream now carries (see InvocationLog.finish). Two scrapers would be\n * two answers to one question.\n */\n// webpieces-disable no-function-outside-class -- pure regex scraper beside this module's other module-scope helpers; exported so the invocation log and the rejection index scrape rule names with the SAME code.\nexport function extractRuleNames(report: string): string[] {\n const names: string[] = [];\n let match = RULE_NAME_RE.exec(report);\n while (match !== null) {\n names.push(match[1]);\n match = RULE_NAME_RE.exec(report);\n }\n RULE_NAME_RE.lastIndex = 0;\n return names;\n}\n\nfunction buildDetailContent(\n timestamp: string,\n toolKind: ToolKind,\n relativePath: string,\n ruleNames: string[],\n report: string,\n input: NormalizedToolInput,\n): string {\n const lines: string[] = [];\n lines.push('# Hook Rejection Detail');\n lines.push('');\n lines.push(`- **Timestamp:** ${timestamp}`);\n lines.push(`- **Tool:** ${toolKind}`);\n lines.push(`- **File:** ${relativePath}`);\n lines.push(`- **Rules violated:** ${ruleNames.join(', ')}`);\n lines.push('');\n lines.push('## Report');\n lines.push('');\n lines.push('```');\n lines.push(report.trimEnd());\n lines.push('```');\n lines.push('');\n lines.push('## Content Being Written');\n lines.push('');\n\n if (toolKind === 'Write') {\n const content = input.edits.length > 0 ? input.edits[0].newString : '';\n lines.push('```typescript');\n lines.push(content.trimEnd());\n lines.push('```');\n } else {\n for (let i = 0; i < input.edits.length; i += 1) {\n const edit = input.edits[i];\n lines.push(`### Edit ${String(i + 1)} of ${String(input.edits.length)}`);\n lines.push('');\n lines.push('**old_string:**');\n lines.push('```typescript');\n lines.push(edit.oldString.trimEnd());\n lines.push('```');\n lines.push('');\n lines.push('**new_string:**');\n lines.push('```typescript');\n lines.push(edit.newString.trimEnd());\n lines.push('```');\n lines.push('');\n }\n }\n\n return lines.join('\\n') + '\\n';\n}\n\nfunction rotateLogFile(logPath: string, prevPath: string): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const stat = fs.statSync(logPath);\n if (stat.size > MAX_LOG_BYTES) {\n if (fs.existsSync(prevPath)) fs.unlinkSync(prevPath);\n fs.renameSync(logPath, prevPath);\n }\n } catch (err: unknown) {\n //const error = toError(err);\n void err;\n }\n}\n\n/**\n * The 7-day sweep over rejection DETAIL files.\n *\n * A class rather than four module-scope helpers because the four belong together: `prune` is the\n * policy and the other three are the swallow-everything filesystem calls it is built from, none of\n * which means anything on its own.\n *\n * The age comes out of the FILENAME, not out of `stat` — the epoch millis are already in the name, so\n * a sweep costs one `readdir` per stream directory and no syscall per file. (The old scheme could\n * delete a whole `hooks/<YYYY-MM-DD>/` in one recursive `rm` because the date WAS the directory; the\n * directory is now the WRITER, which never expires, so expiry moved down one level.)\n *\n * IDEMPOTENT AND RACE-TOLERANT ON PURPOSE. Several agents sweep the same directory concurrently, so\n * two processes will regularly try to unlink the same week-old file and to rmdir the same emptied\n * directory. `force: true` makes an already-gone target a no-op, an rmdir of a still-populated (or\n * already-removed) directory fails harmlessly, and every error is swallowed: the loser of the race\n * does nothing and reports nothing, which is the correct outcome — the file is gone either way.\n */\nclass DetailPruner {\n prune(logsDir: string, maxAgeDays: number): void {\n const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;\n for (const dirName of this.readdirOrEmpty(logsDir)) {\n const dir = path.join(logsDir, dirName);\n if (!this.isDirectory(dir)) continue;\n const names = this.readdirOrEmpty(dir);\n let removed = 0;\n for (const name of names) {\n const match = DETAIL_RE.exec(name);\n if (match === null) continue;\n if (Number(match[1]) >= cutoff) continue;\n this.removeFile(path.join(dir, name));\n removed += 1;\n }\n // Only a directory we just emptied is a candidate, and the removal is a NON-recursive rmdir\n // on purpose: it fails harmlessly if a concurrent writer dropped a fresh detail in between,\n // so the sweep can never take a file it did not decide was expired.\n if (removed > 0 && removed === names.length) this.removeEmptyDir(dir);\n }\n }\n\n private readdirOrEmpty(dir: string): string[] {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return fs.readdirSync(dir);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return [];\n }\n }\n\n private isDirectory(target: string): boolean {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n return fs.statSync(target).isDirectory();\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n return false;\n }\n }\n\n // `force: true` is what makes the concurrent sweep harmless: a file another agent already deleted\n // is a no-op rather than an ENOENT.\n private removeFile(target: string): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n fs.rmSync(target, { force: true });\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n }\n }\n\n // NON-recursive by design — see prune(). This must refuse a directory that is not empty.\n private removeEmptyDir(target: string): void {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n fs.rmdirSync(target);\n } catch (err: unknown) {\n const error = toError(err);\n void error;\n }\n }\n}\n\nconst detailPruner = new DetailPruner();\n"]}
|
package/src/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { ToolKind, RuleScope, RuleOptions, IsLineDisabled, Violation, NormalizedEdit, NormalizedToolInput, EditContext, FileContext, BashContext, Rule, PlainRule, RuleGroup, BlockedResult, ResolvedConfig, ResolvedRuleConfig, } from './core/types';
|
|
2
|
-
export { AiType, AgentEventKind, AgentHookEvent, FileOperation } from './core/agent-event';
|
|
2
|
+
export { AiType, AI_TYPES, AI_TYPE_UNKNOWN, AgentEventKind, AgentHookEvent, FileOperation } from './core/agent-event';
|
|
3
3
|
export { AI_TYPE_SH, AI_TYPE_TOKEN_SH, detectAiType } from './adapters/detect-ai';
|
|
4
4
|
export { FixHint, DisableEscape } from './core/fix-hint';
|
|
5
5
|
export { EditRuleBase, FileRuleBase, BashRuleBase, EmptyRuleConfig } from './core/rule-base';
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
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.BASH_SUBJECT = exports.READ_SUBJECT = void 0;
|
|
4
5
|
// Pluggable write-time validation framework for AI coding agents
|
|
5
6
|
var types_1 = require("./core/types");
|
|
6
7
|
Object.defineProperty(exports, "Violation", { enumerable: true, get: function () { return types_1.Violation; } });
|
|
@@ -17,6 +18,8 @@ Object.defineProperty(exports, "ResolvedRuleConfig", { enumerable: true, get: fu
|
|
|
17
18
|
// decides which harness produced it. `AgentHookEvent` sits beside the types above because it is the
|
|
18
19
|
// shape the adapters produce and the hook body consumes.
|
|
19
20
|
var agent_event_1 = require("./core/agent-event");
|
|
21
|
+
Object.defineProperty(exports, "AI_TYPES", { enumerable: true, get: function () { return agent_event_1.AI_TYPES; } });
|
|
22
|
+
Object.defineProperty(exports, "AI_TYPE_UNKNOWN", { enumerable: true, get: function () { return agent_event_1.AI_TYPE_UNKNOWN; } });
|
|
20
23
|
Object.defineProperty(exports, "AgentHookEvent", { enumerable: true, get: function () { return agent_event_1.AgentHookEvent; } });
|
|
21
24
|
Object.defineProperty(exports, "FileOperation", { enumerable: true, get: function () { return agent_event_1.FileOperation; } });
|
|
22
25
|
var detect_ai_1 = require("./adapters/detect-ai");
|
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":"
|
|
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"]}
|
package/templates/ai-hook.sh
CHANGED
|
@@ -195,6 +195,12 @@ WP_AID="$(printf '%s' "$PAYLOAD" | sed -n 's/.*"agent_id"[[:space:]]*:[[:space:]
|
|
|
195
195
|
FILE="$(printf '%s' "$PAYLOAD" | sed -n 's/.*"file_path"[[:space:]]*:[[:space:]]*"\([^"\\]*\)".*/\1/p')"
|
|
196
196
|
WP_CWD="$(printf '%s' "$PAYLOAD" | sed -n 's/.*"cwd"[[:space:]]*:[[:space:]]*"\([^"\\]*\)".*/\1/p')"
|
|
197
197
|
[ -n "$WP_CWD" ] || WP_CWD="$ROOT" # no cwd in the payload (older client, or a hand-run) → the shim's own tree
|
|
198
|
+
# WHICH HARNESS sent this call — the ONE discriminator, imported from ../adapters/detect-ai so the sh
|
|
199
|
+
# half of L0 and the JS half answer the identical question from one definition (the same twin pattern
|
|
200
|
+
# L0_ALLOW_ERE_SH / L0_ALLOW_JS uses). One `case`, no JSON parser: consistent with how every field
|
|
201
|
+
# above is `sed`-scraped, and the values are the AiType union's own strings so the twin-agreement spec
|
|
202
|
+
# compares them byte for byte instead of translating between two vocabularies.
|
|
203
|
+
case "$PAYLOAD" in *'"turn_id":'*) AI=codex ;; *) AI=claude-code ;; esac
|
|
198
204
|
# Best-effort AUDIT TRAIL of what L0 did with this call — every call, not just the broken ones. One
|
|
199
205
|
# tab-separated line per invocation into this TREE's own
|
|
200
206
|
# logs/L0-shim/<session>-<agent|coordinator>-<binName>.log (gitignored), so the
|
|
@@ -293,7 +299,7 @@ wp_log() { # $1 = L0 fault code (D|X|K|-), $2 = verdict label
|
|
|
293
299
|
# what distinguishes it from the ~50 constant bytes 'bin=' used to spend above.
|
|
294
300
|
_wp_row=1
|
|
295
301
|
case "$2" in ALLOW*) _wp_row=2 ;; DENY*) _wp_row=3 ;; esac
|
|
296
|
-
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s%s\t%s\t%s\n' "$(date '+%Y-%m-%dT%H:%M:%S%z' 2>/dev/null)" "$BIN_NAME" "$TOOL" "tree=$WP_TREE" "layer=L0" "row=$_wp_row" "shim=$ROOT" "$_wp_bin" "fault=$1" "$2" "$CMD_LOG" >> "$_wp_f"
|
|
302
|
+
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s%s\t%s\t%s\n' "$(date '+%Y-%m-%dT%H:%M:%S%z' 2>/dev/null)" "$BIN_NAME" "$TOOL" "ai=$AI" "tree=$WP_TREE" "layer=L0" "row=$_wp_row" "shim=$ROOT" "$_wp_bin" "fault=$1" "$2" "$CMD_LOG" >> "$_wp_f"
|
|
297
303
|
} 2>/dev/null || true
|
|
298
304
|
}
|
|
299
305
|
BROKEN_BIN=""
|
|
@@ -348,6 +354,14 @@ if [ "$TOOL" = "Read" ]; then
|
|
|
348
354
|
wp_log "$WP_FAULT" ALLOW-READ # you must be able to read to work out how to fix this
|
|
349
355
|
exit 0
|
|
350
356
|
fi
|
|
357
|
+
case "$TOOL" in
|
|
358
|
+
webrun|collaborationspawn_agent|collaborationwait_agent|view_image|update_plan)
|
|
359
|
+
# Nothing to judge — the Codex tools that are neither a shell command nor a file edit. sh twin of
|
|
360
|
+
# isAllowed()'s L0_IGNORED_TOOLS branch; see there for why the list is EXPLICIT and why apply_patch
|
|
361
|
+
# (Codex's only WRITE) is not on it.
|
|
362
|
+
wp_log "$WP_FAULT" ALLOW-IGNORED
|
|
363
|
+
exit 0 ;;
|
|
364
|
+
esac
|
|
351
365
|
case "$FILE" in
|
|
352
366
|
*/webpieces.config.json|webpieces.config.json)
|
|
353
367
|
wp_log "$WP_FAULT" ALLOW-CONFIG # the always-allowed recovery target — every guard is configured from it
|