@sema-agent/core
Version:
Stateless, task-oriented AI agent core
407 lines (406 loc) • 30 kB
TypeScript
/**
* One deny entry: a `/`-separated run of path SEGMENTS. `*` matches any run of non-separator
* characters within one segment and is the ONLY metacharacter (everything else is literal). The run
* may match ANYWHERE in the judged path (`.ssh` covers `~/.ssh/…` and any other `.ssh` directory) —
* unless the pattern opens with the HOME word ({@link READ_DENY_HOME_WORD}): `~/<run>` matches only where
* the run starts right below the execution environment's home directory (`~/.sema/settings.json` is the
* home's file, never a project's `.sema/settings.json`). Only a FIRST segment that is exactly `~` is the
* word; `~user` and a `~` further along are literal segments.
* String shorthand ≡ `{ pattern }`. Matching is ASCII-case-insensitive by default (a case-insensitive
* filesystem serves `.SSH` and `.ssh` as the same directory); `caseSensitive: true` opts a single
* entry out. Unicode normalization (NFC/NFD) and volume-level case semantics are NOT modeled (v1
* residual, shared with the write guard; the canonical view — realpath returns the on-disk spelling —
* covers half of it).
*/
export type ReadDenyEntry = string | {
pattern: string;
caseSensitive?: boolean;
};
/** The HOME word of the pattern grammar ({@link ReadDenyEntry}): a FIRST segment spelled exactly this anchors the run
* right below the execution environment's home directory. */
export declare const READ_DENY_HOME_WORD = "~";
/** A pattern read as its anchor and its run: `~/a/b` ⇒ the run `a/b`, home-anchored; any other pattern ⇒ itself,
* unanchored. The one reader of the home word — the compiler, the traversal globs, the classifier's rendering and the
* write face's mirror all take the run from here. */
export declare function readDenyPatternRun(pattern: string): {
readonly homeAnchored: boolean;
readonly run: string;
};
/** How many leading segments of a run are LITERAL — the run's head before the first segment carrying `*`. A home-anchored
* row's head names one location below the home, which a mount resolves to where it really leads
* ({@link compileReadDeny}'s `linked`); the segments after it are matched below that location as the run matches. */
export declare function readDenyRunHead(run: string): number;
/** The normalized (validated, defaulted) form of one entry. */
export interface NormalizedReadDenyEntry {
pattern: string;
caseSensitive: boolean;
}
/** A deny verdict: which entry's pattern matched. */
export interface ReadDenyHit {
pattern: string;
/** The view string the pattern actually matched (set by {@link CompiledReadDeny.matchTarget}: the
* canonical key, or the lexical view when only the SPELLING matched). Disclosure must cite the
* matched view — under a symlink/case alias the two strings differ, and naming the other one
* sends the reader chasing a path the pattern does not match. */
matchedView?: string;
}
/** The two faces of the deny table: what a READ must not reach, and what a WRITE must not reach. */
export declare const DENY_FACES: readonly ["read", "write"];
export type DenyFace = (typeof DENY_FACES)[number];
/**
* A deny row in the rule DSL's PATH grammar — a `Read(<pattern>)` / `Edit(<pattern>)` deny rule, compiled by the
* rule compiler against its own bases (`createPermissionRulePolicy`). It joins the SAME table as the segment rows: one
* judge (`matchPath` / `matchTarget`), one refusal sentence, one classifier rendering — only the row's reach is the
* rule grammar's (the path it names and everything under a directory it names), not a segment run.
* · `rule` — the rule as written, the row's name wherever a refusal or the classifier names it;
* · `tool` / `pattern` — the rule's two parts, for a rendering that states the pattern inertly;
* · `faces` — which faces the row guards (`Read` ⇒ read and write: a path whose reads are denied is not written
* either; `Edit` ⇒ write);
* · `reaches` — the rule compiler's own reach over an ABSOLUTE path.
*/
export interface PathRuleDenyRow {
readonly rule: string;
readonly tool: string;
readonly pattern: string;
readonly faces: readonly DenyFace[];
readonly reaches: (absolutePath: string) => boolean;
/** The row's HEAD — the absolute location its reach starts at (the rule model's `pathRuleHeadOf`: the pattern's
* literal prefix under its base, a directory rule's directory), spelled so the execution environment can resolve it.
* A leg judges the row where that location LEADS too ({@link withRuleRowsWhereTheyLead}). Absent on a hand-built
* row, or one whose head cannot be read: it is judged as spelled. */
readonly head?: string;
}
/**
* The rule rows as a leg judges them: each row as spelled, and — for every location on the way to the row's HEAD (each
* leading part of it: the anchor's children down to the head itself) that the environment resolves to another spelling
* (a link or firmlink: `/tmp` → `/private/tmp`, `/var` → `/private/var`, a project root, a home entry or a directory
* inside the rule reached through a symlink) — the same row at that spelling: a path at or below the resolved location
* is judged as its spelling at or below that part. Every read and write face judges the CANONICAL key of a target beside
* its spelled view, so a row whose head is spelled through a link would otherwise never meet a target named by its real
* location — whether the row was written with the short spelling and the call with the long one, or the reverse. Every
* part, not the head alone: a link inside the rule's literal prefix that is re-pointed after the rows were placed still
* meets the row through the parts above it (`<real root>/alias/**` holds when `alias` moves). One rule for every row
* form (absolute, home, root, working directory, directory): the reference compiles the same "physical twin" of every
* deny / ask rule's literal prefix (cli280.js `ei` 9766583, compiled in `mt` 9767083). `leadsTo` is the leg's resolution,
* the one the faces mint their keys with (`undefined` = could not resolve: no twin there); each location is resolved
* once, and a part that only extends its parent's resolution adds no row.
*/
export declare function withRuleRowsWhereTheyLead(rows: readonly PathRuleDenyRow[], leadsTo: (head: string) => Promise<string | undefined>): Promise<PathRuleDenyRow[]>;
/**
* The WRITE face of the deny table: the rule rows whose faces include `write` — the judge the shell station reads for a
* command's write targets (a redirection, `tee`, `sed -i`, `cp` / `mv` / `touch` / `mkdir` / `rm` operands). Same row
* type, same judgment (`ruleRowHit`) as the read face's rule rows; a hit names the row by its rule text.
*/
export interface WriteDenyMatcher {
/** Which face this judge is — the write tools' path resolution reads it to word and code the refusal as a write. */
readonly face: "write";
readonly ruleRows: readonly PathRuleDenyRow[];
matchPath(path: string): ReadDenyHit | null;
/** The target judgment the read face makes, on this face's rows: canonical key, lexical view, the leaf chain's hops. */
matchTarget(canonicalKey: string, lexicalView?: string, opts?: {
readonly aliasResolved?: boolean;
readonly hops?: readonly string[];
}): ReadDenyHit | null;
}
/** Compile the write face over the rule rows (the rows not on it are dropped here, once). */
export declare function compileWriteDeny(rules?: readonly PathRuleDenyRow[]): WriteDenyMatcher;
/** One compiled ripgrep glob flag for the traversal legs (`--iglob` = case-insensitive entry). */
export interface ReadDenyRgGlob {
flag: "--glob" | "--iglob";
glob: string;
}
/**
* The compiled deny judge — ONE compilation, consumed by every judgment point (target judgment in
* resolveKey, the JS walker, the rg exclusion/probe legs, the classify operand probe, the ENOENT
* sibling filter, the attachment reader), so the legs cannot drift apart.
*/
export interface ReadDenyMatcher {
/** Normalized entries (built-ins first, then additions; exact duplicates folded). The disclosure /
* persistence face. */
readonly entries: readonly NormalizedReadDenyEntry[];
/** The rule-DSL rows on the READ face ({@link PathRuleDenyRow}), judged after the segment rows. Not persisted:
* they are the caller policy's, which a resume re-supplies. Absent on a matcher compiled without rules. */
readonly ruleRows?: readonly PathRuleDenyRow[];
/** Judge ONE path view (segment-window semantics). Returns the first matching entry, or null.
* `aliasResolved` = the key's MINTER reports the real filesystem already collapsed any win32
* component alias in it (canonicalizeTarget's realpath arm); unset ⇒ both readings are judged,
* which is the fail-closed default. `walked` = the path is a MEMBER a traversal enumerated below a
* root the command's text names, not a spelling of the command: the rule-DSL rows do not judge it (a
* `Read(<pattern>)` deny reaches what the command SPELLS — the reference implementation's best-effort
* reading of a rule against a command's arguments), while the segment rows (the built-in tiers, the
* deployment's patterns) reach every path a read can open. Unset ⇒ every row, the fail-closed default. */
matchPath(path: string, opts?: {
readonly aliasResolved?: boolean;
readonly walked?: boolean;
}): ReadDenyHit | null;
/** §3.3 target judgment: canonical view + lexical view + every hop of the leaf symlink chain (`hops`,
* each an unresolved spelling some link wrote); ANY hit decides. `aliasResolved` applies to the
* CANONICAL arm only — the lexical view and the hops are unresolved spellings. */
matchTarget(canonicalKey: string, lexicalView?: string, opts?: {
readonly aliasResolved?: boolean;
readonly hops?: readonly string[];
}): ReadDenyHit | null;
/** Traversal exclusions for the rg legs: `!`-polarity glob pairs — `!**\/<p>` plus `!**\/<p>/…`
* (the entry itself and its descendants). An entry placed at a home carries none (a glob is relative to the
* search root): `matchPath` judges it on the rg leg's output and in the walker. */
readonly rgExclusionGlobs: readonly ReadDenyRgGlob[];
/** Positive-polarity twins of the exclusions, for the bounded existence probe (first hit = entries
* matching the deny list exist under the scope and were excluded). */
readonly rgProbeGlobs: readonly ReadDenyRgGlob[];
/** Whether an entry that carries NO traversal glob (one placed at a home) can match a path under `scope` (an absolute
* directory): the rg leg cannot exclude such an entry, and a search that reads it and prunes afterwards answers
* differently by whether the entry's content matched — so a search over such a scope takes the walker, which prunes
* by path before reading. A scope that is not rooted answers true. */
unexcludableUnder?(scope: string): boolean;
/** A row that lies AT or BELOW `path` — the path is inside one of the row's matches, or a match would run on below the
* path from its own segments ({@link rowMeetsPath}) — so moving, copying or removing the path changes what the row
* covers. A rule row's reach below a path is not enumerable: any rule row answers. */
rowAtOrBelow?(path: string): ReadDenyHit | null;
}
/** The DENY HALF of a shell read boundary, in the shape `BashReadonlyRootBoundary` declares it (structural:
* this module is the leaf every face depends on). `denyMatch` judges one LEXICALLY RESOLVED operand,
* returning the matched pattern or null; PRESENT-IFF there is something to judge. */
export interface ReadDenySeat {
/** `walked` — the path is a traversal's enumerated member, not a spelling ({@link ReadDenyMatcher.matchPath}). */
denyMatch?: (resolvedPath: string, opts?: {
readonly walked?: boolean;
}) => string | null;
/** {@link ReadDenyMatcher.rowAtOrBelow} for one lexically resolved path; present exactly when `denyMatch` is. */
denyAtOrBelow?: (resolvedPath: string) => string | null;
denyEntries: readonly string[];
}
/** The ONE adapter from a compiled matcher to that seat, so every face mounts it the SAME way and the "wire
* the judge only when it has something to judge" rule is spelled once. That rule is load-bearing: the
* seat's PRESENCE is itself read as a fact (the recursive-read demotion mints on "a deny judge is wired" =
* the check a traversal would outflank is in place), so a judge over an EMPTY set must not be wired. Call
* ONCE per mount and reuse the result — the returned closure is then the function OBJECT every face mounted
* from that matcher shares, which makes "the same judge" checkable rather than argued. */
export declare function readDenySeat(matcher: ReadDenyMatcher | undefined): ReadDenySeat;
/** The ONE naming of the deny table and the pattern that matched. Every face composes its own SUBJECT and
* VERB in front of it and its own DISPOSITION behind it (the verb is not shared: one face names the
* target, another the REQUESTED SPELLING under a symlink alias). What must not fork is the naming — a
* reader comparing two surfaces about one finding must not be reading two findings. A LIST of patterns is
* the same naming in the plural, for the one finding that names no single row: a traversal that goes PAST
* the judge names the table IN FORCE, never a match it did not make. Four composers: the
* structured read faces' refusal (`safety.ts`), the shell faces' refusal card (`readDenyRefusal`), the
* lexical walk's verdict sentence (`bash-readonly-classifier.ts`), and the full shell's MANDATED ask card,
* which is approvable and so states the finding without claiming a refusal. NOT composers, each stating
* its own fact: the compaction lane's withheld-attachment HTML attribute, and the traversal notes'
* withheld-COUNT lines, which name the table and a pattern LIST. */
export declare function sensitivePathDenyList(pattern: string | readonly string[], face?: DenyFace): string;
/** The deny table's REACH: a deny row holds INSIDE the containment roots, so no root grant answers it. */
export declare const SENSITIVE_PATH_DENY_SCOPE = "This list guards credential-class paths and applies regardless of the containment roots.";
/** The write face's reach, in the same place of the sentence: a write deny row holds under every root and every mode. */
export declare const WRITE_DENY_SCOPE = "A write deny row holds regardless of the containment roots and of the permission mode.";
/**
* THE deny refusal sentence — five segments, one mint, every read face: the ACTION, the ABSOLUTE target as the
* caller spelled it (with its canonical target in parentheses when the two differ), the DISPOSITION (a deny row is
* refused, never escalated), the table BY NAME with the PATTERN verbatim ({@link sensitivePathDenyList}), and
* one sentence of why ({@link SENSITIVE_PATH_DENY_SCOPE}). `matchedView` names the view the pattern actually hit
* when that is the requested SPELLING rather than the target — under a symlink or case alias the two strings
* differ, only one matches, and naming the other sends the reader chasing a path the pattern does not match.
* A face prefixes its own `Error (<tool>): ` and may append its own closing clause; the finding itself is
* never re-spelled.
*/
export declare function readDenySentence(finding: {
readonly action?: string;
readonly target: string;
readonly canonical?: string;
readonly pattern: string;
readonly matchedView?: string;
/** The table face that refused (default `read`): a write-face hit names the write list and its reach, and writes. */
readonly face?: DenyFace;
}): string;
/** The per-part budget of the deny sentence — the gate's own card-account cap, not a second number. */
export declare const READ_DENY_SENTENCE_PART_MAX = 200;
/** `s` verbatim when it fits `budget`, else elided in its MIDDLE ({@link elideMiddle}). */
export declare function fitMiddle(s: string, budget: number): string;
/** `s` shortened to `budget` by removing its MIDDLE, marked — cut through the repo's surrogate-safe
* primitive, never a raw `slice` (a raw cut leaves half an astral code point on a card and in a durable
* row). Under a dozen characters the HEAD is taken. */
export declare function elideMiddle(s: string, budget: number): string;
/**
* The closed set of built-in tiers. A tier is the deployment's selection unit
* ({@link ReadDenyBuiltinConfig.tiers}); every {@link READ_FACE_BUILTIN_DENY_TABLE} row belongs to
* exactly one. Unknown tier names refuse loudly — this array is the validation source and the
* admin-face vocabulary, never re-spelled downstream.
*/
export declare const READ_DENY_BUILTIN_TIERS: readonly ["credentials", "shell-history", "browser", "wallet", "agent-config"];
/** One member of {@link READ_DENY_BUILTIN_TIERS}. */
export type ReadDenyBuiltinTier = (typeof READ_DENY_BUILTIN_TIERS)[number];
/**
* One row of the built-in table: the canonical deny pattern (also the row's STABLE NAME — the
* `readDenyBuiltinExclude` key; renaming a pattern is a semantic change and deliberately invalidates
* configs naming the old spelling, loudly) and the tier it belongs to.
*/
export interface ReadDenyBuiltinRow {
readonly pattern: string;
readonly tier: ReadDenyBuiltinTier;
}
/**
* READ_FACE_BUILTIN_DENY_TABLE — the FULL built-in table with tier metadata: the material basis of
* the deployment admin face, what an org administrator "sees" before selecting tiers / excluding
* rows. Activation is a SEPARATE fact: the tiers of {@link READ_DENY_DEFAULT_TIERS} are active unless the
* deployment selects otherwise, the rest are inert until a deployment names their tier — so this is the
* MENU, never the active set. Rows stay curated regardless — the menu has to be worth switching on.
* The dotenv family is deliberately NOT here — workspace `.env` files are working material for the
* tasks this engine runs; an `outside_workspace` tier is likewise deliberately absent.
*
* Each row states its tradeoff. An unanchored row is matched as a segment run anywhere in the path, so a
* repo fixture spelled `fixtures/.ssh/id_rsa` is refused too — deliberate: the refusal names the pattern,
* and a false refusal on a fixture is the cheap direction (deny errs strict). A home-anchored row
* (`~/…`, {@link READ_DENY_HOME_WORD}) is matched only below the home directory.
*
* NOT listed, deliberately: `.env`/`.env.*` (ruled out — workspace material; the WRITE guard still
* covers them; the ENGINE's own `.env` under its data root is a different file and IS listed, see the
* agent-config tier); `*.pem`/`*.key`/`*.p12`/`*.pfx` (repo certificates/test keys are routinely
* READ — refusing them breaks ordinary work, the write guard covers the mutation direction);
* `.git/hooks`/`.git/config` (reads are harmless; writes are the escalation and stay guarded); the
* engine's own data root as a WHOLE (transcript integrity policy owns it with an ask on the write
* face, and the memory library the model is told to read and edit lives under it).
*
* agent-config tier — the agent's OWN user-level configuration files whose `env` / server blocks carry API
* keys in cleartext (the shell's `settings.json` env block, an MCP server env, the engine host's data-root
* `.env`). Unguarded, a structured read of one of these lands the key verbatim in the tool result and
* therefore in the session record. Every row is HOME-ANCHORED: the user's own files under the home
* directory are refused, while a PROJECT's file of the same name (`./.sema/settings.json`, `./.mcp.json`,
* `./.claude/settings.json`) is working material the person edits with the model and reads normally — its
* writes are the write faces' business. Rows name FILES, not the config DIRECTORIES: `.sema`/`.claude` also
* hold agents, skills, hooks and (under the shell's layout, `~/.sema/engine-data`) the engine data root
* with the memory library — material the model reads and edits as ordinary work, and a directory-wide row
* would need a data-root passthrough (one more rule) to keep it readable. Token
* STORES of the same tooling (`.credentials.json`, an agent CLI's `auth.json`, …) are credentials rows, not
* agent-config rows: a deployment that switches this tier off to debug its agent configuration must
* not also expose the OAuth stores beside it. The rows' RUNS are ALSO the opt-in write deny set's
* agent-configuration block ({@link import("../../core/sensitive-path-policy.js").agentConfigDenyPatterns},
* which reads them from here; contract sentence there), so a row added here is judged by both faces.
*/
export declare const READ_FACE_BUILTIN_DENY_TABLE: readonly ReadDenyBuiltinRow[];
/**
* The DEFAULT tier selection — what an unconfigured deployment activates: the `credentials` tier (key
* material, cloud and registry credential files, token stores) and the `agent-config` tier (the agent's own
* user-level configuration files, home-anchored). The `shell-history`, `browser` and `wallet` tiers are a
* deployment's opt-in. Activation is a deployment decision with a visible seat, and the leg's wiring
* manifest reports which tiers it got.
*
* The NAMED default rather than an inline literal: `[]` and absence stay separately spellable on the
* seat, and a deployment profile wanting a different default changes THIS expression, not call sites.
*/
export declare const READ_DENY_DEFAULT_TIERS: readonly ReadDenyBuiltinTier[];
/**
* The deployment's built-in-table configuration — deployment seat ONLY (RunnerDeps /
* HandsToolkitOptions; deliberately NOT a TaskSpec key and NOT in the governed workflow whitelist,
* so no task or governed script can widen below its deployment).
* - `tiers` — EXACTLY-these tier selection: listed tiers are active, unlisted ones are not
* (`[]` = no built-in tiers at all — an explicit, legal posture; absent = {@link READ_DENY_DEFAULT_TIERS}).
* - `exclude` — per-row removal by canonical pattern name, applied AFTER tier selection.
* Unknown tier names / unknown row names refuse loudly — never silently ignored.
*/
export interface ReadDenyBuiltinConfig {
tiers?: readonly string[];
exclude?: readonly string[];
}
/**
* Resolve the ACTIVE built-in rows under a deployment configuration (validated, loud — all three
* bad-value states throw: garbage shapes, unknown tier names, unknown row names). The one
* resolution used by {@link compileReadDeny}; exported so an admin face can preview the effective
* table under a candidate configuration with the engine's own rules.
*/
export declare function resolveReadDenyBuiltins(config?: ReadDenyBuiltinConfig): readonly ReadDenyBuiltinRow[];
/**
* The deployment's built-in configuration as ONE validated snapshot, read off the seats' OWN values: each seat is an
* array of names or absent — any other value (a string, the empty string included) refuses loudly, naming the seat,
* before anything is spread or copied (a spread string reads as its characters, or as no names at all). The names are
* then judged by {@link resolveReadDenyBuiltins}. The snapshot holds copies: a caller mutating its arrays afterwards
* moves nothing, so every compile of one mount reads the same selection.
*/
export declare function readDenyBuiltinConfigOf(seats: {
readonly tiers?: unknown;
readonly exclude?: unknown;
}): ReadDenyBuiltinConfig;
/**
* The tiers a deployment configuration ACTIVATES — the tier names of {@link resolveReadDenyBuiltins}'s
* surviving rows, in {@link READ_DENY_BUILTIN_TIERS} order (the closed set is the ordering source, so
* two deployments that select the same tiers in different spellings report the same list).
*
* MATERIAL, not intentional: a tier whose every row was removed by `exclude` activates nothing and
* is NOT reported. An EMPTY result means the built-in table contributes nothing on this leg — the
* unconfigured default. Same loud validation as the resolution it wraps.
*/
export declare function activeReadDenyBuiltinTiers(config?: ReadDenyBuiltinConfig): readonly ReadDenyBuiltinTier[];
/**
* READ_FACE_DEFAULT_DENY_ENTRIES — the DEFAULT-ACTIVE built-in set: {@link READ_FACE_BUILTIN_DENY_TABLE}
* under {@link READ_DENY_DEFAULT_TIERS} — the answer to "what does an unconfigured deployment refuse, from
* this table". Derived, so it follows the default selection; the tier-annotated table is where a consumer
* renders "what COULD be switched on".
*/
export declare const READ_FACE_DEFAULT_DENY_ENTRIES: readonly ReadDenyEntry[];
/** A compiled segment-run pattern (shared engine — the write guard delegates here too). */
export interface CompiledSegmentPattern {
raw: string;
segments: RegExp[];
/** Fold BOTH sides through {@link asciiLower} before testing (compile lowered the pattern side). */
foldAscii: boolean;
/** The segment lists of the directories the run is ANCHORED at (folded as the pattern folds): the run must start
* right after one of them. Absent = the run matches anywhere. */
anchors?: readonly (readonly string[])[];
/** Judged in a ROOTED view only: an extra location of a home-anchored row (where its literal head leads). A view that
* is not rooted cannot be placed and is covered by the row's own run, held anywhere. */
rootedOnly?: true;
}
/**
* Compile one segment-glob pattern (shared engine). `fold`:
* · "none" — byte-exact segments;
* · "ascii" — ASCII-case-insensitive (deny-set contract);
* · "unicode" — RegExp `i` flag (the write guard's historical host-keyed behavior — kept for it
* byte-identically; NOT used by the deny set).
* Returns null when the pattern reduces to zero segments — the CALLER owns its loud path (the two
* consumers refuse with their own surface-specific texts).
*/
export declare function compileSegmentPattern(raw: string, fold: "none" | "ascii" | "unicode"): CompiledSegmentPattern | null;
/**
* Does the path contain a compiled pattern as a CONTIGUOUS run of full path segments? (Shared engine:
* `.git/hooks` matches `/repo/.git/hooks/pre-commit`; `.ssh` matches any `.ssh` segment.) An ANCHORED pattern
* ({@link CompiledSegmentPattern.anchors}) matches only where the path's leading segments are one anchor's and
* the run starts right after them. Splits on BOTH separator families — win32 canonical keys are backslash-form.
* Returns the matched raw pattern or null.
*/
export declare function matchSegmentPatterns(path: string, compiled: readonly CompiledSegmentPattern[], opts?: {
readonly aliasResolved?: boolean;
}): string | null;
/**
* the PURE validation/canonicality predicate over a PERSISTED deny entry (checkpoint v9
* face section), for the resume pre-CAS ladder: it must refuse BEFORE the approval CAS everything
* {@link compileReadDeny} would throw on AFTER it (a post-CAS compile throw consumes the human's
* approval and strands the row), plus non-canonical spellings (normalizeEntry canonicalizes at every
* mint, so a persisted non-canonical pattern was not minted by any release = damaged). Returns the
* refusal reason, or null when the entry is valid.
*/
export declare function persistedReadDenyEntryProblem(e: unknown): string | null;
/** The home spellings a home-anchored row is placed under, judged as given: an array of absolute path strings, loud
* otherwise and naming `readDenyHomes` — a relative or empty spelling places the row where no home is, and a bare
* string would be read as its characters. */
export declare function checkedReadDenyHomes(homes: unknown, seat?: string): readonly string[];
/**
* Compile the read-face deny judge: ACTIVE built-in rows ∪ additions. Additions are add-only at every
* task layer (`[]` additions ≡ absent, deliberately a no-op rather than a refusal: an array API
* cannot observe a "replace" intent, and union-with-empty is the identity, not a silent fallback);
* the built-in half is selected by the DEPLOYMENT's `builtin` config (absent = the default tier
* selection, see {@link resolveReadDenyBuiltins}). A deployment that turned a tier off can still
* re-add single patterns through the additions seats — they then ride as ordinary additions.
* Bad entry shapes / zero-segment patterns / unknown tier or row names throw loudly, naming the
* layer or knob.
*
* `homes` — the spellings of the execution environment's home directory a home-anchored entry
* ({@link READ_DENY_HOME_WORD}) is placed under: the declared home, the account home and, where the environment's
* canonical spelling of one differs, that one too (the faces judge canonical keys beside the spelled views). Absolute
* spellings only — any other shape refuses loudly, naming `readDenyHomes`. EMPTY = the environment declares no
* home: a home-anchored entry then matches its run anywhere — a row that cannot be placed holds wider, never narrower.
*
* `linked` — where a home-anchored row's LITERAL HEAD ({@link readDenyRunHead}) really leads, keyed by the row's
* pattern: the canonical locations a mount resolved (`readDenyHomeLinksOf`, read-deny-home.ts) where one of the
* head's segments is a link (`~/.sema` linked into a dotfiles directory). The row is placed at each location too —
* the same row, the same name in every refusal — with the rest of its run matched below it. The read faces judge
* canonical keys, so every spelling that reaches the linked file meets the row there. Absolute locations only.
*/
export declare function compileReadDeny(additions?: readonly ReadDenyEntry[], layer?: string, builtin?: ReadDenyBuiltinConfig, rules?: readonly PathRuleDenyRow[], homes?: readonly string[], linked?: ReadonlyMap<string, readonly string[]>): ReadDenyMatcher;