@sema-agent/core 7.33.7 → 7.34.0

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.
Files changed (206) hide show
  1. package/CHANGELOG.md +357 -0
  2. package/dist/agents/cross-session-judge.d.ts +16 -7
  3. package/dist/agents/cross-session-judge.js +18 -15
  4. package/dist/agents/peer-session-drain.d.ts +4 -0
  5. package/dist/agents/peer-session-drain.js +5 -2
  6. package/dist/agents/repair-loop.d.ts +1 -1
  7. package/dist/agents/subagent.d.ts +3 -0
  8. package/dist/agents/subagent.js +12 -3
  9. package/dist/brain/anthropic.js +1 -0
  10. package/dist/brain/open-responses.js +1 -0
  11. package/dist/brain/openai.js +1 -0
  12. package/dist/brain/stream-engine.d.ts +3 -0
  13. package/dist/brain/stream-engine.js +2 -1
  14. package/dist/core/a2a.d.ts +3 -1
  15. package/dist/core/a2a.js +1 -1
  16. package/dist/core/ask-origin.d.ts +351 -154
  17. package/dist/core/ask-origin.js +223 -45
  18. package/dist/core/ask-unresolvable-notice.d.ts +5 -2
  19. package/dist/core/ask-unresolvable-notice.js +4 -3
  20. package/dist/core/auto-mode-arming.d.ts +17 -4
  21. package/dist/core/auto-mode-arming.js +31 -6
  22. package/dist/core/auto-mode-defaults.d.ts +16 -7
  23. package/dist/core/auto-mode-defaults.js +2 -1
  24. package/dist/core/auto-mode-directive.d.ts +18 -30
  25. package/dist/core/auto-mode-directive.js +9 -18
  26. package/dist/core/auto-mode-prompt.d.ts +62 -1
  27. package/dist/core/auto-mode-prompt.js +42 -4
  28. package/dist/core/auto-mode-rebuild.d.ts +2 -1
  29. package/dist/core/auto-mode-rebuild.js +1 -0
  30. package/dist/core/auto-mode.d.ts +32 -3
  31. package/dist/core/auto-mode.js +80 -41
  32. package/dist/core/checkpoint-store.d.ts +50 -35
  33. package/dist/core/checkpoint-store.js +58 -3
  34. package/dist/core/child-clamp-seats.js +1 -0
  35. package/dist/core/classifier-unavailable-arm.d.ts +57 -0
  36. package/dist/core/classifier-unavailable-arm.js +17 -0
  37. package/dist/core/effective-path-target.d.ts +12 -0
  38. package/dist/core/effective-path-target.js +18 -7
  39. package/dist/core/engine-notice.d.ts +53 -2
  40. package/dist/core/fs-write-gate-policy.d.ts +5 -1
  41. package/dist/core/fs-write-gate-policy.js +4 -1
  42. package/dist/core/gate-fold.js +17 -1
  43. package/dist/core/gate-lanes.js +111 -46
  44. package/dist/core/gate-outcome.d.ts +40 -7
  45. package/dist/core/gate-outcome.js +26 -4
  46. package/dist/core/governance-codes.d.ts +1 -1
  47. package/dist/core/governance-codes.js +12 -0
  48. package/dist/core/hooks.d.ts +50 -29
  49. package/dist/core/hooks.js +1 -3
  50. package/dist/core/mcp.js +1 -1
  51. package/dist/core/mode-policy.d.ts +182 -0
  52. package/dist/core/mode-policy.js +179 -0
  53. package/dist/core/oracle-isolation.d.ts +3 -3
  54. package/dist/core/permission-rule-model.d.ts +16 -3
  55. package/dist/core/permission-rule-model.js +36 -3
  56. package/dist/core/permission-rule-provider.d.ts +22 -11
  57. package/dist/core/permission-rule-provider.js +17 -7
  58. package/dist/core/permission-rules.d.ts +23 -1
  59. package/dist/core/permission-rules.js +35 -8
  60. package/dist/core/persisted-rule-arms.d.ts +10 -9
  61. package/dist/core/persisted-rule-arms.js +9 -6
  62. package/dist/core/present-plan-tool.d.ts +1 -1
  63. package/dist/core/present-plan-tool.js +3 -3
  64. package/dist/core/read-only-shell.d.ts +19 -19
  65. package/dist/core/read-only-shell.js +38 -12
  66. package/dist/core/retired-keys.d.ts +9 -3
  67. package/dist/core/retired-keys.js +5 -1
  68. package/dist/core/runner/classifier-ladder.d.ts +43 -1
  69. package/dist/core/runner/classifier-ladder.js +29 -0
  70. package/dist/core/runner/contracts.d.ts +37 -35
  71. package/dist/core/runner/denial-limit-arms.d.ts +30 -7
  72. package/dist/core/runner/denial-limit-arms.js +15 -9
  73. package/dist/core/runner/env-capability-census.js +1 -1
  74. package/dist/core/runner/gate-exit.d.ts +30 -4
  75. package/dist/core/runner/gate-exit.js +28 -7
  76. package/dist/core/runner/permission-rule-lanes.d.ts +9 -5
  77. package/dist/core/runner/permission-rule-lanes.js +44 -40
  78. package/dist/core/runner/persisted-path-rows.d.ts +64 -0
  79. package/dist/core/runner/persisted-path-rows.js +89 -0
  80. package/dist/core/runner/prepare-artifact.d.ts +5 -1
  81. package/dist/core/runner/prepare-artifact.js +9 -3
  82. package/dist/core/runner/prepare-ask-lane.d.ts +5 -3
  83. package/dist/core/runner/prepare-ask-lane.js +1 -1
  84. package/dist/core/runner/prepare-caps-and-workflow.d.ts +39 -13
  85. package/dist/core/runner/prepare-caps-and-workflow.js +58 -29
  86. package/dist/core/runner/prepare-gate-stations.d.ts +33 -6
  87. package/dist/core/runner/prepare-gate-stations.js +32 -19
  88. package/dist/core/runner/prepare-hands-readface.d.ts +40 -17
  89. package/dist/core/runner/prepare-hands-readface.js +64 -53
  90. package/dist/core/runner/prepare-inherited-gate.d.ts +22 -22
  91. package/dist/core/runner/prepare-inherited-gate.js +37 -23
  92. package/dist/core/runner/prepare-park-ask.d.ts +2 -2
  93. package/dist/core/runner/prepare-park-ask.js +2 -1
  94. package/dist/core/runner/prepare-policy-chain.d.ts +22 -2
  95. package/dist/core/runner/prepare-policy-chain.js +39 -12
  96. package/dist/core/runner/prepare-protocol-tools.d.ts +28 -0
  97. package/dist/core/runner/prepare-protocol-tools.js +13 -3
  98. package/dist/core/runner/prepare-run-refs.d.ts +6 -6
  99. package/dist/core/runner/prepare-run-refs.js +9 -4
  100. package/dist/core/runner/prepare-safety-scan.d.ts +5 -0
  101. package/dist/core/runner/prepare-safety-scan.js +19 -3
  102. package/dist/core/runner/prepare-suspend-saga.d.ts +5 -5
  103. package/dist/core/runner/prepare-suspend-saga.js +6 -6
  104. package/dist/core/runner/prepare-task.js +51 -22
  105. package/dist/core/runner/prepare-wiring-manifest.d.ts +10 -6
  106. package/dist/core/runner/prepare-wiring-manifest.js +5 -3
  107. package/dist/core/runner/read-root-grant.d.ts +9 -0
  108. package/dist/core/runner/read-root-grant.js +12 -0
  109. package/dist/core/runner/retired-runner-deps.js +1 -0
  110. package/dist/core/runner/run-identity-wiring.js +1 -1
  111. package/dist/core/runner/run-leg.js +24 -15
  112. package/dist/core/runner/run-notification-lane.js +1 -0
  113. package/dist/core/runner/run-telemetry-and-budget-seats.d.ts +2 -2
  114. package/dist/core/runner/run-telemetry-and-budget-seats.js +1 -1
  115. package/dist/core/runner/run-turn-boundary.js +1 -1
  116. package/dist/core/runner/runtask.js +3 -0
  117. package/dist/core/runner/stream-mode-verb.d.ts +27 -0
  118. package/dist/core/runner/stream-mode-verb.js +19 -0
  119. package/dist/core/runner/turn-attachments.d.ts +3 -3
  120. package/dist/core/runner/turn-attachments.js +1 -1
  121. package/dist/core/runner/write-protection-station.d.ts +7 -1
  122. package/dist/core/runner/write-protection-station.js +6 -3
  123. package/dist/core/runner-deps.d.ts +31 -34
  124. package/dist/core/runtime-caps.d.ts +12 -3
  125. package/dist/core/safety-axis-vocab.d.ts +2 -2
  126. package/dist/core/safety-axis-vocab.js +1 -1
  127. package/dist/core/secret-env.d.ts +16 -2
  128. package/dist/core/secret-env.js +25 -1
  129. package/dist/core/sensitive-path-policy.d.ts +4 -2
  130. package/dist/core/sensitive-path-policy.js +39 -30
  131. package/dist/core/spec-contract.d.ts +15 -0
  132. package/dist/core/spec-contract.js +19 -10
  133. package/dist/core/task-event.d.ts +1 -1
  134. package/dist/core/task-result.d.ts +1 -1
  135. package/dist/core/task-spec.d.ts +41 -89
  136. package/dist/core/task-stream.d.ts +16 -0
  137. package/dist/core/terminal-cause.js +1 -0
  138. package/dist/core/tighten-task-spec.d.ts +2 -2
  139. package/dist/core/tighten-task-spec.js +12 -14
  140. package/dist/core/tool-catalog-entries.js +4 -3
  141. package/dist/core/tool-policy.d.ts +54 -25
  142. package/dist/core/tool-policy.js +43 -2
  143. package/dist/core/tool-registry.d.ts +2 -0
  144. package/dist/core/tool-registry.js +3 -0
  145. package/dist/core/tool-spec.d.ts +8 -9
  146. package/dist/core/trace.d.ts +7 -6
  147. package/dist/core/types.d.ts +2 -1
  148. package/dist/core/wiring-manifest.d.ts +31 -5
  149. package/dist/core/wiring-manifest.js +28 -1
  150. package/dist/core/write-protect.d.ts +11 -1
  151. package/dist/core/write-protect.js +26 -15
  152. package/dist/engine/execution-env/kill-tree.js +4 -1
  153. package/dist/engine/execution-env/node-execution-env.d.ts +3 -0
  154. package/dist/engine/execution-env/node-execution-env.js +34 -35
  155. package/dist/engine/harness/types.d.ts +6 -7
  156. package/dist/engine/llm/types.d.ts +2 -4
  157. package/dist/engine/loop/types.d.ts +7 -0
  158. package/dist/index.d.ts +15 -5
  159. package/dist/index.js +12 -3
  160. package/dist/orchestration/run-spec.d.ts +4 -0
  161. package/dist/orchestration/run-spec.js +4 -3
  162. package/dist/orchestration/workflow-governance.js +1 -1
  163. package/dist/orchestration/workflow-primitives.d.ts +1 -1
  164. package/dist/orchestration/workflow-size-guideline.js +2 -2
  165. package/dist/stores/file/fs-atomic.js +2 -1
  166. package/dist/tools/artifact/artifact-tool.d.ts +17 -0
  167. package/dist/tools/artifact/artifact-tool.js +16 -1
  168. package/dist/tools/fs/bash-readonly-classifier.d.ts +28 -7
  169. package/dist/tools/fs/bash-readonly-classifier.js +88 -0
  170. package/dist/tools/fs/containment-face.d.ts +9 -3
  171. package/dist/tools/fs/containment-face.js +2 -0
  172. package/dist/tools/fs/file-read-check.d.ts +49 -0
  173. package/dist/tools/fs/file-read-check.js +71 -0
  174. package/dist/tools/fs/fs-bash.d.ts +3 -3
  175. package/dist/tools/fs/fs-read.d.ts +1 -0
  176. package/dist/tools/fs/fs-search-tools.d.ts +2 -2
  177. package/dist/tools/fs/fs-search-tools.js +4 -4
  178. package/dist/tools/fs/fs-shared.js +20 -6
  179. package/dist/tools/fs/fs-write.d.ts +4 -3
  180. package/dist/tools/fs/fs-write.js +9 -9
  181. package/dist/tools/fs/index.d.ts +39 -3
  182. package/dist/tools/fs/index.js +38 -15
  183. package/dist/tools/fs/read-deny-home.d.ts +93 -0
  184. package/dist/tools/fs/read-deny-home.js +225 -0
  185. package/dist/tools/fs/read-deny.d.ts +176 -34
  186. package/dist/tools/fs/read-deny.js +268 -61
  187. package/dist/tools/fs/safety.d.ts +6 -1
  188. package/dist/tools/fs/safety.js +18 -5
  189. package/dist/tools/fs/search.d.ts +3 -0
  190. package/dist/tools/fs/search.js +47 -9
  191. package/dist/tools/fs/shell-read-check.d.ts +42 -11
  192. package/dist/tools/fs/shell-read-check.js +188 -13
  193. package/dist/tools/fs/shell-read-establish.d.ts +7 -3
  194. package/dist/tools/fs/shell-read-establish.js +134 -21
  195. package/dist/tools/fs/shell-read-identity.d.ts +7 -0
  196. package/dist/tools/fs/shell-read-identity.js +21 -0
  197. package/dist/tools/fs/shell-read-judge.d.ts +92 -32
  198. package/dist/tools/fs/shell-read-judge.js +43 -18
  199. package/dist/tools/fs/shell-read-plan.d.ts +4 -0
  200. package/dist/tools/fs/shell-read-plan.js +26 -4
  201. package/dist/tools/fs/shell-read-write-targets.d.ts +73 -0
  202. package/dist/tools/fs/shell-read-write-targets.js +173 -0
  203. package/package.json +3 -1
  204. package/test/export-surface.snapshot.json +103 -11
  205. package/wire-changes/core@7.33.8.json +165 -0
  206. package/wire-changes/core@7.34.0.json +2621 -0
