@armoriq/policy-ir 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{document-entry-CDOQVeis.d.cts → document-entry-iS3HikJ9.d.cts} +117 -3
- package/dist/{document-entry-CDOQVeis.d.ts → document-entry-iS3HikJ9.d.ts} +117 -3
- package/dist/document.cjs +86 -15
- package/dist/document.cjs.map +1 -1
- package/dist/document.d.cts +1 -1
- package/dist/document.d.ts +1 -1
- package/dist/document.js +85 -15
- package/dist/document.js.map +1 -1
- package/dist/index.cjs +85 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +14 -44
- package/dist/index.d.ts +14 -44
- package/dist/index.js +84 -51
- package/dist/index.js.map +1 -1
- package/dist/vectors.cjs +26 -2
- package/dist/vectors.cjs.map +1 -1
- package/dist/vectors.js +26 -2
- package/dist/vectors.js.map +1 -1
- package/package.json +1 -1
- package/vectors/compile.json +8 -1
- package/vectors/evaluate.json +18 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { P as PolicyDocument, V as Verdict, A as Approval, L as Layer, C as Condition, S as Statement,
|
|
2
|
-
export { c as
|
|
1
|
+
import { P as PolicyDocument, V as Verdict, A as Approval, L as Layer, C as Condition, S as Statement, D as Diagnostics, E as EnforceabilityReport, a as PolicySource, I as InventoryTool, b as InventoryPort } from './document-entry-iS3HikJ9.cjs';
|
|
2
|
+
export { c as CAPABILITY_KEYS, d as COMPOSE_INTENT_MAX, e as Capture, f as ComposeAnswer, g as ComposeChangedCall, h as ComposeExample, i as ComposeHeldServer, j as ComposePreview, k as ComposeQuestion, l as ComposeReplay, m as ComposeRequest, n as ComposeResponse, o as ComposeTarget, p as ComposeUnsupported, q as ConditionOp, r as ConditionSpec, s as Defaults, t as Diagnostic, u as Enforceability, K as KeyTier, N as NEVER, v as NetworkEntry, O as Overlay, w as ParseResult, x as PolicySpec, y as PolicyTarget, z as Provenance, R as RuleValue, B as RuleVariant, F as SCHEMA_VERSION, G as SOURCE_OPERATORS, H as SchemaBinding, J as Section, M as Severity, Q as SourceOperator, T as SourceTarget, U as StatementMatch, W as Subject, X as ToolsSection, Y as VERDICTS, Z as enforceabilityReport, _ as layerOfTarget, $ as parseDocument, a0 as parseYaml, a1 as serialize, a2 as validate } from './document-entry-iS3HikJ9.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Canonical JSON: sorted keys, no whitespace, ASCII escapes for anything
|
|
@@ -43,6 +43,11 @@ interface Principal {
|
|
|
43
43
|
/** One call, as the enforce path and the local hook both see it. */
|
|
44
44
|
interface CallInput {
|
|
45
45
|
tool: string;
|
|
46
|
+
/**
|
|
47
|
+
* The inventory's `mcp__<server>__<tool>` name for the tool the host matched,
|
|
48
|
+
* so a rule keyed by the qualified name applies to a bare-name call.
|
|
49
|
+
*/
|
|
50
|
+
qualifiedName?: string;
|
|
46
51
|
arguments: Record<string, unknown>;
|
|
47
52
|
/**
|
|
48
53
|
* Derived fields a host may supply: `path`, `program`, `command`,
|
|
@@ -98,7 +103,7 @@ interface BoundDocuments {
|
|
|
98
103
|
* Four stages over four layers (PRD §7.4):
|
|
99
104
|
* 1. floor: a floor block ends evaluation; otherwise its verdict is retained
|
|
100
105
|
* 2. resolve: the member scope inside the most specific document
|
|
101
|
-
* 3. candidate: member rule, then agent, then group, then the
|
|
106
|
+
* 3. candidate: member rule, then agent, then group, then the stricter of the bound tools.default values
|
|
102
107
|
* 4. merge: the stricter of floor and candidate
|
|
103
108
|
*/
|
|
104
109
|
declare function evaluate(input: CallInput, bound: BoundDocuments): Evaluation;
|
|
@@ -122,47 +127,12 @@ declare function conditionsHold(input: CallInput, conditions: Condition[]): bool
|
|
|
122
127
|
declare function conditionHolds(input: CallInput, condition: Condition): boolean;
|
|
123
128
|
|
|
124
129
|
/**
|
|
125
|
-
* Key selection:
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
|
|
129
|
-
declare function selectRuleKey(statements: Statement[], tool: string): string | null;
|
|
130
|
-
|
|
131
|
-
/** One tool the target can reach, as the inventory knows it. */
|
|
132
|
-
interface InventoryTool {
|
|
133
|
-
/** The wire name the model sees; for an MCP tool the host may also supply the qualified `mcp__<server>__<tool>` form as `qualifiedName`. */
|
|
134
|
-
name: string;
|
|
135
|
-
qualifiedName?: string;
|
|
136
|
-
owner: {
|
|
137
|
-
type: 'mcp_server' | 'agent' | 'local';
|
|
138
|
-
id: string;
|
|
139
|
-
name?: string;
|
|
140
|
-
};
|
|
141
|
-
schemaHash: string | null;
|
|
142
|
-
inputSchema?: unknown;
|
|
143
|
-
capabilities?: string[];
|
|
144
|
-
/** False for a row the observed feed created and no owner has declared: the compiler warns instead of binding. */
|
|
145
|
-
declared: boolean;
|
|
146
|
-
}
|
|
147
|
-
/** What a host implements: conmap on `tool_snapshots`, the Armor CLI on the local tool list. */
|
|
148
|
-
interface InventoryPort {
|
|
149
|
-
toolsForTarget(target: SourceTarget): Promise<InventoryTool[]>;
|
|
150
|
-
}
|
|
151
|
-
declare const CAPABILITY_KEYS: ReadonlySet<string>;
|
|
152
|
-
|
|
153
|
-
type Enforceability = 'enforced' | 'argument-only' | 'inert';
|
|
154
|
-
type Section = 'tools' | 'files' | 'network' | 'skills' | 'secrets';
|
|
155
|
-
/**
|
|
156
|
-
* Per-section honesty (FR-9, FR-19): a local device sees real paths and
|
|
157
|
-
* hosts; the enterprise SDK path sees only path-shaped and URL-shaped
|
|
158
|
-
* arguments; skills and secrets have no enterprise surface at all.
|
|
130
|
+
* Key selection: a key matches the call's bare name or its qualified name.
|
|
131
|
+
* An exact key beats any glob, a longer key beats a shorter one. Two
|
|
132
|
+
* same-length globs matching one tool are rejected at compile time; here the
|
|
133
|
+
* first in document order wins so evaluation stays deterministic.
|
|
159
134
|
*/
|
|
160
|
-
|
|
161
|
-
sections: Partial<Record<Section, Enforceability>>;
|
|
162
|
-
/** Tools in the inventory that no capability rule reaches, when the document uses capability keys (M-12). */
|
|
163
|
-
notCoveredByCapabilityRules: string[];
|
|
164
|
-
}
|
|
165
|
-
declare function enforceabilityReport(source: PolicySource, inventory: InventoryTool[]): EnforceabilityReport;
|
|
135
|
+
declare function selectRuleKey(statements: Statement[], call: Pick<CallInput, 'tool' | 'qualifiedName'>): string | null;
|
|
166
136
|
|
|
167
137
|
interface CompileOptions {
|
|
168
138
|
policyId: string;
|
|
@@ -198,4 +168,4 @@ declare function checkArgumentKeys(fields: string[], tool: InventoryTool, diag:
|
|
|
198
168
|
|
|
199
169
|
declare const KNOWN_CONTROLS: ReadonlySet<string>;
|
|
200
170
|
|
|
201
|
-
export { Approval, type BoundDocuments,
|
|
171
|
+
export { Approval, type BoundDocuments, type CallInput, type CompileOptions, type CompileResult, Condition, Diagnostics, EnforceabilityReport, type Evaluation, HASH_PREFIX, InventoryPort, InventoryTool, KNOWN_CONTROLS, Layer, type Obligation, PolicyDocument, PolicySource, type Principal, type SchemaDriftObligation, Statement, VERDICT_RANK, Verdict, type VerdictProvenance, canonicalJson, checkArgumentKeys, checkGlobCollisions, compile, compileWithPort, conditionHolds, conditionsHold, evaluate, globToRegExp, isGlob, matchGlob, policyHash, resolveField, resolveKey, schemaHash, selectRuleKey, sha256Canonical, stricter, verifyPolicyHash };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { P as PolicyDocument, V as Verdict, A as Approval, L as Layer, C as Condition, S as Statement,
|
|
2
|
-
export { c as
|
|
1
|
+
import { P as PolicyDocument, V as Verdict, A as Approval, L as Layer, C as Condition, S as Statement, D as Diagnostics, E as EnforceabilityReport, a as PolicySource, I as InventoryTool, b as InventoryPort } from './document-entry-iS3HikJ9.js';
|
|
2
|
+
export { c as CAPABILITY_KEYS, d as COMPOSE_INTENT_MAX, e as Capture, f as ComposeAnswer, g as ComposeChangedCall, h as ComposeExample, i as ComposeHeldServer, j as ComposePreview, k as ComposeQuestion, l as ComposeReplay, m as ComposeRequest, n as ComposeResponse, o as ComposeTarget, p as ComposeUnsupported, q as ConditionOp, r as ConditionSpec, s as Defaults, t as Diagnostic, u as Enforceability, K as KeyTier, N as NEVER, v as NetworkEntry, O as Overlay, w as ParseResult, x as PolicySpec, y as PolicyTarget, z as Provenance, R as RuleValue, B as RuleVariant, F as SCHEMA_VERSION, G as SOURCE_OPERATORS, H as SchemaBinding, J as Section, M as Severity, Q as SourceOperator, T as SourceTarget, U as StatementMatch, W as Subject, X as ToolsSection, Y as VERDICTS, Z as enforceabilityReport, _ as layerOfTarget, $ as parseDocument, a0 as parseYaml, a1 as serialize, a2 as validate } from './document-entry-iS3HikJ9.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Canonical JSON: sorted keys, no whitespace, ASCII escapes for anything
|
|
@@ -43,6 +43,11 @@ interface Principal {
|
|
|
43
43
|
/** One call, as the enforce path and the local hook both see it. */
|
|
44
44
|
interface CallInput {
|
|
45
45
|
tool: string;
|
|
46
|
+
/**
|
|
47
|
+
* The inventory's `mcp__<server>__<tool>` name for the tool the host matched,
|
|
48
|
+
* so a rule keyed by the qualified name applies to a bare-name call.
|
|
49
|
+
*/
|
|
50
|
+
qualifiedName?: string;
|
|
46
51
|
arguments: Record<string, unknown>;
|
|
47
52
|
/**
|
|
48
53
|
* Derived fields a host may supply: `path`, `program`, `command`,
|
|
@@ -98,7 +103,7 @@ interface BoundDocuments {
|
|
|
98
103
|
* Four stages over four layers (PRD §7.4):
|
|
99
104
|
* 1. floor: a floor block ends evaluation; otherwise its verdict is retained
|
|
100
105
|
* 2. resolve: the member scope inside the most specific document
|
|
101
|
-
* 3. candidate: member rule, then agent, then group, then the
|
|
106
|
+
* 3. candidate: member rule, then agent, then group, then the stricter of the bound tools.default values
|
|
102
107
|
* 4. merge: the stricter of floor and candidate
|
|
103
108
|
*/
|
|
104
109
|
declare function evaluate(input: CallInput, bound: BoundDocuments): Evaluation;
|
|
@@ -122,47 +127,12 @@ declare function conditionsHold(input: CallInput, conditions: Condition[]): bool
|
|
|
122
127
|
declare function conditionHolds(input: CallInput, condition: Condition): boolean;
|
|
123
128
|
|
|
124
129
|
/**
|
|
125
|
-
* Key selection:
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
|
|
129
|
-
declare function selectRuleKey(statements: Statement[], tool: string): string | null;
|
|
130
|
-
|
|
131
|
-
/** One tool the target can reach, as the inventory knows it. */
|
|
132
|
-
interface InventoryTool {
|
|
133
|
-
/** The wire name the model sees; for an MCP tool the host may also supply the qualified `mcp__<server>__<tool>` form as `qualifiedName`. */
|
|
134
|
-
name: string;
|
|
135
|
-
qualifiedName?: string;
|
|
136
|
-
owner: {
|
|
137
|
-
type: 'mcp_server' | 'agent' | 'local';
|
|
138
|
-
id: string;
|
|
139
|
-
name?: string;
|
|
140
|
-
};
|
|
141
|
-
schemaHash: string | null;
|
|
142
|
-
inputSchema?: unknown;
|
|
143
|
-
capabilities?: string[];
|
|
144
|
-
/** False for a row the observed feed created and no owner has declared: the compiler warns instead of binding. */
|
|
145
|
-
declared: boolean;
|
|
146
|
-
}
|
|
147
|
-
/** What a host implements: conmap on `tool_snapshots`, the Armor CLI on the local tool list. */
|
|
148
|
-
interface InventoryPort {
|
|
149
|
-
toolsForTarget(target: SourceTarget): Promise<InventoryTool[]>;
|
|
150
|
-
}
|
|
151
|
-
declare const CAPABILITY_KEYS: ReadonlySet<string>;
|
|
152
|
-
|
|
153
|
-
type Enforceability = 'enforced' | 'argument-only' | 'inert';
|
|
154
|
-
type Section = 'tools' | 'files' | 'network' | 'skills' | 'secrets';
|
|
155
|
-
/**
|
|
156
|
-
* Per-section honesty (FR-9, FR-19): a local device sees real paths and
|
|
157
|
-
* hosts; the enterprise SDK path sees only path-shaped and URL-shaped
|
|
158
|
-
* arguments; skills and secrets have no enterprise surface at all.
|
|
130
|
+
* Key selection: a key matches the call's bare name or its qualified name.
|
|
131
|
+
* An exact key beats any glob, a longer key beats a shorter one. Two
|
|
132
|
+
* same-length globs matching one tool are rejected at compile time; here the
|
|
133
|
+
* first in document order wins so evaluation stays deterministic.
|
|
159
134
|
*/
|
|
160
|
-
|
|
161
|
-
sections: Partial<Record<Section, Enforceability>>;
|
|
162
|
-
/** Tools in the inventory that no capability rule reaches, when the document uses capability keys (M-12). */
|
|
163
|
-
notCoveredByCapabilityRules: string[];
|
|
164
|
-
}
|
|
165
|
-
declare function enforceabilityReport(source: PolicySource, inventory: InventoryTool[]): EnforceabilityReport;
|
|
135
|
+
declare function selectRuleKey(statements: Statement[], call: Pick<CallInput, 'tool' | 'qualifiedName'>): string | null;
|
|
166
136
|
|
|
167
137
|
interface CompileOptions {
|
|
168
138
|
policyId: string;
|
|
@@ -198,4 +168,4 @@ declare function checkArgumentKeys(fields: string[], tool: InventoryTool, diag:
|
|
|
198
168
|
|
|
199
169
|
declare const KNOWN_CONTROLS: ReadonlySet<string>;
|
|
200
170
|
|
|
201
|
-
export { Approval, type BoundDocuments,
|
|
171
|
+
export { Approval, type BoundDocuments, type CallInput, type CompileOptions, type CompileResult, Condition, Diagnostics, EnforceabilityReport, type Evaluation, HASH_PREFIX, InventoryPort, InventoryTool, KNOWN_CONTROLS, Layer, type Obligation, PolicyDocument, PolicySource, type Principal, type SchemaDriftObligation, Statement, VERDICT_RANK, Verdict, type VerdictProvenance, canonicalJson, checkArgumentKeys, checkGlobCollisions, compile, compileWithPort, conditionHolds, conditionsHold, evaluate, globToRegExp, isGlob, matchGlob, policyHash, resolveField, resolveKey, schemaHash, selectRuleKey, sha256Canonical, stricter, verifyPolicyHash };
|
package/dist/index.js
CHANGED
|
@@ -214,10 +214,13 @@ function numeric(a, e, test) {
|
|
|
214
214
|
}
|
|
215
215
|
|
|
216
216
|
// src/evaluate/select.ts
|
|
217
|
-
function selectRuleKey(statements,
|
|
217
|
+
function selectRuleKey(statements, call) {
|
|
218
|
+
return selectKey(ruleKeysOf(statements), call.qualifiedName === void 0 ? [call.tool] : [call.tool, call.qualifiedName]);
|
|
219
|
+
}
|
|
220
|
+
function selectKey(keys, names) {
|
|
218
221
|
let best = null;
|
|
219
|
-
for (const key of
|
|
220
|
-
if (!keyMatches(key,
|
|
222
|
+
for (const key of keys) {
|
|
223
|
+
if (!names.some((name) => keyMatches(key, name))) continue;
|
|
221
224
|
if (best === null || keyBeats(key, best)) best = key;
|
|
222
225
|
}
|
|
223
226
|
return best;
|
|
@@ -279,7 +282,7 @@ function decideLayer(statements, input) {
|
|
|
279
282
|
return stricter(capability.statement.effect, ruleDecision.statement.effect) === ruleDecision.statement.effect ? ruleDecision : capability;
|
|
280
283
|
}
|
|
281
284
|
function decideRule(statements, input) {
|
|
282
|
-
const key = selectRuleKey(statements, input
|
|
285
|
+
const key = selectRuleKey(statements, input);
|
|
283
286
|
if (key === null) return null;
|
|
284
287
|
const winner = variantsOf(statements, key).find((v) => conditionsHold(input, v.conditions));
|
|
285
288
|
return winner ? { statement: winner, obligations: driftObligations(winner, input) } : null;
|
|
@@ -307,11 +310,12 @@ function driftObligations(statement, input) {
|
|
|
307
310
|
|
|
308
311
|
// src/evaluate/evaluate.ts
|
|
309
312
|
function evaluate(input, bound) {
|
|
313
|
+
if (!bound.floor && !bound.group && !bound.agent) throw new Error("evaluate: no document is bound to the target");
|
|
310
314
|
const floor2 = decideFloor(bound.floor, input);
|
|
311
315
|
if (floor2?.statement.effect === "block") return fromDecision(floor2, bound.floor, "floor");
|
|
312
|
-
const candidate = decideCandidate(input, bound);
|
|
316
|
+
const candidate = decideCandidate(input, bound, floor2 === null);
|
|
313
317
|
const floorVerdict = floor2?.statement.effect ?? null;
|
|
314
|
-
if (floorVerdict && stricter(floorVerdict, candidate.verdict) !== candidate.verdict) {
|
|
318
|
+
if (!candidate || floorVerdict && stricter(floorVerdict, candidate.verdict) !== candidate.verdict) {
|
|
315
319
|
return { ...fromDecision(floor2, bound.floor, "floor"), floorVerdict };
|
|
316
320
|
}
|
|
317
321
|
return { ...candidate, floorVerdict };
|
|
@@ -320,21 +324,28 @@ function decideFloor(doc, input) {
|
|
|
320
324
|
if (!doc) return null;
|
|
321
325
|
return decideLayer(liveStatements(doc, input).filter((s) => s.subject === null), input);
|
|
322
326
|
}
|
|
323
|
-
function decideCandidate(input, bound) {
|
|
324
|
-
for (const
|
|
325
|
-
if (!
|
|
326
|
-
const statements = liveStatements(
|
|
327
|
+
function decideCandidate(input, bound, floorSilent) {
|
|
328
|
+
for (const doc2 of [bound.agent, bound.group]) {
|
|
329
|
+
if (!doc2) continue;
|
|
330
|
+
const statements = liveStatements(doc2, input);
|
|
327
331
|
const member = decideLayer(memberScope(statements, input.member), input);
|
|
328
|
-
if (member) return fromDecision(member,
|
|
332
|
+
if (member) return fromDecision(member, doc2, "member");
|
|
329
333
|
const own = decideLayer(statements.filter((s) => s.subject === null), input);
|
|
330
|
-
if (own) return fromDecision(own,
|
|
334
|
+
if (own) return fromDecision(own, doc2, doc2.layer);
|
|
331
335
|
}
|
|
332
|
-
|
|
336
|
+
const doc = defaultDocument(bound, floorSilent, input.timestamp);
|
|
337
|
+
return doc ? fromDefault(doc) : null;
|
|
333
338
|
}
|
|
334
|
-
function
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
return
|
|
339
|
+
function defaultDocument(bound, floorSilent, timestamp) {
|
|
340
|
+
const all = [bound.agent, bound.group, bound.floor].filter((d) => d !== void 0);
|
|
341
|
+
const live = all.filter((d) => isLive(d.expiresAt, timestamp));
|
|
342
|
+
if (live.length === 0) return all[0] ?? null;
|
|
343
|
+
let winner = null;
|
|
344
|
+
for (const doc of live) {
|
|
345
|
+
if (doc === bound.floor && !floorSilent) continue;
|
|
346
|
+
if (!winner || stricter(winner.defaults.tools, doc.defaults.tools) !== winner.defaults.tools) winner = doc;
|
|
347
|
+
}
|
|
348
|
+
return winner;
|
|
338
349
|
}
|
|
339
350
|
function fromDecision(decision, doc, layer) {
|
|
340
351
|
const { statement } = decision;
|
|
@@ -360,7 +371,7 @@ function fromDefault(doc) {
|
|
|
360
371
|
return {
|
|
361
372
|
verdict,
|
|
362
373
|
floorVerdict: null,
|
|
363
|
-
approval: null,
|
|
374
|
+
approval: verdict === "hold" ? doc.defaults.approval ?? null : null,
|
|
364
375
|
obligations: [],
|
|
365
376
|
provenance: { policyId: doc.policyId, statementId: null, ruleKey: "tools.default", variant: -1, layer: doc.layer }
|
|
366
377
|
};
|
|
@@ -769,6 +780,35 @@ function parseCapture(r, value) {
|
|
|
769
780
|
return out;
|
|
770
781
|
}
|
|
771
782
|
|
|
783
|
+
// src/document/loosening.ts
|
|
784
|
+
function looseningsOf(base, overlay, inventory) {
|
|
785
|
+
const baseKeys = Object.keys(base.rules ?? {});
|
|
786
|
+
const overlayKeys = Object.keys(overlay);
|
|
787
|
+
const out = [];
|
|
788
|
+
for (const key of overlayKeys) {
|
|
789
|
+
const verdict = loosest(overlay[key]);
|
|
790
|
+
const bases = [baseVerdict(base, baseKeys.includes(key) ? key : null)];
|
|
791
|
+
for (const tool of inventory) {
|
|
792
|
+
const names = tool.qualifiedName === void 0 ? [tool.name] : [tool.name, tool.qualifiedName];
|
|
793
|
+
if (selectKey(overlayKeys, names) === key) bases.push(baseVerdict(base, selectKey(baseKeys, names)));
|
|
794
|
+
}
|
|
795
|
+
const strictest = bases.reduce((a, b) => looser(a, b) ? b : a);
|
|
796
|
+
if (looser(verdict, strictest)) out.push({ key, verdict, base: strictest });
|
|
797
|
+
}
|
|
798
|
+
return out;
|
|
799
|
+
}
|
|
800
|
+
function baseVerdict(base, key) {
|
|
801
|
+
const variants = key === null ? void 0 : base.rules?.[key];
|
|
802
|
+
if (!variants) return base.default;
|
|
803
|
+
return variants.reduce((v, x) => looser(v, x.action) ? x.action : v, "allow");
|
|
804
|
+
}
|
|
805
|
+
function loosest(variants) {
|
|
806
|
+
return variants.reduce((v, x) => looser(x.action, v) ? x.action : v, "block");
|
|
807
|
+
}
|
|
808
|
+
function looser(a, b) {
|
|
809
|
+
return VERDICT_RANK[a] < VERDICT_RANK[b];
|
|
810
|
+
}
|
|
811
|
+
|
|
772
812
|
// src/document/portable-regex.ts
|
|
773
813
|
function isPortablePattern(pattern) {
|
|
774
814
|
if (typeof pattern !== "string") return false;
|
|
@@ -883,9 +923,9 @@ function isSurrogate(c) {
|
|
|
883
923
|
}
|
|
884
924
|
|
|
885
925
|
// src/document/validate.ts
|
|
886
|
-
function validate(source) {
|
|
926
|
+
function validate(source, inventory = []) {
|
|
887
927
|
const diag = new Diagnostics();
|
|
888
|
-
const ctx = { diag, approvalDefaults: source.spec.approvalDefaults, base: source.spec.tools };
|
|
928
|
+
const ctx = { diag, approvalDefaults: source.spec.approvalDefaults, base: source.spec.tools, inventory };
|
|
889
929
|
validateTargetSections(ctx, source);
|
|
890
930
|
if (source.spec.approvalDefaults) validateApproval(ctx, source.spec.approvalDefaults, "/spec/approvalDefaults", true);
|
|
891
931
|
validateRules(ctx, source.spec.tools.rules, "/spec/tools/rules");
|
|
@@ -978,23 +1018,14 @@ function validateOverlays(ctx, overlays2, path) {
|
|
|
978
1018
|
}
|
|
979
1019
|
function flagLoosening(ctx, overlay, path) {
|
|
980
1020
|
const baseDefault = ctx.base.default;
|
|
981
|
-
if (overlay.tools?.default &&
|
|
1021
|
+
if (overlay.tools?.default && looser2(overlay.tools.default, baseDefault)) {
|
|
982
1022
|
ctx.diag.warning("loosening_grant", `${path}/tools/default`, `default ${overlay.tools.default} is looser than the base default ${baseDefault}`);
|
|
983
1023
|
}
|
|
984
|
-
for (const
|
|
985
|
-
|
|
986
|
-
const loosest = variants.reduce((v, x) => looser(x.action, v) ? x.action : v, "block");
|
|
987
|
-
if (looser(loosest, base)) {
|
|
988
|
-
ctx.diag.warning("loosening_grant", `${path}/tools/rules/${key}`, `${key}: ${loosest} is looser than the base ${base}`);
|
|
989
|
-
}
|
|
1024
|
+
for (const { key, verdict, base } of looseningsOf(ctx.base, overlay.tools?.rules ?? {}, ctx.inventory)) {
|
|
1025
|
+
ctx.diag.warning("loosening_grant", `${path}/tools/rules/${key}`, `${key}: ${verdict} is looser than the base ${base}`);
|
|
990
1026
|
}
|
|
991
1027
|
}
|
|
992
|
-
function
|
|
993
|
-
const variants = base.rules?.[key];
|
|
994
|
-
if (!variants) return base.default;
|
|
995
|
-
return variants.reduce((v, x) => looser(v, x.action) ? x.action : v, "allow");
|
|
996
|
-
}
|
|
997
|
-
function looser(a, b) {
|
|
1028
|
+
function looser2(a, b) {
|
|
998
1029
|
return VERDICT_RANK[a] < VERDICT_RANK[b];
|
|
999
1030
|
}
|
|
1000
1031
|
function validateCapture(ctx, source) {
|
|
@@ -1036,11 +1067,11 @@ function resolveKey(key, inventory, diag, path) {
|
|
|
1036
1067
|
}
|
|
1037
1068
|
function bindExact(key, tool, diag, path) {
|
|
1038
1069
|
if (!tool) {
|
|
1039
|
-
diag.warning("unknown_tool_key", path, `"${key}" is not in the target's inventory; the rule
|
|
1070
|
+
diag.warning("unknown_tool_key", path, `"${key}" is not in the target's inventory yet; the rule is enforced on any call that names it, but its conditions are not checked against a schema`);
|
|
1040
1071
|
return null;
|
|
1041
1072
|
}
|
|
1042
1073
|
if (!tool.declared) {
|
|
1043
|
-
diag.warning("observed_only_tool", path, `"${key}" has
|
|
1074
|
+
diag.warning("observed_only_tool", path, `"${key}" has been observed but never declared; the rule is enforced on any call that names it, but its conditions are not checked against a schema`);
|
|
1044
1075
|
return null;
|
|
1045
1076
|
}
|
|
1046
1077
|
return tool;
|
|
@@ -1348,13 +1379,15 @@ function uncovered(source, inventory) {
|
|
|
1348
1379
|
|
|
1349
1380
|
// src/compile/compile.ts
|
|
1350
1381
|
function compile(source, inventory, options) {
|
|
1351
|
-
const diag = validate(source);
|
|
1382
|
+
const diag = validate(source, inventory);
|
|
1352
1383
|
const report = enforceabilityReport(source, inventory);
|
|
1353
1384
|
if (diag.hasErrors) return { document: null, diagnostics: diag, report };
|
|
1354
1385
|
const layer = layerOfTarget(targetOf2(source.target));
|
|
1355
1386
|
const ctx = { inventory, diag, layer, approvalDefaults: source.spec.approvalDefaults, source: options.source ?? "yaml" };
|
|
1356
1387
|
checkGlobCollisions(Object.keys(source.spec.tools.rules ?? {}), inventory, diag, "/spec/tools/rules");
|
|
1357
|
-
|
|
1388
|
+
checkOverlayGlobCollisions(source.spec.members, inventory, diag, "/spec/members");
|
|
1389
|
+
checkOverlayGlobCollisions(source.spec.products, inventory, diag, "/spec/products");
|
|
1390
|
+
checkDeviceFirst(source, inventory, diag);
|
|
1358
1391
|
const statements = [
|
|
1359
1392
|
...lowerRules(source.spec.tools.rules, ctx, "/spec/tools/rules", null, source.spec.expiresAt),
|
|
1360
1393
|
...lowerFloors(source.spec, ctx),
|
|
@@ -1390,7 +1423,8 @@ function defaultsOf(source) {
|
|
|
1390
1423
|
tools: source.spec.tools.default,
|
|
1391
1424
|
network: source.spec.network?.default ?? "block",
|
|
1392
1425
|
files: "allow",
|
|
1393
|
-
skills: source.spec.skills?.default ?? "allow"
|
|
1426
|
+
skills: source.spec.skills?.default ?? "allow",
|
|
1427
|
+
...source.spec.approvalDefaults ? { approval: source.spec.approvalDefaults } : {}
|
|
1394
1428
|
};
|
|
1395
1429
|
}
|
|
1396
1430
|
function captureOf(source) {
|
|
@@ -1402,24 +1436,19 @@ function captureOf(source) {
|
|
|
1402
1436
|
sample: c?.sample ?? 1
|
|
1403
1437
|
};
|
|
1404
1438
|
}
|
|
1405
|
-
function
|
|
1439
|
+
function checkOverlayGlobCollisions(overlays2, inventory, diag, path) {
|
|
1440
|
+
for (const [key, overlay] of Object.entries(overlays2 ?? {})) {
|
|
1441
|
+
checkGlobCollisions(Object.keys(overlay.tools?.rules ?? {}), inventory, diag, `${path}/${key}/tools/rules`);
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
function checkDeviceFirst(source, inventory, diag) {
|
|
1406
1445
|
if (!("device" in source.target) || source.precedence === "agent-first") return;
|
|
1407
1446
|
for (const [product, overlay] of Object.entries(source.spec.products ?? {})) {
|
|
1408
|
-
for (const
|
|
1409
|
-
|
|
1410
|
-
diag.error("device_first_loosening", `/spec/products/${product}/tools/rules/${key}`, `under device-first a product overlay may only tighten the base; set precedence: agent-first to allow this`);
|
|
1411
|
-
}
|
|
1447
|
+
for (const { key } of looseningsOf(source.spec.tools, overlay.tools?.rules ?? {}, inventory)) {
|
|
1448
|
+
diag.error("device_first_loosening", `/spec/products/${product}/tools/rules/${key}`, `under device-first a product overlay may only tighten the base; set precedence: agent-first to allow this`);
|
|
1412
1449
|
}
|
|
1413
1450
|
}
|
|
1414
1451
|
}
|
|
1415
|
-
function baseRankFor(source, key) {
|
|
1416
|
-
const base = source.spec.tools.rules?.[key];
|
|
1417
|
-
if (!Array.isArray(base)) return VERDICT_RANK[source.spec.tools.default];
|
|
1418
|
-
return Math.min(...base.map((v) => VERDICT_RANK[v.action]));
|
|
1419
|
-
}
|
|
1420
|
-
function loosensBase(variants, baseRank) {
|
|
1421
|
-
return variants.some((v) => VERDICT_RANK[v.action] < baseRank);
|
|
1422
|
-
}
|
|
1423
1452
|
|
|
1424
1453
|
// src/compile/serialize.ts
|
|
1425
1454
|
import { stringify } from "yaml";
|
|
@@ -1455,8 +1484,12 @@ function overlays(map) {
|
|
|
1455
1484
|
}
|
|
1456
1485
|
return out;
|
|
1457
1486
|
}
|
|
1487
|
+
|
|
1488
|
+
// src/compose.ts
|
|
1489
|
+
var COMPOSE_INTENT_MAX = 1e5;
|
|
1458
1490
|
export {
|
|
1459
1491
|
CAPABILITY_KEYS,
|
|
1492
|
+
COMPOSE_INTENT_MAX,
|
|
1460
1493
|
Diagnostics,
|
|
1461
1494
|
HASH_PREFIX,
|
|
1462
1495
|
KNOWN_CONTROLS,
|