/** * THE ACTION CODEBOOK, as a type. These are the five actions GUARD_MATRIX.md numbers 1-5, and they * are the vocabulary EVERY layer reports in — so one grep spans L-1, L0, L1 and L2. * * The three distinctions this exists to make, none of which `'ALLOW' | 'BLOCK'` could: * * ALLOW no objection — the call was HANDED DOWN to the next layer. A layer saying ALLOW * is NOT saying the call ran: the layer below it, or the OTHER parallel hook, may * still deny. This is L1's `ACT_DOWN`. * ALLOW_EXEMPT out of scope by construction — allowed, and evaluation STOPS here. L1's * `ACT_EXEMPT`. * ALLOW_FAIL_OPEN state could not be established, so nothing was judged. Keeping this distinct * from ALLOW is the entire point of the type: a fail-open allow and a real allow * that look identical make it impossible to tell whether the guards are protecting * anything or quietly abstaining. It used to be a `' (fail-open)'` SUBSTRING on the * reason field, which is exactly why the abstentions were never countable. * BLOCK_AI_CURE blocked, and the printed cure is a command the AI can run itself. * BLOCK_HUMAN blocked, and it needs a human decision — or a delegation (spawn a subagent) that * the blocked agent cannot perform for itself. * * Hard cut, per CLAUDE.md: `'BLOCK'` is GONE rather than aliased, so every construction site fails to * compile and has to say which kind of block it is. Before, that question had exactly one wrong * answer available — silence. */ export type Verdict = 'ALLOW' | 'ALLOW_EXEMPT' | 'ALLOW_FAIL_OPEN' | 'BLOCK_AI_CURE' | 'BLOCK_HUMAN'; /** * WHICH ROW of WHICH layer's decision table produced this line. Data-only → a class, per CLAUDE.md. * * `row` is the row NUMBER from the layer's row array (`L1_ROWS[i].num`, `L2_ROWS[i].num`) — the * same number the generated doc prints, because the doc is rendered from that same array. So a log * line joins to its matrix row BY NUMBER, and checking observed behaviour against the documented use * cases becomes a lookup rather than an investigation. */ export declare class MatrixRef { readonly layer: string; readonly row: string; constructor(layer: string, row: string); } /** * The layer tokens. EVERY layer now cites a row: L0 through its two decision-matrix rows, L1 through * L1_ROWS, and L2 through L2_ROWS (see `matrixL2Row`). This used to say `'-'` was for "a layer with no * row array YET (L2 is the un-converted one)" — L2 is converted, and a comment describing a state the * code left behind is exactly the kind of doc a reader trusts and should not. * * `'-'` survives for ONE case, and it is a real one: an L2 reason that no row claims. See matrixL2Row. * * These are REQUIRED at the constructor, not defaulted: a defaulted `MatrixRef` would make the * uncited case reachable by doing nothing and impossible to grep — the same defect this file's own * docblock argues against for `'BLOCK'`, where silence was the one wrong answer available. * * L0 NOW CITES ITS ROW TOO. It used to be `'-'` on the grounds that "L0's faults are a table of letters * rather than numbered rows" — but L0 has BOTH: the letter says WHICH fault, and the numbered decision * matrix (`renderGuardMatrixDoc`) says which of its three rows was taken. There is one ALLOW row and one * BLOCK row, so the pair `row=` + `fault=` pins the decision exactly, and the deny an agent reads now * carries the identical pair. That is the join: one grep spans the deny, the log line and the doc. * Two constants, not one `MATRIX_L0`, because the two call sites are a cure-bypass ALLOW and an L0 * BLOCK — one token covering both is what made the row unciteable in the first place. */ export declare const MATRIX_L0_ALLOW: MatrixRef; export declare const MATRIX_L0_BLOCK: MatrixRef; /** * The L2 reference for one decision, with the row DERIVED FROM THE REASON. * * There is no `MATRIX_L2` constant any more, and its absence is the point: a single shared instance * meant every L2 line in the repo carried `row=-`, which reads as "L2 has no rows" rather than "this * decision was not classified". Deleting it makes every construction site name a reason, and the reason * is the only thing a call site has that identifies which row it is an instance of. * * `l2RowForReason` returns null for a reason no row claims, and that renders as `'-'` — visible in the * log, not silently absorbed into a default row. l2-matrix.spec.ts reads the four guard sources and * fails the build if any reason literal in them is unmapped, so `-` should never appear in practice; * when it does, it is a genuine hole in the table and the log says so. */ export declare function matrixL2Row(reason: string): MatrixRef; /** * The L2 stream, with NO row — for the two kinds of line that genuinely are not an instance of a row. * * 1. The runner's AGGREGATE bash lines ("no bash-guard block" / "bash-guard block"). They summarise * the whole guard set's answer for one command, not one row's verdict; the per-guard lines that * DO cite rows are written alongside them by the guards themselves. * 2. `whole-repo-build-guard`, which is not a branch-state policy at all — it is the experimental * home-config guard, has no webpieces.config.json entry, and shares this stream only because the * stream is "bash decisions", not "L2 rows". * * A NAMED constant rather than an inline `new MatrixRef('L2', '-')`, so `grep MATRIX_L2_UNROWED` lists * every uncited line and the list stays short and arguable. It is deliberately NOT called `MATRIX_L2`: * the old name was used by everything and made "L2 has no rows" indistinguishable from "this line is * not a row". */ export declare const MATRIX_L2_UNROWED: MatrixRef; export declare class GuardDecision { rule: string; tool: string; target: string; branch: string; verdict: Verdict; reason: string; cache: string; /** * The L0 fault this decision IS, in the codebook's letter (core/l0-fault-codes.ts), or `-` for an * ordinary rule decision. The `sh` shim has always stamped `fault=` on its own stream; the three * JS-side faults (S/C/Y) reached this one with no label at all, so `grep fault=S` found nothing * even while an S storm was blocking every call. */ fault: string; /** Which layer + row decided this. See MatrixRef — it is what joins a log line to the doc. */ matrix: MatrixRef; constructor(rule: string, tool: string, target: string, branch: string, verdict: Verdict, reason: string, cache: string | undefined, fault: string, matrix: MatrixRef); } /** * Append one tab-separated line per L2 decision to `.webpieces/logs/L2-decisions/.log`, where * is LogStream's `--` key (a caller that never * identified renders as `unknown-coordinator-hook` — there is no un-keyed name). * `root` is * the repo/workspace root that holds `.webpieces` (callers pass workspaceRoot, or a * RepoRootFinder-resolved root at the pre-load config-bypass site — never a raw cwd, so a bypass * logged from a subdir never scatters a stray `.webpieces`). Swallows all errors — logging must never * block or fail a hook. */ export declare function logGuardDecision(root: string, decision: GuardDecision): void; /** * The L1 stream — `.webpieces/logs/L1-location/.log`. * * L1 had NO stream. Its three blocking paths wrote into L2's file under an implementation name * (`force-to-root`, `trinary-version-skew`, `cd-must-be-first`), and its NON-blocking outcomes — * the exempt row and the three hand-down rows — wrote nothing at all. So "L1 had no objection" was * unobservable, and "show me every L1 decision" had no answer: L1 existed in the trail only as the * `root=` / `projectDir=` / `tree=` columns stapled onto somebody else's line. * * A SIBLING rather than a `base` parameter on logGuardDecision, deliberately: that signature is what * the process-wide `logStream` singleton exists to keep unchanged (see LogStream's docblock), and * `INVOCATION_LOG_FILE` already establishes the pattern of a second stream owning its own name in * this same module. */ export declare function logL1Decision(root: string, decision: GuardDecision): void; /** * What the guard SAW on one invocation, captured up front and held until the outcome is known. * Data-only (per CLAUDE.md: classes for data, explicit construction). */ export declare class GuardInvocation { readonly root: string; readonly timestamp: string; readonly tool: string; readonly target: string; readonly branch: string; readonly sync: string; readonly projectDir: string; constructor(root: string, timestamp: string, tool: string, target: string, branch: string, sync: string, projectDir: string); } /** * The per-INVOCATION stream — `.webpieces/logs/calls/.log` (see LogStream for the writer * key), one line for EVERY guards-hook * call (allow or block, bash or file), unlike `L2-decisions/` which records only the calls a * rule actually judged. It captures the tool, the command/file, the live git branch, the async-written * main-sync-status.json snapshot (branch / merged / fork-point / conflict), and — since this class * replaced a bare log-and-forget function — HOW THE CALL ENDED. * * WHY IT IS TWO CALLS. The line used to be written the moment the hook started, so it could not carry * a verdict: the decision had not been made yet. Answering "what happened to this call?" therefore * meant joining this file against the L2 decision stream BY TIMESTAMP, which is exactly the kind of * reconstruction a log exists to make unnecessary. So {@link begin} now only CAPTURES (including the * git/cache reads, which must still happen while the hook is running), and {@link finish} — called * from the hook's single terminal boundary, emitAllow/emitDeny — writes the whole line once the * outcome is known. The two streams stay distinct in purpose: this one is "every call and how it * ended", the decision log remains "every judgement and why". * * Every error is swallowed: logging must never block or fail a hook. */ export declare class InvocationLog { private pending; /** * Capture the context of one invocation. `cwd` is the AI's working dir; the repo root that owns * `.webpieces` is resolved from it. Writes NOTHING — {@link finish} does that. */ begin(cwd: string, tool: string, target: string): void; /** * Write the captured line, now stamped with the outcome. A no-op when nothing was captured (the * 'rules' hook, or a terminal boundary reached before begin()), and it clears the pending entry so * a second emit cannot double-log. * * `rule` is the rule that blocked, or '-' when there is none; `fault` is the L0 fault code when this * call ended on one (S/C/Y — the JS-side faults), else '-'. FIELD ORDER IS APPEND-ONLY: the five * original fields keep their positions (cleanup automation mines this file), and `guards=` / * `rule=` / … / `fault=` are added at the end. */ finish(verdict: Verdict, rule: string, fault?: string): void; } export declare const invocationLog: InvocationLog; export declare function branchForLog(root: string): string;