@@ -0,0 +1,225 @@
1
+ import { canonicalizeTarget, isAbsolutePathForm, joinForFamily, normalizeAbsPathLexically, pathFamilyOf } from "./safety.js";
2
+ import { DEFAULT_GATE_AWAIT_TIMEOUT_MS, withGateTimeout } from "../../core/gate-bounded-await.js";
3
+ import { readDenyPatternRun, readDenyRunHead } from "./read-deny.js";
4
+ export function declaredReadDenyHomes(env) {
5
+ const home = env.homeDir;
6
+ return home !== undefined && isAbsolutePathForm(home) ? [home] : [];
7
+ }
8
+ export async function readDenyHomesOf(env, signal) {
9
+ return (await readDenyHomesAnswered(env, signal)).homes;
10
+ }
11
+ async function readDenyHomesAnswered(env, signal) {
12
+ const home = declaredReadDenyHomes(env)[0];
13
+ if (home === undefined)
14
+ return { homes: [], whole: true };
15
+ const canonical = await env.canonicalPath(home, signal);
16
+ if (canonical.ok)
17
+ return { homes: canonical.value !== home ? [home, canonical.value] : [home], whole: true };
18
+ return { homes: [home], whole: canonical.error.code === "not_found" || canonical.error.code === "not_directory" };
19
+ }
20
+ export async function readDenyHomeLinksOf(env, entries, homes, signal) {
21
+ const rows = [];
22
+ const familyOf = new Map();
23
+ for (const { pattern, caseSensitive } of entries) {
24
+ const { homeAnchored, run } = readDenyPatternRun(pattern);
25
+ if (!homeAnchored)
26
+ continue;
27
+ const head = run.split("/").filter(Boolean).slice(0, readDenyRunHead(run)).join("/");
28
+ if (head.length === 0)
29
+ continue;
30
+ const spelled = homes.map((home) => {
31
+ const family = pathFamilyOf({ root: home });
32
+ const at = normalizeAbsPathLexically(joinForFamily(family, home, head));
33
+ familyOf.set(at, family);
34
+ return at;
35
+ });
36
+ rows.push({ pattern, caseSensitive: caseSensitive === true, spelled });
37
+ }
38
+ const resolved = new Map(await Promise.all([...familyOf].map(async ([at, family]) => [at, await whereItLeads(env, at, family, signal)])));
39
+ const out = new Map();
40
+ const whole = [...resolved.values()].every((location) => location !== undefined);
41
+ for (const { pattern, caseSensitive, spelled } of rows) {
42
+ const fold = (p) => (caseSensitive ? p : p.replace(/[A-Z]/g, (c) => c.toLowerCase()));
43
+ const own = new Set(spelled.map(fold));
44
+ const locations = [];
45
+ for (const at of spelled) {
46
+ const location = resolved.get(at);
47
+ if (location !== undefined && !own.has(fold(location)) && !locations.includes(location))
48
+ locations.push(location);
49
+ }
50
+ if (locations.length > 0)
51
+ out.set(pattern, locations);
52
+ }
53
+ return { linked: out, whole };
54
+ }
55
+ export async function whereItLeads(env, at, family = pathFamilyOf({ root: at }), signal) {
56
+ try {
57
+ const key = await canonicalizeTarget(env, at, signal, family);
58
+ return key.ok ? normalizeAbsPathLexically(key.key) : undefined;
59
+ }
60
+ catch {
61
+ return undefined;
62
+ }
63
+ }
64
+ export async function whereItsAnsweredPrefixLeads(env, at, family = pathFamilyOf({ root: at }), signal) {
65
+ const walk = async () => {
66
+ const rest = [];
67
+ for (let here = normalizeAbsPathLexically(at);;) {
68
+ const led = await whereItLeads(env, here, family, signal);
69
+ if (led !== undefined)
70
+ return rest.reduce((p, name) => joinForFamily(family, p, name), led);
71
+ const parent = normalizeAbsPathLexically(`${here}/..`);
72
+ if (parent === here)
73
+ return undefined;
74
+ rest.unshift(here.slice(parent.length).replace(/^[\\/]+/, ""));
75
+ here = parent;
76
+ }
77
+ };
78
+ return await withGateTimeout(walk(), DEFAULT_GATE_AWAIT_TIMEOUT_MS, signal, "ruleRowHead").catch(() => undefined);
79
+ }
80
+ export async function placeReadDenyAtHome(env, compile, homes, signal) {
81
+ let at = homes;
82
+ if (at === undefined) {
83
+ const answer = await withGateTimeout(readDenyHomesAnswered(env, signal), DEFAULT_GATE_AWAIT_TIMEOUT_MS, signal, "readDenyHomes");
84
+ if (!answer.whole)
85
+ return { matcher: compile([]), homes: [], linked: new Map(), whole: false };
86
+ at = answer.homes;
87
+ }
88
+ const atHomes = compile(at);
89
+ const { linked, whole } = await readDenyHomeLinksOf(env, atHomes.entries, at, signal);
90
+ return { matcher: linked.size === 0 ? atHomes : compile(at, linked), homes: at, linked, whole };
91
+ }
92
+ export function readDenyPlacedLater(unplaced, place) {
93
+ let current = unplaced;
94
+ let whole = false;
95
+ let pending;
96
+ const again = () => (pending ??= place()
97
+ .then((placed) => {
98
+ current = placed.matcher;
99
+ whole = placed.whole;
100
+ }, () => undefined)
101
+ .finally(() => {
102
+ pending = undefined;
103
+ }));
104
+ void again();
105
+ const ready = async () => {
106
+ if (pending !== undefined)
107
+ await pending;
108
+ if (!whole)
109
+ await again();
110
+ };
111
+ const matcher = {
112
+ get entries() {
113
+ return current.entries;
114
+ },
115
+ get ruleRows() {
116
+ return current.ruleRows;
117
+ },
118
+ matchPath: (path, opts) => current.matchPath(path, opts),
119
+ matchTarget: (canonicalKey, lexicalView, opts) => current.matchTarget(canonicalKey, lexicalView, opts),
120
+ get rgExclusionGlobs() {
121
+ return current.rgExclusionGlobs;
122
+ },
123
+ get rgProbeGlobs() {
124
+ return current.rgProbeGlobs;
125
+ },
126
+ unexcludableUnder: (scope) => current.unexcludableUnder?.(scope) ?? false,
127
+ rowAtOrBelow: (path) => current.rowAtOrBelow?.(path) ?? null,
128
+ };
129
+ return { matcher, ready };
130
+ }
131
+ const HOME_CHAINS = new WeakMap();
132
+ function homeChainOf(env) {
133
+ const identity = env.fileIdentity;
134
+ const home = declaredReadDenyHomes(env)[0];
135
+ if (typeof identity !== "function" || home === undefined)
136
+ return undefined;
137
+ let perEnv = HOME_CHAINS.get(env);
138
+ if (perEnv === undefined)
139
+ HOME_CHAINS.set(env, (perEnv = new Map()));
140
+ const known = perEnv;
141
+ let chain = known.get(home);
142
+ if (chain === undefined) {
143
+ const reading = withGateTimeout((async () => {
144
+ const canonical = await env.canonicalPath(home);
145
+ if (!canonical.ok)
146
+ return { index: undefined, whole: false };
147
+ const out = new Map();
148
+ let whole = true;
149
+ for (let dir = normalizeAbsPathLexically(canonical.value);;) {
150
+ const id = await identityOf(env, identity, dir);
151
+ if (id?.ok === true) {
152
+ if (!out.has(id.value))
153
+ out.set(id.value, dir);
154
+ }
155
+ else
156
+ whole = false;
157
+ const parent = normalizeAbsPathLexically(`${dir}/..`);
158
+ if (parent === dir)
159
+ break;
160
+ dir = parent;
161
+ }
162
+ return { index: out.size > 0 ? out : undefined, whole };
163
+ })(), DEFAULT_GATE_AWAIT_TIMEOUT_MS, undefined, "homeChain").catch(() => ({ index: undefined, whole: false }));
164
+ chain = reading;
165
+ known.set(home, reading);
166
+ const drop = () => {
167
+ if (known.get(home) === reading)
168
+ known.delete(home);
169
+ };
170
+ void reading.then((c) => (c.whole ? undefined : drop()));
171
+ }
172
+ return chain.then((c) => c.index);
173
+ }
174
+ async function identityOf(env, identity, path, signal) {
175
+ try {
176
+ return await identity.call(env, path, signal);
177
+ }
178
+ catch {
179
+ return undefined;
180
+ }
181
+ }
182
+ export function homeViewsOf(env, signal) {
183
+ const chain = homeChainOf(env);
184
+ const identity = env.fileIdentity;
185
+ const views = new Map();
186
+ const viewOf = (path, leafIsFile) => {
187
+ const key = `${leafIsFile ? "f" : "d"}:${path}`;
188
+ let v = views.get(key);
189
+ if (v === undefined) {
190
+ v = (async () => {
191
+ const index = await chain;
192
+ if (index === undefined || typeof identity !== "function")
193
+ return undefined;
194
+ if (!leafIsFile) {
195
+ const id = await identityOf(env, identity, path, signal);
196
+ if (id === undefined)
197
+ return undefined;
198
+ if (id.ok) {
199
+ const at = index.get(id.value);
200
+ if (at !== undefined)
201
+ return at;
202
+ }
203
+ else if (id.error.code !== "not_found" && id.error.code !== "not_directory")
204
+ return undefined;
205
+ }
206
+ const parent = normalizeAbsPathLexically(`${path}/..`);
207
+ if (parent === path)
208
+ return undefined;
209
+ const up = await viewOf(parent, false);
210
+ return up === undefined ? undefined : joinForFamily(pathFamilyOf({ root: path }), up, path.slice(parent.length).replace(/^[\\/]+/, ""));
211
+ })();
212
+ views.set(key, v);
213
+ }
214
+ return v;
215
+ };
216
+ return {
217
+ async of(path, opts) {
218
+ if (chain === undefined || !isAbsolutePathForm(path))
219
+ return undefined;
220
+ const spelled = normalizeAbsPathLexically(path);
221
+ const view = await viewOf(spelled, opts?.leaf === "file");
222
+ return view === undefined || view === spelled ? undefined : view;
223
+ },
224
+ };
225
+ }
@@ -1,7 +1,11 @@
1
1
  /**
2
2
  * One deny entry: a `/`-separated run of path SEGMENTS. `*` matches any run of non-separator
3
3
  * characters within one segment and is the ONLY metacharacter (everything else is literal). The run
4
- * may match ANYWHERE in the judged path (`.ssh` covers `~/.ssh/…` and any other `.ssh` directory).
4
+ * may match ANYWHERE in the judged path (`.ssh` covers `~/.ssh/…` and any other `.ssh` directory) —
5
+ * unless the pattern opens with the HOME word ({@link READ_DENY_HOME_WORD}): `~/<run>` matches only where
6
+ * the run starts right below the execution environment's home directory (`~/.sema/settings.json` is the
7
+ * home's file, never a project's `.sema/settings.json`). Only a FIRST segment that is exactly `~` is the
8
+ * word; `~user` and a `~` further along are literal segments.
5
9
  * String shorthand ≡ `{ pattern }`. Matching is ASCII-case-insensitive by default (a case-insensitive
6
10
  * filesystem serves `.SSH` and `.ssh` as the same directory); `caseSensitive: true` opts a single
7
11
  * entry out. Unicode normalization (NFC/NFD) and volume-level case semantics are NOT modeled (v1
@@ -12,6 +16,20 @@ export type ReadDenyEntry = string | {
12
16
  pattern: string;
13
17
  caseSensitive?: boolean;
14
18
  };
19
+ /** The HOME word of the pattern grammar ({@link ReadDenyEntry}): a FIRST segment spelled exactly this anchors the run
20
+ * right below the execution environment's home directory. */
21
+ export declare const READ_DENY_HOME_WORD = "~";
22
+ /** A pattern read as its anchor and its run: `~/a/b` ⇒ the run `a/b`, home-anchored; any other pattern ⇒ itself,
23
+ * unanchored. The one reader of the home word — the compiler, the traversal globs, the classifier's rendering and the
24
+ * write face's mirror all take the run from here. */
25
+ export declare function readDenyPatternRun(pattern: string): {
26
+ readonly homeAnchored: boolean;
27
+ readonly run: string;
28
+ };
29
+ /** How many leading segments of a run are LITERAL — the run's head before the first segment carrying `*`. A home-anchored
30
+ * row's head names one location below the home, which a mount resolves to where it really leads
31
+ * ({@link compileReadDeny}'s `linked`); the segments after it are matched below that location as the run matches. */
32
+ export declare function readDenyRunHead(run: string): number;
15
33
  /** The normalized (validated, defaulted) form of one entry. */
