zelari-code 2.11.2 → 2.13.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 (33) hide show
  1. package/dist/cli/headless.js +16 -5
  2. package/dist/cli/headless.js.map +1 -1
  3. package/dist/cli/hooks/useChatTurn.js +17 -4
  4. package/dist/cli/hooks/useChatTurn.js.map +1 -1
  5. package/dist/cli/kraken/completionProof.js +176 -0
  6. package/dist/cli/kraken/completionProof.js.map +1 -0
  7. package/dist/cli/kraken/nativeVerification.js +12 -5
  8. package/dist/cli/kraken/nativeVerification.js.map +1 -1
  9. package/dist/cli/kraken/verificationBridge.js +5 -1
  10. package/dist/cli/kraken/verificationBridge.js.map +1 -1
  11. package/dist/cli/kraken/verifierLifecycle.js +72 -8
  12. package/dist/cli/kraken/verifierLifecycle.js.map +1 -1
  13. package/dist/cli/main.bundled.js +1141 -433
  14. package/dist/cli/main.bundled.js.map +4 -4
  15. package/dist/cli/orchestration/policy.js +88 -0
  16. package/dist/cli/orchestration/policy.js.map +1 -0
  17. package/dist/cli/runHeadless.js +49 -0
  18. package/dist/cli/runHeadless.js.map +1 -1
  19. package/dist/cli/safety/policyEngine.js +315 -0
  20. package/dist/cli/safety/policyEngine.js.map +1 -0
  21. package/dist/cli/safety/policyLayers.js +55 -0
  22. package/dist/cli/safety/policyLayers.js.map +1 -0
  23. package/dist/cli/safety/toolPermissions.js +38 -0
  24. package/dist/cli/safety/toolPermissions.js.map +1 -1
  25. package/dist/cli/toolRegistry.js +49 -9
  26. package/dist/cli/toolRegistry.js.map +1 -1
  27. package/dist/cli/tools/krakenCsvFanout.js +4 -4
  28. package/dist/cli/tools/krakenCsvFanout.js.map +1 -1
  29. package/dist/cli/tools/krakenModel.js +75 -0
  30. package/dist/cli/tools/krakenModel.js.map +1 -1
  31. package/dist/cli/tools/taskTool.js +4 -3
  32. package/dist/cli/tools/taskTool.js.map +1 -1
  33. package/package.json +2 -2