16
34
  export interface NormalizedReadDenyEntry {
17
35
  pattern: string;
@@ -26,6 +44,66 @@ export interface ReadDenyHit {
26
44
  * sends the reader chasing a path the pattern does not match. */
27
45
  matchedView?: string;
28
46
  }
47
+ /** The two faces of the deny table: what a READ must not reach, and what a WRITE must not reach. */
48
+ export declare const DENY_FACES: readonly ["read", "write"];
49
+ export type DenyFace = (typeof DENY_FACES)[number];
50
+ /**
51
+ * A deny row in the rule DSL's PATH grammar — a `Read(<pattern>)` / `Edit(<pattern>)` deny rule, compiled by the
52
+ * rule compiler against its own bases (`createPermissionRulePolicy`). It joins the SAME table as the segment rows: one
53
+ * judge (`matchPath` / `matchTarget`), one refusal sentence, one classifier rendering — only the row's reach is the
54
+ * rule grammar's (the path it names and everything under a directory it names), not a segment run.
55
+ * · `rule` — the rule as written, the row's name wherever a refusal or the classifier names it;
56
+ * · `tool` / `pattern` — the rule's two parts, for a rendering that states the pattern inertly;
57
+ * · `faces` — which faces the row guards (`Read` ⇒ read and write: a path whose reads are denied is not written
58
+ * either; `Edit` ⇒ write);
59
+ * · `reaches` — the rule compiler's own reach over an ABSOLUTE path.
60
+ */
61
+ export interface PathRuleDenyRow {
62
+ readonly rule: string;
63
+ readonly tool: string;
64
+ readonly pattern: string;
65
+ readonly faces: readonly DenyFace[];
66
+ readonly reaches: (absolutePath: string) => boolean;
67
+ /** The row's HEAD — the absolute location its reach starts at (the rule model's `pathRuleHeadOf`: the pattern's
68
+ * literal prefix under its base, a directory rule's directory), spelled so the execution environment can resolve it.
69
+ * A leg judges the row where that location LEADS too ({@link withRuleRowsWhereTheyLead}). Absent on a hand-built
70
+ * row, or one whose head cannot be read: it is judged as spelled. */
71
+ readonly head?: string;
72
+ }
73
+ /**
74
+ * 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
75
+ * leading part of it: the anchor's children down to the head itself) that the environment resolves to another spelling
76
+ * (a link or firmlink: `/tmp` → `/private/tmp`, `/var` → `/private/var`, a project root, a home entry or a directory
77
+ * inside the rule reached through a symlink) — the same row at that spelling: a path at or below the resolved location
78
+ * is judged as its spelling at or below that part. Every read and write face judges the CANONICAL key of a target beside
79
+ * its spelled view, so a row whose head is spelled through a link would otherwise never meet a target named by its real
80
+ * location — whether the row was written with the short spelling and the call with the long one, or the reverse. Every
81
+ * part, not the head alone: a link inside the rule's literal prefix that is re-pointed after the rows were placed still
82
+ * meets the row through the parts above it (`<real root>/alias/**` holds when `alias` moves). One rule for every row
83
+ * form (absolute, home, root, working directory, directory): the reference compiles the same "physical twin" of every
84
+ * deny / ask rule's literal prefix (cli280.js `ei` 9766583, compiled in `mt` 9767083). `leadsTo` is the leg's resolution,
85
+ * the one the faces mint their keys with (`undefined` = could not resolve: no twin there); each location is resolved
86
+ * once, and a part that only extends its parent's resolution adds no row.
87
+ */
88
+ export declare function withRuleRowsWhereTheyLead(rows: readonly PathRuleDenyRow[], leadsTo: (head: string) => Promise<string | undefined>): Promise<PathRuleDenyRow[]>;
89
+ /**
90
+ * The WRITE face of the deny table: the rule rows whose faces include `write` — the judge the shell station reads for a
91
+ * command's write targets (a redirection, `tee`, `sed -i`, `cp` / `mv` / `touch` / `mkdir` / `rm` operands). Same row
92
+ * type, same judgment (`ruleRowHit`) as the read face's rule rows; a hit names the row by its rule text.
93
+ */
94
+ export interface WriteDenyMatcher {
95
+ /** Which face this judge is — the write tools' path resolution reads it to word and code the refusal as a write. */
96
+ readonly face: "write";
97
+ readonly ruleRows: readonly PathRuleDenyRow[];
98
+ matchPath(path: string): ReadDenyHit | null;
99
+ /** The target judgment the read face makes, on this face's rows: canonical key, lexical view, the leaf chain's hops. */
100
+ matchTarget(canonicalKey: string, lexicalView?: string, opts?: {
101
+ readonly aliasResolved?: boolean;
102
+ readonly hops?: readonly string[];
103
+ }): ReadDenyHit | null;
104
+ }
105
+ /** Compile the write face over the rule rows (the rows not on it are dropped here, once). */
106
+ export declare function compileWriteDeny(rules?: readonly PathRuleDenyRow[]): WriteDenyMatcher;
29
107
  /** One compiled ripgrep glob flag for the traversal legs (`--iglob` = case-insensitive entry). */
30
108
  export interface ReadDenyRgGlob {
31
109
  flag: "--glob" | "--iglob";
@@ -40,12 +118,20 @@ export interface ReadDenyMatcher {
40
118
  /** Normalized entries (built-ins first, then additions; exact duplicates folded). The disclosure /
41
119
  * persistence face. */
42
120
  readonly entries: readonly NormalizedReadDenyEntry[];
121
+ /** The rule-DSL rows on the READ face ({@link PathRuleDenyRow}), judged after the segment rows. Not persisted:
122
+ * they are the caller policy's, which a resume re-supplies. Absent on a matcher compiled without rules. */
123
+ readonly ruleRows?: readonly PathRuleDenyRow[];
43
124
  /** Judge ONE path view (segment-window semantics). Returns the first matching entry, or null.
44
125
  * `aliasResolved` = the key's MINTER reports the real filesystem already collapsed any win32
45
126
  * component alias in it (canonicalizeTarget's realpath arm); unset ⇒ both readings are judged,
46
- * which is the fail-closed default. */
127
+ * which is the fail-closed default. `walked` = the path is a MEMBER a traversal enumerated below a
128
+ * root the command's text names, not a spelling of the command: the rule-DSL rows do not judge it (a
129
+ * `Read(<pattern>)` deny reaches what the command SPELLS — the reference implementation's best-effort
130
+ * reading of a rule against a command's arguments), while the segment rows (the built-in tiers, the
131
+ * deployment's patterns) reach every path a read can open. Unset ⇒ every row, the fail-closed default. */
47
132
  matchPath(path: string, opts?: {
48
133
  readonly aliasResolved?: boolean;
134
+ readonly walked?: boolean;
49
135
  }): ReadDenyHit | null;
50
136
  /** §3.3 target judgment: canonical view + lexical view + every hop of the leaf symlink chain (`hops`,
51
137
  * each an unresolved spelling some link wrote); ANY hit decides. `aliasResolved` applies to the
@@ -55,17 +141,32 @@ export interface ReadDenyMatcher {
55
141
  readonly hops?: readonly string[];
56
142
  }): ReadDenyHit | null;
57
143
  /** Traversal exclusions for the rg legs: `!`-polarity glob pairs — `!**\/<p>` plus `!**\/<p>/…`
58
- * (the entry itself and its descendants). */
144
+ * (the entry itself and its descendants). An entry placed at a home carries none (a glob is relative to the
145
+ * search root): `matchPath` judges it on the rg leg's output and in the walker. */
59
146
  readonly rgExclusionGlobs: readonly ReadDenyRgGlob[];
60
147
  /** Positive-polarity twins of the exclusions, for the bounded existence probe (first hit = entries
61
148
  * matching the deny list exist under the scope and were excluded). */
62
149
  readonly rgProbeGlobs: readonly ReadDenyRgGlob[];
150
+ /** Whether an entry that carries NO traversal glob (one placed at a home) can match a path under `scope` (an absolute
151
+ * directory): the rg leg cannot exclude such an entry, and a search that reads it and prunes afterwards answers
152
+ * differently by whether the entry's content matched — so a search over such a scope takes the walker, which prunes
153
+ * by path before reading. A scope that is not rooted answers true. */
154
+ unexcludableUnder?(scope: string): boolean;
155
+ /** 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
156
+ * path from its own segments ({@link rowMeetsPath}) — so moving, copying or removing the path changes what the row
157
+ * covers. A rule row's reach below a path is not enumerable: any rule row answers. */
158
+ rowAtOrBelow?(path: string): ReadDenyHit | null;
63
159
  }
64
160
  /** The DENY HALF of a shell read boundary, in the shape `BashReadonlyRootBoundary` declares it (structural:
65
161
  * this module is the leaf every face depends on). `denyMatch` judges one LEXICALLY RESOLVED operand,
66
162
  * returning the matched pattern or null; PRESENT-IFF there is something to judge. */
67
163
  export interface ReadDenySeat {
68
- denyMatch?: (resolvedPath: string) => string | null;
164
+ /** `walked` — the path is a traversal's enumerated member, not a spelling ({@link ReadDenyMatcher.matchPath}). */
165
+ denyMatch?: (resolvedPath: string, opts?: {
166
+ readonly walked?: boolean;
167
+ }) => string | null;
168
+ /** {@link ReadDenyMatcher.rowAtOrBelow} for one lexically resolved path; present exactly when `denyMatch` is. */
169
+ denyAtOrBelow?: (resolvedPath: string) => string | null;
69
170
  denyEntries: readonly string[];
70
171
  }
71
172
  /** The ONE adapter from a compiled matcher to that seat, so every face mounts it the SAME way and the "wire
@@ -86,9 +187,11 @@ export declare function readDenySeat(matcher: ReadDenyMatcher | undefined): Read
86
187
  * which is approvable and so states the finding without claiming a refusal. NOT composers, each stating
87
188
  * its own fact: the compaction lane's withheld-attachment HTML attribute, and the traversal notes'
88
189
  * withheld-COUNT lines, which name the table and a pattern LIST. */
89
- export declare function sensitivePathDenyList(pattern: string | readonly string[]): string;
190
+ export declare function sensitivePathDenyList(pattern: string | readonly string[], face?: DenyFace): string;
90
191
  /** The deny table's REACH: a deny row holds INSIDE the containment roots, so no root grant answers it. */
91
192
  export declare const SENSITIVE_PATH_DENY_SCOPE = "This list guards credential-class paths and applies regardless of the containment roots.";
193
+ /** The write face's reach, in the same place of the sentence: a write deny row holds under every root and every mode. */
194
+ export declare const WRITE_DENY_SCOPE = "A write deny row holds regardless of the containment roots and of the permission mode.";
92
195
  /**
93
196
  * THE deny refusal sentence — five segments, one mint, every read face: the ACTION, the ABSOLUTE target as the
94
197
  * caller spelled it (with its canonical target in parentheses when the two differ), the DISPOSITION (a deny row is
@@ -105,6 +208,8 @@ export declare function readDenySentence(finding: {
105
208
  readonly canonical?: string;
106
209
  readonly pattern: string;
107
210
  readonly matchedView?: string;
211
+ /** The table face that refused (default `read`): a write-face hit names the write list and its reach, and writes. */
212
+ readonly face?: DenyFace;
108
213
  }): string;