@@ -0,0 +1,315 @@
1
+ /**
2
+ * Policy engine v2 (P0.A) — per-command and per-path permission rules per
3
+ * agent, layered on top of the per-CATEGORY policy in toolPermissions.ts.
4
+ *
5
+ * Rules come from two USER-authored config files (same trust level as
6
+ * AGENTS.MD — deliberately NO trust subsystem here; the GLOBAL file is the
7
+ * user's personal floor, which the project file can NO LONGER relax):
8
+ *
9
+ * <root>/.zelari/policy.json — project rules
10
+ * ~/.zelari/policy.json — global rules
11
+ *
12
+ * Shape:
13
+ * {
14
+ * version?: 1,
15
+ * agents: {
16
+ * lead? : { shell?: Rule[], edit?: Rule[] },
17
+ * explore? : { shell?: Rule[], edit?: Rule[] },
18
+ * general? : { shell?: Rule[], edit?: Rule[] },
19
+ * verify? : { shell?: Rule[], edit?: Rule[] }
20
+ * }
21
+ * }
22
+ * Rule = { match: string, effect: 'allow' | 'ask' | 'deny', reason?: string }
23
+ *
24
+ * `match` is a GLOB-style prefix pattern: `git push*` matches any command
25
+ * starting with "git push"; `src/**` matches any path under src/; `*` alone
26
+ * matches everything. Within one LAYER's ordered rule list the FIRST match
27
+ * wins (P0.A): the two layers stay SEPARATE and combine RESTRICT-ONLY — every
28
+ * matching rule intersects with the category decision, most-restrictive-wins
29
+ * (deny > ask > allow), so a global deny can never be masked by the project.
30
+ * Escape hatch: ZELARI_POLICY_PRECEDENCE=legacy restores the v1 behavior
31
+ * (project rules concatenated before global ones, first-match overrides);
32
+ * the active mode is exposed as `PolicySet.precedence`.
33
+ *
34
+ * Fail-closed conventions (mirrors toolPermissions.ts): a missing file is
35
+ * silently empty; a broken file NEVER throws — the parse error is collected
36
+ * in `warnings` and the offending file/section is ignored. Opt out entirely
37
+ * with ZELARI_POLICY=0 (always the empty set).
38
+ *
39
+ * Hand-rolled validators on purpose: no zod, no new deps.
40
+ *
41
+ * @since v2.12.0 · restrict-only layers v2.13.0
42
+ */
43
+ import { readFileSync } from 'node:fs';
44
+ import { homedir } from 'node:os';
45
+ import path from 'node:path';
46
+ /** The authoritative agent keys ('lead' = the main registry; the three
47
+ * sub-agent kinds mirror taskAgentToProfile in toolRegistry.ts). */
48
+ export const POLICY_AGENTS = ['lead', 'explore', 'general', 'verify'];
49
+ const KNOWN_AGENTS = new Set(POLICY_AGENTS);
50
+ export const EMPTY_POLICY_RULE_SET = { shell: [], edit: [] };
51
+ export const EMPTY_POLICY_LAYERS = {
52
+ global: EMPTY_POLICY_RULE_SET,
53
+ project: EMPTY_POLICY_RULE_SET,
54
+ };
55
+ export function emptyPolicySet() {
56
+ return { agents: new Map(), warnings: [], precedence: policyPrecedenceFromEnv() };
57
+ }
58
+ /** Raw layers for one agent; unknown/unlisted agents get the empty layers. */
59
+ export function agentLayersFor(set, agent) {
60
+ return set.agents.get(agent) ?? EMPTY_POLICY_LAYERS;
61
+ }
62
+ /**
63
+ * COMPAT view (v1 shape): project rules then global rules in ONE list —
64
+ * first-match over this concatenation IS legacy precedence. Restrict-only
65
+ * consumers must use agentLayersFor + matchAgentPolicyRuleLayered instead.
66
+ */
67
+ export function agentRulesFor(set, agent) {
68
+ const l = set.agents.get(agent);
69
+ if (!l)
70
+ return EMPTY_POLICY_RULE_SET;
71
+ return {
72
+ shell: [...l.project.shell, ...l.global.shell],
73
+ edit: [...l.project.edit, ...l.global.edit],
74
+ };
75
+ }
76
+ // ── Glob matching ──────────────────────────────────────────────────────────
77
+ /**
78
+ * Compile a GLOB-style pattern into an ANCHORED RegExp (full match).
79
+ *
80
+ * Wildcard semantics: `*` and `**` are both "any characters, including path
81
+ * separators" — these are prefix patterns, not full minimatch; `src/**`
82
+ * means "anything under src/", `git push*` means "starts with git push",
83
+ * and a bare `*` is the catch-all. Consecutive stars collapse to one `.*`.
84
+ * Every other character is regex-escaped (`data.json` matches literally).
85
+ */
86
+ export function globToRegExp(pattern) {
87
+ let src = '^';
88
+ for (let i = 0; i < pattern.length; i++) {
89
+ const ch = pattern[i];
90
+ if (ch === '*') {
91
+ while (pattern[i + 1] === '*')
92
+ i++;
93
+ src += '.*';
94
+ }
95
+ else if ('\\^$.|?+()[]{}'.includes(ch)) {
96
+ src += '\\' + ch;
97
+ }
98
+ else {
99
+ src += ch;
100
+ }
101
+ }
102
+ return new RegExp(src + '$');
103
+ }
104
+ /** Backslashes (Windows paths) normalize to `/` so `src/**` matches `src\x.ts`. */
105
+ function normalizeForMatch(value) {
106
+ return value.replace(/\\/g, '/');
107
+ }
108
+ /**
109
+ * First match wins: return the FIRST rule whose pattern matches `value`,
110
+ * or null when nothing matches (caller keeps its category decision).
111
+ */
112
+ export function resolvePolicyRule(rules, value) {
113
+ const v = normalizeForMatch(value);
114
+ for (const rule of rules) {
115
+ if (globToRegExp(normalizeForMatch(rule.match)).test(v))
116
+ return rule;
117
+ }
118
+ return null;
119
+ }
120
+ // ── Merge with the category decision ──────────────────────────────────────
121
+ /** Shared lattice with policyLayers.ts intersectEffects: deny > ask > allow. */
122
+ export const EFFECT_RANK = { allow: 0, ask: 1, deny: 2 };
123
+ /**
124
+ * Merge a matched rule's effect into the category-level action under the
125
+ * same lattice as intersectPermissionPolicy: deny > ask > allow. A rule can
126
+ * only ADD restriction — an `allow` rule never un-asks or un-denies a
127
+ * category restriction (no privilege escalation via policy.json).
128
+ */
129
+ export function mergeRuleEffect(base, rule) {
130
+ if (!rule)
131
+ return base;
132
+ return EFFECT_RANK[rule.effect] > EFFECT_RANK[base] ? rule.effect : base;
133
+ }
134
+ /**
135
+ * Path candidates to test against edit rules: the argument as passed, plus
136
+ * the root-relative form when the arg lives under `root` (so `src/**` works
137
+ * whether the model passed `src/x.ts` or `E:\repo\src\x.ts`). Root-relative
138
+ * first — it is what users write in policy.json.
139
+ */
140
+ function pathCandidates(value, root) {
141
+ const norm = normalizeForMatch(value);
142
+ if (!root)
143
+ return [norm];
144
+ const prefix = normalizeForMatch(root).replace(/\/+$/, '') + '/';
145
+ const stripped = norm.toLowerCase().startsWith(prefix.toLowerCase())
146
+ ? norm.slice(prefix.length)
147
+ : null;
148
+ return stripped !== null ? [stripped, norm] : [norm];
149
+ }
150
+ /**
151
+ * Find the agent rule that applies to one tool invocation:
152
+ * - execute-category tools (bash, inspect_command): shell rules vs the
153
+ * `command` string argument;
154
+ * - write-category tools (write_file / edit_file / apply_diff): edit rules
155
+ * vs the primary path argument (`path`, falling back to `file_path`),
156
+ * tried root-relative first (see pathCandidates).
157
+ * Tools without a usable argument (or no rules) match nothing → null.
158
+ */
159
+ export function matchAgentPolicyRule(rules, required, args, root) {
160
+ if (!rules)
161
+ return null;
162
+ const a = (args !== null && typeof args === 'object' ? args : {});
163
+ if (required.includes('execute')) {
164
+ const cmd = a['command'];
165
+ if (typeof cmd === 'string' && cmd !== '') {
166
+ const hit = resolvePolicyRule(rules.shell, cmd);
167
+ if (hit)
168
+ return hit;
169
+ }
170
+ }
171
+ if (required.includes('write')) {
172
+ const p = typeof a['path'] === 'string' ? a['path'] : typeof a['file_path'] === 'string' ? a['file_path'] : '';
173
+ if (p !== '') {
174
+ for (const candidate of pathCandidates(p, root)) {
175
+ const hit = resolvePolicyRule(rules.edit, candidate);
176
+ if (hit)
177
+ return hit;
178
+ }
179
+ }
180
+ }
181
+ return null;
182
+ }
183
+ // ── Loading & validation (fail-closed on files, never throws) ─────────────
184
+ /** Env opt-out: ZELARI_POLICY=0 (also false/no/off) → always the empty set. */
185
+ export function isPolicyEngineDisabled() {
186
+ const v = process.env.ZELARI_POLICY?.trim().toLowerCase();
187
+ return v === '0' || v === 'false' || v === 'no' || v === 'off';
188
+ }
189
+ /**
190
+ * P0.A escape hatch: ZELARI_POLICY_PRECEDENCE=legacy selects the v1
191
+ * project-overrides-global first-match behavior; any other value (unset
192
+ * included) selects the default restrict-only layering. Read at policy-load
193
+ * time and exposed as PolicySet.precedence so callers/tests can assert the
194
+ * active mode.
195
+ */
196
+ export function policyPrecedenceFromEnv() {
197
+ const v = process.env.ZELARI_POLICY_PRECEDENCE?.trim().toLowerCase();
198
+ return v === 'legacy' ? 'legacy' : 'restrict-only';
199
+ }
200
+ function isPlainObject(v) {
201
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
202
+ }
203
+ function parseRuleList(raw, origin, warnings) {
204
+ if (raw === undefined)
205
+ return [];
206
+ if (!Array.isArray(raw)) {
207
+ warnings.push(`${origin}: expected an array of rules, got ${typeof raw} — section ignored.`);
208
+ return [];
209
+ }
210
+ const out = [];
211
+ raw.forEach((item, i) => {
212
+ const where = `${origin}[${i}]`;
213
+ if (!isPlainObject(item)) {
214
+ warnings.push(`${where}: rule is not an object — skipped.`);
215
+ return;
216
+ }
217
+ const match = item['match'];
218
+ const effect = item['effect'];
219
+ const reason = item['reason'];
220
+ if (typeof match !== 'string' || match.trim() === '') {
221
+ warnings.push(`${where}: missing or empty "match" — skipped.`);
222
+ return;
223
+ }
224
+ if (effect !== 'allow' && effect !== 'ask' && effect !== 'deny') {
225
+ warnings.push(`${where} ("${match}"): "effect" must be allow|ask|deny — skipped.`);
226
+ return;
227
+ }
228
+ out.push(typeof reason === 'string' && reason.trim() !== '' ? { match, effect, reason } : { match, effect });
229
+ });
230
+ return out;
231
+ }
232
+ /** Returns null when the WHOLE file must be ignored (bad shape / version). */
233
+ function parsePolicyFile(raw, origin, warnings) {
234
+ if (!isPlainObject(raw)) {
235
+ warnings.push(`${origin}: policy file is not a JSON object — file ignored.`);
236
+ return null;
237
+ }
238
+ const version = raw['version'];
239
+ if (version !== undefined && version !== 1) {
240
+ warnings.push(`${origin}: unsupported "version" ${JSON.stringify(version)} (expected 1) — file ignored.`);
241
+ return null;
242
+ }
243
+ const agentsRaw = raw['agents'];
244
+ if (agentsRaw === undefined) {
245
+ warnings.push(`${origin}: no "agents" key — file ignored.`);
246
+ return null;
247
+ }
248
+ if (!isPlainObject(agentsRaw)) {
249
+ warnings.push(`${origin}: "agents" is not an object — file ignored.`);
250
+ return null;
251
+ }
252
+ const out = new Map();
253
+ for (const [key, val] of Object.entries(agentsRaw)) {
254
+ const agent = key.trim().toLowerCase();
255
+ if (!KNOWN_AGENTS.has(agent)) {
256
+ warnings.push(`${origin}: unknown agent "${key}" (known: ${POLICY_AGENTS.join(' | ')}) — ignored.`);
257
+ continue;
258
+ }
259
+ if (!isPlainObject(val)) {
260
+ warnings.push(`${origin}: agents.${key} is not an object — ignored.`);
261
+ continue;
262
+ }
263
+ const shell = parseRuleList(val['shell'], `${origin} agents.${key}.shell`, warnings);
264
+ const edit = parseRuleList(val['edit'], `${origin} agents.${key}.edit`, warnings);
265
+ out.set(agent, { shell, edit });
266
+ }
267
+ return out;
268
+ }
269
+ function readPolicyFile(file, warnings) {
270
+ let text;
271
+ try {
272
+ text = readFileSync(file, 'utf8');
273
+ }
274
+ catch {
275
+ // Missing file is the common case (most repos/users have none) — not a
276
+ // warning, just no rules from this source.
277
+ return new Map();
278
+ }
279
+ let parsed;
280
+ try {
281
+ parsed = JSON.parse(text);
282
+ }
283
+ catch (err) {
284
+ warnings.push(`${file}: invalid JSON (${err instanceof Error ? err.message : String(err)}) — file ignored.`);
285
+ return new Map();
286
+ }
287
+ return parsePolicyFile(parsed, file, warnings) ?? new Map();
288
+ }
289
+ /**
290
+ * Load the project (`<root>/.zelari/policy.json`) and global
291
+ * (`~/.zelari/policy.json`) rule sets into DISTINCT layers (P0.A): the two
292
+ * sources are NEVER concatenated at load time — evaluation intersects them
293
+ * restrict-only (matchAgentPolicyRuleLayered in policyLayers.ts) unless
294
+ * ZELARI_POLICY_PRECEDENCE=legacy restores the v1 concat view. NEVER
295
+ * throws: broken files yield warnings + whatever parsed cleanly. Pass
296
+ * `homeDir` to override the global-file location (tests).
297
+ */
298
+ export function loadPolicySet(root, opts = {}) {
299
+ if (isPolicyEngineDisabled())
300
+ return emptyPolicySet();
301
+ const warnings = [];
302
+ const precedence = policyPrecedenceFromEnv();
303
+ const project = readPolicyFile(path.join(root, '.zelari', 'policy.json'), warnings);
304
+ const global = readPolicyFile(path.join(opts.homeDir ?? homedir(), '.zelari', 'policy.json'), warnings);
305
+ const agents = new Map();
306
+ for (const [agent, p] of project) {
307
+ agents.set(agent, { project: p, global: EMPTY_POLICY_RULE_SET });
308
+ }
309
+ for (const [agent, g] of global) {
310
+ const l = agents.get(agent);
311
+ agents.set(agent, l ? { ...l, global: g } : { project: EMPTY_POLICY_RULE_SET, global: g });
312
+ }
313
+ return { agents, warnings, precedence };
314
+ }
315
+ //# sourceMappingURL=policyEngine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policyEngine.js","sourceRoot":"","sources":["../../../src/cli/safety/policyEngine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import { EFFECT_RANK, matchAgentPolicyRule, } from './policyEngine.js';
2
+ /**
3
+ * Intersect candidate effects into ONE effect: deny > ask > allow. Absent
4
+ * matches arrive as `undefined` and simply do not constrain the result; with
5
+ * NO constraint at all the slot is unconstrained (`allow`). Feed this the
6
+ * category decision plus every layer's matched effect and the result is the
7
+ * effective action — no operand can ever RELAX another.
8
+ */
9
+ export function intersectEffects(...effects) {
10
+ let best;
11
+ for (const effect of effects) {
12
+ if (effect === undefined)
13
+ continue;
14
+ if (best === undefined || EFFECT_RANK[effect] > EFFECT_RANK[best])
15
+ best = effect;
16
+ }
17
+ return best ?? 'allow';
18
+ }
19
+ /**
20
+ * Resolve THIS agent's policy contribution across BOTH layers for one tool
21
+ * invocation:
22
+ *
23
+ * - `restrict-only` (default): each layer matches independently and the
24
+ * STRICTER effect wins; equal ranks surface the project rule (it is the
25
+ * more specific intent).
26
+ * - `legacy`: v1 semantics — project rules concatenated before global ones,
27
+ * FIRST match wins (a project match masks the global rule entirely).
28
+ *
29
+ * Either way a single rule carries the effect the caller must intersect into
30
+ * the category decision (see intersectEffects and wrapWithPermissions in
31
+ * toolRegistry.ts). Tools with no usable argument match nothing in both
32
+ * layers → null, leaving the category decision untouched.
33
+ */
34
+ export function matchAgentPolicyRuleLayered(layers, precedence, required, args, root) {
35
+ if (!layers)
36
+ return null;
37
+ if (precedence === 'legacy') {
38
+ // v1: ONE concatenated list — project rules first, first-match-wins.
39
+ return matchAgentPolicyRule({
40
+ shell: [...layers.project.shell, ...layers.global.shell],
41
+ edit: [...layers.project.edit, ...layers.global.edit],
42
+ }, required, args, root);
43
+ }
44
+ // Default restrict-only: independent matches, stricter effect wins (ties
45
+ // keep the project rule as the surfaced representative).
46
+ const g = matchAgentPolicyRule(layers.global, required, args, root);
47
+ const p = matchAgentPolicyRule(layers.project, required, args, root);
48
+ if (!g)
49
+ return p;
50
+ if (!p)
51
+ return g;
52
+ const win = intersectEffects(p.effect, g.effect);
53
+ return p.effect === win ? p : g;
54
+ }
55
+ //# sourceMappingURL=policyLayers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policyLayers.js","sourceRoot":"","sources":["../../../src/cli/safety/policyLayers.ts"],"names":[],"mappings":"AAsBA,OAAO,EACL,WAAW,EACX,oBAAoB,GAIrB,MAAM,mBAAmB,CAAC;AAE3B;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAG,OAA4C;IAC9E,IAAI,IAAkC,CAAC;IACvC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,MAAM,KAAK,SAAS;YAAE,SAAS;QACnC,IAAI,IAAI,KAAK,SAAS,IAAI,WAAW,CAAC,MAAM,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC;YAAE,IAAI,GAAG,MAAM,CAAC;IACnF,CAAC;IACD,OAAO,IAAI,IAAI,OAAO,CAAC;AACzB,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,2BAA2B,CACzC,MAAwC,EACxC,UAA4B,EAC5B,QAAmC,EACnC,IAAa,EACb,IAAa;IAEb,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,IAAI,UAAU,KAAK,QAAQ,EAAE,CAAC;QAC5B,qEAAqE;QACrE,OAAO,oBAAoB,CACzB;YACE,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;YACxD,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;SACtD,EACD,QAAQ,EACR,IAAI,EACJ,IAAI,CACL,CAAC;IACJ,CAAC;IACD,yEAAyE;IACzE,yDAAyD;IACzD,MAAM,CAAC,GAAG,oBAAoB,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IACpE,MAAM,CAAC,GAAG,oBAAoB,CAAC,MAAM,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IACrE,IAAI,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IACjB,IAAI,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC;IACjB,MAAM,GAAG,GAAG,gBAAgB,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC;IACjD,OAAO,CAAC,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAClC,CAAC"}
@@ -66,6 +66,44 @@ export function defaultPermissionPolicy(overrides) {
66
66
  ...overrides,
67
67
  };