109
214
  /** The per-part budget of the deny sentence — the gate's own card-account cap, not a second number. */
110
215
  export declare const READ_DENY_SENTENCE_PART_MAX = 200;
@@ -135,15 +240,16 @@ export interface ReadDenyBuiltinRow {
135
240
  /**
136
241
  * READ_FACE_BUILTIN_DENY_TABLE — the FULL built-in table with tier metadata: the material basis of
137
242
  * the deployment admin face, what an org administrator "sees" before selecting tiers / excluding
138
- * rows. Activation is a SEPARATE fact and is OFF by default: every row here is inert until a
139
- * deployment names its tier ({@link READ_DENY_DEFAULT_TIERS} is empty), so this is the MENU, never
140
- * the active set. Rows stay curated regardless — the menu has to be worth switching on.
243
+ * rows. Activation is a SEPARATE fact: the tiers of {@link READ_DENY_DEFAULT_TIERS} are active unless the
244
+ * deployment selects otherwise, the rest are inert until a deployment names their tier — so this is the
245
+ * MENU, never the active set. Rows stay curated regardless — the menu has to be worth switching on.
141
246
  * The dotenv family is deliberately NOT here — workspace `.env` files are working material for the
142
247
  * tasks this engine runs; an `outside_workspace` tier is likewise deliberately absent.
143
248
  *
144
- * Each row states its tradeoff. Rows are matched as segment runs anywhere in the path, so a repo
145
- * fixture spelled `fixtures/.ssh/id_rsa` is refused too — deliberate: the refusal names the pattern,
146
- * and a false refusal on a fixture is the cheap direction (deny errs strict).
249
+ * Each row states its tradeoff. An unanchored row is matched as a segment run anywhere in the path, so a
250
+ * repo fixture spelled `fixtures/.ssh/id_rsa` is refused too — deliberate: the refusal names the pattern,
251
+ * and a false refusal on a fixture is the cheap direction (deny errs strict). A home-anchored row
252
+ * (`~/…`, {@link READ_DENY_HOME_WORD}) is matched only below the home directory.
147
253
  *
148
254
  * NOT listed, deliberately: `.env`/`.env.*` (ruled out — workspace material; the WRITE guard still
149
255
  * covers them; the ENGINE's own `.env` under its data root is a different file and IS listed, see the
@@ -153,27 +259,31 @@ export interface ReadDenyBuiltinRow {
153
259
  * engine's own data root as a WHOLE (transcript integrity policy owns it with an ask on the write
154
260
  * face, and the memory library the model is told to read and edit lives under it).
155
261
  *
156
- * agent-config tier — the agent's OWN configuration files whose `env` / server blocks carry API keys
157
- * in cleartext (the shell's `settings.json` env block, a project `.mcp.json` server env, the engine
158
- * host's data-root `.env`). Unguarded, a structured read of one of these lands the key verbatim in
159
- * the tool result and therefore in the session record. Rows name FILES, not the config DIRECTORIES:
160
- * `.sema`/`.claude` also hold agents, skills, hooks and (under the shell's layout,
161
- * `~/.sema/engine-data`) the engine data root with the memory library — material the model reads
162
- * and edits as ordinary work, and a directory-wide row would need a data-root passthrough (one more
163
- * rule) to keep it readable. Token
262
+ * agent-config tier — the agent's OWN user-level configuration files whose `env` / server blocks carry API
263
+ * keys in cleartext (the shell's `settings.json` env block, an MCP server env, the engine host's data-root
264
+ * `.env`). Unguarded, a structured read of one of these lands the key verbatim in the tool result and
265
+ * therefore in the session record. Every row is HOME-ANCHORED: the user's own files under the home
266
+ * directory are refused, while a PROJECT's file of the same name (`./.sema/settings.json`, `./.mcp.json`,
267
+ * `./.claude/settings.json`) is working material the person edits with the model and reads normally — its
268
+ * writes are the write faces' business. Rows name FILES, not the config DIRECTORIES: `.sema`/`.claude` also
269
+ * hold agents, skills, hooks and (under the shell's layout, `~/.sema/engine-data`) the engine data root
270
+ * with the memory library — material the model reads and edits as ordinary work, and a directory-wide row
271
+ * would need a data-root passthrough (one more rule) to keep it readable. Token
164
272
  * STORES of the same tooling (`.credentials.json`, an agent CLI's `auth.json`, …) are credentials rows, not
165
273
  * agent-config rows: a deployment that switches this tier off to debug its agent configuration must
166
- * not also expose the OAuth stores beside it. These rows are ALSO the opt-in write deny set's
274
+ * not also expose the OAuth stores beside it. The rows' RUNS are ALSO the opt-in write deny set's
167
275
  * agent-configuration block ({@link import("../../core/sensitive-path-policy.js").agentConfigDenyPatterns},
168
276
  * which reads them from here; contract sentence there), so a row added here is judged by both faces.
169
277
  */
170
278
  export declare const READ_FACE_BUILTIN_DENY_TABLE: readonly ReadDenyBuiltinRow[];
171
279
  /**
172
- * The DEFAULT tier selection: EMPTY — an unconfigured deployment activates NO built-in row, so the
173
- * table above judges nothing until a deployment selects tiers. Activation is a deployment decision
174
- * with a visible seat, and the leg's wiring manifest reports which tiers it got.
280
+ * The DEFAULT tier selection — what an unconfigured deployment activates: the `credentials` tier (key
281
+ * material, cloud and registry credential files, token stores) and the `agent-config` tier (the agent's own
282
+ * user-level configuration files, home-anchored). The `shell-history`, `browser` and `wallet` tiers are a
283
+ * deployment's opt-in. Activation is a deployment decision with a visible seat, and the leg's wiring
284
+ * manifest reports which tiers it got.
175
285
  *
176
- * Still the NAMED default rather than nothing: `[]` and absence stay separately spellable on the
286
+ * The NAMED default rather than an inline literal: `[]` and absence stay separately spellable on the
177
287
  * seat, and a deployment profile wanting a different default changes THIS expression, not call sites.
178
288
  */
179
289
  export declare const READ_DENY_DEFAULT_TIERS: readonly ReadDenyBuiltinTier[];
@@ -182,8 +292,7 @@ export declare const READ_DENY_DEFAULT_TIERS: readonly ReadDenyBuiltinTier[];
182
292
  * HandsToolkitOptions; deliberately NOT a TaskSpec key and NOT in the governed workflow whitelist,
183
293
  * so no task or governed script can widen below its deployment).
184
294
  * - `tiers` — EXACTLY-these tier selection: listed tiers are active, unlisted ones are not
185
- * (`[]` = no built-in tiers at all — an explicit, legal posture; absent = {@link READ_DENY_DEFAULT_TIERS},
186
- * which is itself empty, so absent and `[]` resolve alike today and stay separately spellable).
295
+ * (`[]` = no built-in tiers at all — an explicit, legal posture; absent = {@link READ_DENY_DEFAULT_TIERS}).
187
296
  * - `exclude` — per-row removal by canonical pattern name, applied AFTER tier selection.
188
297
  * Unknown tier names / unknown row names refuse loudly — never silently ignored.
189
298
  */
@@ -198,6 +307,17 @@ export interface ReadDenyBuiltinConfig {
198
307
  * table under a candidate configuration with the engine's own rules.
199
308
  */
200
309
  export declare function resolveReadDenyBuiltins(config?: ReadDenyBuiltinConfig): readonly ReadDenyBuiltinRow[];
310
+ /**
311
+ * The deployment's built-in configuration as ONE validated snapshot, read off the seats' OWN values: each seat is an
312
+ * array of names or absent — any other value (a string, the empty string included) refuses loudly, naming the seat,
313
+ * before anything is spread or copied (a spread string reads as its characters, or as no names at all). The names are
314
+ * then judged by {@link resolveReadDenyBuiltins}. The snapshot holds copies: a caller mutating its arrays afterwards
315
+ * moves nothing, so every compile of one mount reads the same selection.
316
+ */
317
+ export declare function readDenyBuiltinConfigOf(seats: {
318
+ readonly tiers?: unknown;
319
+ readonly exclude?: unknown;
320
+ }): ReadDenyBuiltinConfig;
201
321
  /**
202
322
  * The tiers a deployment configuration ACTIVATES — the tier names of {@link resolveReadDenyBuiltins}'s
203
323
  * surviving rows, in {@link READ_DENY_BUILTIN_TIERS} order (the closed set is the ordering source, so
@@ -210,10 +330,9 @@ export declare function resolveReadDenyBuiltins(config?: ReadDenyBuiltinConfig):
210
330
  export declare function activeReadDenyBuiltinTiers(config?: ReadDenyBuiltinConfig): readonly ReadDenyBuiltinTier[];
211
331
  /**
212
332
  * READ_FACE_DEFAULT_DENY_ENTRIES — the DEFAULT-ACTIVE built-in set: {@link READ_FACE_BUILTIN_DENY_TABLE}
213
- * under {@link READ_DENY_DEFAULT_TIERS}. That selection is EMPTY, so this is an EMPTY list — the
214
- * honest answer to what it is asked ("what does an unconfigured deployment refuse": nothing, from
215
- * this table). Kept, and kept derived, so a consumer rendering that answer keeps reading it; the
216
- * tier-annotated table is where a consumer renders "what COULD be switched on".
333
+ * under {@link READ_DENY_DEFAULT_TIERS} — the answer to "what does an unconfigured deployment refuse, from
334
+ * this table". Derived, so it follows the default selection; the tier-annotated table is where a consumer
335
+ * renders "what COULD be switched on".
217
336
  */
218
337
  export declare const READ_FACE_DEFAULT_DENY_ENTRIES: readonly ReadDenyEntry[];
219
338
  /** A compiled segment-run pattern (shared engine — the write guard delegates here too). */
@@ -222,6 +341,12 @@ export interface CompiledSegmentPattern {
222
341
  segments: RegExp[];
223
342
  /** Fold BOTH sides through {@link asciiLower} before testing (compile lowered the pattern side). */
224
343
  foldAscii: boolean;
344
+ /** The segment lists of the directories the run is ANCHORED at (folded as the pattern folds): the run must start
345
+ * right after one of them. Absent = the run matches anywhere. */
346
+ anchors?: readonly (readonly string[])[];
347
+ /** Judged in a ROOTED view only: an extra location of a home-anchored row (where its literal head leads). A view that
348
+ * is not rooted cannot be placed and is covered by the row's own run, held anywhere. */
349
+ rootedOnly?: true;
225
350
  }
226
351
  /**
227
352
  * Compile one segment-glob pattern (shared engine). `fold`:
@@ -235,9 +360,10 @@ export interface CompiledSegmentPattern {
235
360
  export declare function compileSegmentPattern(raw: string, fold: "none" | "ascii" | "unicode"): CompiledSegmentPattern | null;
236
361
  /**
237
362
  * Does the path contain a compiled pattern as a CONTIGUOUS run of full path segments? (Shared engine:
238
- * `.git/hooks` matches `/repo/.git/hooks/pre-commit`; `.ssh` matches any `.ssh` segment.) Splits on
239
- * BOTH separator families — win32 canonical keys are backslash-form. Returns the matched raw pattern
240
- * or null.
363
+ * `.git/hooks` matches `/repo/.git/hooks/pre-commit`; `.ssh` matches any `.ssh` segment.) An ANCHORED pattern
364
+ * ({@link CompiledSegmentPattern.anchors}) matches only where the path's leading segments are one anchor's and
365
+ * the run starts right after them. Splits on BOTH separator families — win32 canonical keys are backslash-form.
366
+ * Returns the matched raw pattern or null.
241
367
  */
242
368
  export declare function matchSegmentPatterns(path: string, compiled: readonly CompiledSegmentPattern[], opts?: {
243
369
  readonly aliasResolved?: boolean;
@@ -251,6 +377,10 @@ export declare function matchSegmentPatterns(path: string, compiled: readonly Co
251
377
  * refusal reason, or null when the entry is valid.
252
378
  */
253
379
  export declare function persistedReadDenyEntryProblem(e: unknown): string | null;
380
+ /** The home spellings a home-anchored row is placed under, judged as given: an array of absolute path strings, loud
381
+ * otherwise and naming `readDenyHomes` — a relative or empty spelling places the row where no home is, and a bare
382
+ * string would be read as its characters. */
383
+ export declare function checkedReadDenyHomes(homes: unknown, seat?: string): readonly string[];
254
384
  /**
255
385
  * Compile the read-face deny judge: ACTIVE built-in rows ∪ additions. Additions are add-only at every
256
386
  * task layer (`[]` additions ≡ absent, deliberately a no-op rather than a refusal: an array API
@@ -260,5 +390,17 @@ export declare function persistedReadDenyEntryProblem(e: unknown): string | null
260
390
  * re-add single patterns through the additions seats — they then ride as ordinary additions.
261
391
  * Bad entry shapes / zero-segment patterns / unknown tier or row names throw loudly, naming the
262
392
  * layer or knob.
393
+ *
394
+ * `homes` — the spellings of the execution environment's home directory a home-anchored entry
395
+ * ({@link READ_DENY_HOME_WORD}) is placed under: the declared home and, where the environment's canonical
396
+ * spelling differs, that one too (the faces judge canonical keys beside the spelled views). Absolute
397
+ * spellings only — any other shape refuses loudly, naming `readDenyHomes`. EMPTY = the environment declares no
398
+ * home: a home-anchored entry then matches its run anywhere — a row that cannot be placed holds wider, never narrower.
399
+ *
400
+ * `linked` — where a home-anchored row's LITERAL HEAD ({@link readDenyRunHead}) really leads, keyed by the row's
401
+ * pattern: the canonical locations a mount resolved (`readDenyHomeLinksOf`, read-deny-home.ts) where one of the
402
+ * head's segments is a link (`~/.sema` linked into a dotfiles directory). The row is placed at each location too —
403
+ * the same row, the same name in every refusal — with the rest of its run matched below it. The read faces judge
404
+ * canonical keys, so every spelling that reaches the linked file meets the row there. Absolute locations only.
263
405
  */
264
- export declare function compileReadDeny(additions?: readonly ReadDenyEntry[], layer?: string, builtin?: ReadDenyBuiltinConfig): ReadDenyMatcher;
406
+ export declare function compileReadDeny(additions?: readonly ReadDenyEntry[], layer?: string, builtin?: ReadDenyBuiltinConfig, rules?: readonly PathRuleDenyRow[], homes?: readonly string[], linked?: ReadonlyMap<string, readonly string[]>): ReadDenyMatcher;