68
68
  }
69
+ /**
70
+ * Restrictiveness lattice for {@link PermissionAction}: deny > ask > allow
71
+ * (most restrictive wins).
72
+ */
73
+ const ACTION_RANK = { allow: 0, ask: 1, deny: 2 };
74
+ function moreRestrictive(a, b) {
75
+ return ACTION_RANK[a] >= ACTION_RANK[b] ? a : b;
76
+ }
77
+ /**
78
+ * Capability inheritance (P0.4): a Kraken tentacle (sub-agent spawned via
79
+ * the `task` tool) must NEVER hold more permission than its parent. The
80
+ * sub-agent's effective policy is the intersection of the parent's policy
81
+ * and the sub-agent profile's own policy, merged per category with the most
82
+ * restrictive action winning (deny > ask > allow). A category restricted in
83
+ * only ONE of the two policies keeps that restriction.
84
+ *
85
+ * `auto` (ask resolves as allow without UI) intersects as the AND of both:
86
+ * silently auto-allowing a category the parent would have had to ask about
87
+ * would itself be an escalation.
88
+ *
89
+ * If the intersection downgrades a category to `ask` while the sub-agent
90
+ * context has no interactive ask handler, resolution must FAIL CLOSED
91
+ * (deny). That behavior already lives in `wrapWithPermissions`
92
+ * (toolRegistry.ts): `ask` without `onPermissionAsk` returns a typedErr, so
93
+ * the escalation path stays closed with no extra handling here.
94
+ *
95
+ * Pure function, no I/O.
96
+ */
97
+ export function intersectPermissionPolicy(parent, child) {
98
+ return {
99
+ read: moreRestrictive(parent.read, child.read),
100
+ write: moreRestrictive(parent.write, child.write),
101
+ execute: moreRestrictive(parent.execute, child.execute),
102
+ network: moreRestrictive(parent.network, child.network),
103
+ ui: moreRestrictive(parent.ui, child.ui),
104
+ auto: parent.auto && child.auto,
105
+ };
106
+ }
69
107
  /**
70
108
  * Resolve effective action for a tool given its required permission tags.
71
109
  * Empty permissions → allow. Most restrictive action wins: deny > ask > allow.
@@ -1 +1 @@
1
- {"version":3,"file":"toolPermissions.js","sourceRoot":"","sources":["../../../src/cli/safety/toolPermissions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"toolPermissions.js","sourceRoot":"","sources":["../../../src/cli/safety/toolPermissions.ts"],"names":[],"mappings":"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"}
@@ -43,8 +43,10 @@ import { createWorldModelTools } from './workspace/worldModel.js';
43
43
  import { providerFromEnv, providerConfigFor, } from './provider/openai-compatible.js';
44
44
  import { buildProviderStream } from './provider/resolveStream.js';
45
45
  import { getKrakenVerifierOverride } from './providerConfig.js';
46
- import { defaultPermissionPolicy, resolveToolPermission, } from './safety/toolPermissions.js';
46
+ import { defaultPermissionPolicy, intersectPermissionPolicy, resolveToolPermission, } from './safety/toolPermissions.js';
47
47
  import { createDefaultLifecycleHooks } from './safety/lifecycleHooks.js';
48
+ import { agentLayersFor, loadPolicySet, mergeRuleEffect, } from './safety/policyEngine.js';
49
+ import { matchAgentPolicyRuleLayered } from './safety/policyLayers.js';
48
50
  import { withResultCache } from './toolResultCache.js';
49
51
  import { typedErr } from '@zelari/core/harness/tools/toolTypes';
50
52
  import { cliToolToEnhanced, registerCustomTool } from '@zelari/core/skills';
@@ -105,7 +107,15 @@ export function createBuiltinToolRegistry(options = {}) {
105
107
  const allowMutators = !readOnly && !verifyMode && !gauntletParent;
106
108
  const allowBash = (allowMutators || verifyMode) && !gauntletParent;
107
109
  const permPolicy = options.permissionPolicy ?? defaultPermissionPolicy();
108
- const withPerm = (t) => wrapWithPermissions(t, permPolicy, options.onPermissionAsk);
110
+ // P0.A layered policy engine: per-command/per-path rules for THIS agent
111
+ // identity with global and project kept as DISTINCT layers. Default
112
+ // evaluation is restrict-only (a global deny/ask can never be relaxed by the
113
+ // project file); ZELARI_POLICY_PRECEDENCE=legacy restores the v1
114
+ // project-first override. Loaded once per registry build; empty when
115
+ // absent/invalid/disabled (ZELARI_POLICY=0).
116
+ const agentPolicySet = loadPolicySet(root);
117
+ const agentPolicyLayers = agentLayersFor(agentPolicySet, options.policyAgent ?? 'lead');
118
+ const withPerm = (t) => wrapWithPermissions(t, permPolicy, options.onPermissionAsk, agentPolicyLayers, agentPolicySet.precedence, root);
109
119
  // Observe tools — always registered.
110
120
  registry.register(withPerm(safeReadFile));
111
121
  registry.register(withPerm(safeGrepContent));
@@ -300,6 +310,10 @@ export function createBuiltinToolRegistry(options = {}) {
300
310
  root,
301
311
  audit,
302
312
  sessionId,
313
+ // P0.4 capability inheritance: tentacles intersect THIS
314
+ // registry's own policy (permPolicy above) — they can never
315
+ // exceed it.
316
+ parentPolicy: permPolicy,
303
317
  ...(options.subAgentProvider ? { provider: options.subAgentProvider } : {}),
304
318
  ...(options.subAgentModel ? { model: options.subAgentModel } : {}),
305
319
  }),
@@ -468,7 +482,7 @@ function taskAgentToProfile(agent) {
468
482
  * (F6) — can build a `TaskToolDeps` without duplicating this wiring.
469
483
  */
470
484
  export function createKrakenSubAgentContextFactory(opts) {
471
- const { root, audit, sessionId, provider: providerOverride, model: modelOverride } = opts;
485
+ const { root, audit, sessionId, provider: providerOverride, model: modelOverride, parentPolicy } = opts;
472
486
  return async ({ agent, cwd: subCwd }) => {
473
487
  const cfg = providerOverride
474
488
  ? await providerConfigFor(providerOverride)
@@ -496,6 +510,18 @@ export function createKrakenSubAgentContextFactory(opts) {
496
510
  const subCfg = { ...effCfg, model };
497
511
  const subProfile = taskAgentToProfile(agent);
498
512
  const subRoot = subCwd || root;
513
+ // P0.4 capability inheritance: a tentacle NEVER holds more permission
514
+ // than its parent. The sub-profile policy this factory builds is
515
+ // headless-style auto-allow (a tentacle has no interactive prompt to
516
+ // ask at); the effective policy is its intersection with the parent
517
+ // policy — deny > ask > allow per category, `auto` is the AND of both.
518
+ // If the intersection downgrades a category to `ask`, resolution must
519
+ // FAIL CLOSED: the sub-agent context has no interactive ask handler, and
520
+ // `wrapWithPermissions` already returns typedErr for `ask` without
521
+ // `onPermissionAsk`, so that guarantee is reused as-is here (no extra
522
+ // handling needed).
523
+ const agentPolicyForSubProfile = defaultPermissionPolicy({ auto: true });
524
+ const effectiveSubPolicy = intersectPermissionPolicy(parentPolicy ?? agentPolicyForSubProfile, agentPolicyForSubProfile);
499
525
  const { registry: subRegistry } = createBuiltinToolRegistry({
500
526
  root: subRoot,
501
527
  audit,
@@ -505,7 +531,9 @@ export function createKrakenSubAgentContextFactory(opts) {
505
531
  enableSkill: agent === 'general',
506
532
  diagnostics: false,
507
533
  lspProvider: null,
508
- permissionPolicy: defaultPermissionPolicy({ auto: true }),
534
+ permissionPolicy: effectiveSubPolicy,
535
+ // P0.5: the tentacle's agent identity drives per-agent policy rules.
536
+ policyAgent: agent,
509
537
  });
510
538
  return {
511
539
  providerStream: buildProviderStream(subCfg),
@@ -525,7 +553,7 @@ export function createKrakenSubAgentContextFactory(opts) {
525
553
  /**
526
554
  * Gate tool execution with allow/ask/deny policy (OpenCode-style permissions).
527
555
  */
528
- function wrapWithPermissions(original, policy, onAsk) {
556
+ function wrapWithPermissions(original, policy, onAsk, agentLayers, precedence = 'restrict-only', root) {
529
557
  const required = (original.permissions ?? []);
530
558
  // Pure read tools under allow policy — no wrap overhead.
531
559
  const decisionProbe = resolveToolPermission(original.name, required, policy);
@@ -537,12 +565,24 @@ function wrapWithPermissions(original, policy, onAsk) {
537
565
  ...original,
538
566
  execute: async (input, ctx) => {
539
567
  const decision = resolveToolPermission(original.name, required, policy);
540
- if (decision.action === 'deny') {
541
- return typedErr(`[permission] ${decision.reason}`);
568
+ // P0.A: resolve this agent's rule across the global+project layers —
569
+ // restrict-only by default (deny > ask > allow; a global deny/ask can
570
+ // never be relaxed by the project file; legacy precedence restores the
571
+ // v1 project-first override). The survivor only ever ADDS restriction
572
+ // to the category decision (mergeRuleEffect).
573
+ const rule = agentLayers
574
+ ? matchAgentPolicyRuleLayered(agentLayers, precedence, required, (input ?? {}), root ?? process.cwd())
575
+ : null;
576
+ const action = mergeRuleEffect(decision.action, rule);
577
+ const rulePrefix = rule
578
+ ? `[policy] rule '${rule.match}'${rule.reason ? ` — ${rule.reason}` : ''}`
579
+ : '';
580
+ if (action === 'deny') {
581
+ return typedErr(`[permission] ${rulePrefix || decision.reason}`);
542
582
  }
543
- if (decision.action === 'ask') {
583
+ if (action === 'ask') {
544
584
  if (!onAsk) {
545
- return typedErr(`[permission] ${decision.reason} No interactive approval available ` +
585
+ return typedErr(`[permission] ${rulePrefix ? `${rulePrefix} ` : ''}${decision.reason} No interactive approval available ` +
546
586
  `(set ZELARI_AUTO=1 to auto-allow, or configure onPermissionAsk).`);
547
587
  }
548
588
  try {