@agenticprimitives/tool-policy 0.1.0-alpha.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Agentic Trust Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,50 @@
1
+ # @agenticprimitives/tool-policy
2
+
3
+ Protocol-agnostic classification, risk tiers, and exact-call policy primitives. Consumable by MCP, A2A, LangGraph, Vercel AI — any tool runtime.
4
+
5
+ This package is **deliberately transport-free**: no MCP SDK, no LangChain, no Vercel imports. That's what lets a LangGraph user `pnpm add` it without buying into MCP middleware, and what lets `@agenticprimitives/mcp-runtime` use it without being the only consumer.
6
+
7
+ See [`spec.md`](./spec.md) → [`specs/204-tool-policy.md`](../../specs/204-tool-policy.md).
8
+
9
+ ## Quick start
10
+
11
+ ```ts
12
+ import { declareTool, evaluatePolicy } from '@agenticprimitives/tool-policy';
13
+
14
+ const getProfileTool = declareTool(
15
+ { name: 'get_profile', inputSchema: { /* ... */ }, handler: /* ... */ },
16
+ {
17
+ '@sa-tool': 'delegation-verified',
18
+ '@sa-auth': 'session-token',
19
+ '@sa-validation': 'shape-check',
20
+ '@sa-risk-tier': 'low',
21
+ },
22
+ );
23
+
24
+ const decision = evaluatePolicy({
25
+ toolName: 'get_profile',
26
+ classification: getProfileTool._classification,
27
+ delegation, // from @agenticprimitives/delegation
28
+ callerKind: 'user-session',
29
+ });
30
+
31
+ if (decision.decision === 'deny') throw new Error(decision.reason);
32
+ if (decision.decision === 'requires-consent') /* prompt user */;
33
+ ```
34
+
35
+ ## Exact-call policy
36
+
37
+ ```ts
38
+ import { exactCall, matchesExactCall } from '@agenticprimitives/tool-policy';
39
+
40
+ const policy = exactCall(safeAddress, '0xa9059cbb', {
41
+ calldataHash: '0xdeadbeef...',
42
+ valueLimit: 0n,
43
+ });
44
+
45
+ const ok = matchesExactCall({ to, data, value }, policy);
46
+ ```
47
+
48
+ ## Status
49
+
50
+ Pre-alpha. Spec stable.
@@ -0,0 +1,5 @@
1
+ import type { ToolClassification } from './types';
2
+ export declare function declareTool<T>(def: T, classification: ToolClassification): T & {
3
+ _classification: ToolClassification;
4
+ };
5
+ //# sourceMappingURL=classification.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classification.d.ts","sourceRoot":"","sources":["../src/classification.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD,wBAAgB,WAAW,CAAC,CAAC,EAC3B,GAAG,EAAE,CAAC,EACN,cAAc,EAAE,kBAAkB,GACjC,CAAC,GAAG;IAAE,eAAe,EAAE,kBAAkB,CAAA;CAAE,CAI7C"}
@@ -0,0 +1,6 @@
1
+ // declareTool: attach a classification at runtime so non-JSDoc consumers
2
+ // surface the same metadata that the lint script reads from JSDoc tags.
3
+ export function declareTool(def, classification) {
4
+ return Object.assign(def, { _classification: classification });
5
+ }
6
+ //# sourceMappingURL=classification.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"classification.js","sourceRoot":"","sources":["../src/classification.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,wEAAwE;AAIxE,MAAM,UAAU,WAAW,CACzB,GAAM,EACN,cAAkC;IAElC,OAAO,MAAM,CAAC,MAAM,CAAC,GAAa,EAAE,EAAE,eAAe,EAAE,cAAc,EAAE,CAEtE,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { PolicyContext, PolicyDecision } from './types';
2
+ export declare function evaluatePolicy(ctx: PolicyContext): PolicyDecision;
3
+ //# sourceMappingURL=decision.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision.d.ts","sourceRoot":"","sources":["../src/decision.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAsB,MAAM,SAAS,CAAC;AAsDjF,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,cAAc,CA4CjE"}
@@ -0,0 +1,103 @@
1
+ // evaluatePolicy — pure decision engine, no I/O, no clocks.
2
+ // Deterministic; same context → same decision. Fail-CLOSED on any
3
+ // unrecognised classification metadata (audit P0-1).
4
+ //
5
+ // Order of operations (fail-closed first):
6
+ // 0. Classification shape: every required tag present + value in
7
+ // the closed enum set → reject otherwise.
8
+ // 1. classification['@sa-auth'] === 'none' AND caller != 'service' → deny
9
+ // 2. classification['@sa-tool'] === 'service-only' AND caller != 'service' → deny
10
+ // 3. classification['@sa-tool'] === 'bootstrap' AND caller != 'service' → deny
11
+ // 4. classification['@sa-tool'] === 'dev-only' → deny (env gate required)
12
+ // 5. classification['@sa-tool'] === 'delegation-verified' AND no delegation → deny
13
+ // 6. classification['@sa-risk-tier'] === 'critical' → requires-consent
14
+ // 7. else → allow
15
+ // Closed enum sets — keep in lockstep with `types.ts ToolClassification`.
16
+ // Any value outside these sets is treated as unknown → deny. This is
17
+ // the wire-format security boundary; loosening any of these requires
18
+ // a coordinated change to the type, the enum here, and (likely) the
19
+ // production preflight that audits classification coverage.
20
+ const KNOWN_TOOL_KINDS = new Set([
21
+ 'delegation-verified',
22
+ 'service-only',
23
+ 'bootstrap',
24
+ 'dev-only',
25
+ ]);
26
+ const KNOWN_AUTH_KINDS = new Set([
27
+ 'session-token',
28
+ 'service-hmac',
29
+ 'none',
30
+ 'none-with-csrf',
31
+ ]);
32
+ const KNOWN_RISK_TIERS = new Set(['low', 'medium', 'high', 'critical']);
33
+ /**
34
+ * Validate the *shape* of a classification. Returns null when the
35
+ * classification is well-formed, or a deny-reason string otherwise.
36
+ *
37
+ * Spec invariant: `@sa-tool` AND `@sa-auth` are REQUIRED; `@sa-risk-tier`
38
+ * is required for everything except `service-only` / `bootstrap` /
39
+ * `none-with-csrf` (which have no human risk dimension). Unknown values
40
+ * in ANY of the three tags is unrecoverable — the package can't infer
41
+ * an interpretation it doesn't know.
42
+ */
43
+ function validateClassificationShape(cls) {
44
+ const tool = cls['@sa-tool'];
45
+ if (typeof tool !== 'string' || !KNOWN_TOOL_KINDS.has(tool)) {
46
+ return `classification: @sa-tool="${tool}" not in known set ${[...KNOWN_TOOL_KINDS].join('|')}`;
47
+ }
48
+ const auth = cls['@sa-auth'];
49
+ if (typeof auth !== 'string' || !KNOWN_AUTH_KINDS.has(auth)) {
50
+ return `classification: @sa-auth="${auth}" not in known set ${[...KNOWN_AUTH_KINDS].join('|')}`;
51
+ }
52
+ // Risk tier is REQUIRED for user-facing tool kinds.
53
+ const userFacing = tool === 'delegation-verified' || tool === 'dev-only';
54
+ const tier = cls['@sa-risk-tier'];
55
+ if (userFacing) {
56
+ if (typeof tier !== 'string' || !KNOWN_RISK_TIERS.has(tier)) {
57
+ return `classification: @sa-risk-tier="${tier}" required for tool=${tool} but not in known set ${[...KNOWN_RISK_TIERS].join('|')}`;
58
+ }
59
+ }
60
+ else if (tier !== undefined && !KNOWN_RISK_TIERS.has(tier)) {
61
+ // Set but with an unknown value → deny (no silent acceptance).
62
+ return `classification: @sa-risk-tier="${tier}" not in known set ${[...KNOWN_RISK_TIERS].join('|')}`;
63
+ }
64
+ return null;
65
+ }
66
+ export function evaluatePolicy(ctx) {
67
+ const cls = ctx.classification;
68
+ // 0. SHAPE GATE (fail-closed default). Unknown/missing classification
69
+ // metadata is the most common cause of mis-configured tools
70
+ // silently being accepted. Reject here before any rule fires.
71
+ const shapeErr = validateClassificationShape(cls);
72
+ if (shapeErr) {
73
+ return { decision: 'deny', reason: shapeErr };
74
+ }
75
+ if (cls['@sa-auth'] === 'none' && ctx.callerKind !== 'service') {
76
+ return { decision: 'deny', reason: 'tool is @sa-auth:none; only service callers permitted' };
77
+ }
78
+ if (cls['@sa-tool'] === 'service-only' && ctx.callerKind !== 'service') {
79
+ return { decision: 'deny', reason: 'tool is @sa-tool:service-only; user/agent calls rejected' };
80
+ }
81
+ if (cls['@sa-tool'] === 'bootstrap' && ctx.callerKind !== 'service') {
82
+ return { decision: 'deny', reason: 'tool is @sa-tool:bootstrap; only service callers permitted' };
83
+ }
84
+ if (cls['@sa-tool'] === 'dev-only') {
85
+ // Dev-only tools must be gated by environment; we don't read env here
86
+ // (purity), so we deny unless explicitly classified differently by
87
+ // the consumer's runtime. Consumers wanting "allow in dev" should
88
+ // re-declare the tool as service-only with @sa-prod-gate:disabled.
89
+ return { decision: 'deny', reason: 'tool is @sa-tool:dev-only; explicit env gate required' };
90
+ }
91
+ if (cls['@sa-tool'] === 'delegation-verified' && !ctx.delegation) {
92
+ return { decision: 'deny', reason: 'tool requires delegation but none presented' };
93
+ }
94
+ if (cls['@sa-risk-tier'] === 'critical') {
95
+ return {
96
+ decision: 'requires-consent',
97
+ promptId: `consent:critical:${ctx.toolName}`,
98
+ risk: 'critical',
99
+ };
100
+ }
101
+ return { decision: 'allow' };
102
+ }
103
+ //# sourceMappingURL=decision.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decision.js","sourceRoot":"","sources":["../src/decision.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import type { Address, Hex } from '@agenticprimitives/types';
2
+ import type { ExactCallPolicy } from './types';
3
+ export declare function exactCall(target: Address, selector: Hex, opts?: {
4
+ calldataHash?: Hex;
5
+ valueLimit?: bigint;
6
+ }): ExactCallPolicy;
7
+ export declare function matchesExactCall(call: {
8
+ to: Address;
9
+ data: Hex;
10
+ value: bigint;
11
+ }, policy: ExactCallPolicy): boolean;
12
+ //# sourceMappingURL=exact-call.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exact-call.d.ts","sourceRoot":"","sources":["../src/exact-call.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAY/C,wBAAgB,SAAS,CACvB,MAAM,EAAE,OAAO,EACf,QAAQ,EAAE,GAAG,EACb,IAAI,CAAC,EAAE;IAAE,YAAY,CAAC,EAAE,GAAG,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,GACjD,eAAe,CAajB;AAED,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,GAAG,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAC/C,MAAM,EAAE,eAAe,GACtB,OAAO,CAeT"}
@@ -0,0 +1,49 @@
1
+ // Exact-call DSL: a delegation can authorize EXACTLY one call
2
+ // (target + selector + optional calldata hash + optional value cap).
3
+ //
4
+ // Used for critical-tier operations where the user should authorize the
5
+ // exact intent and nothing else.
6
+ import { keccak_256 } from '@noble/hashes/sha3';
7
+ function keccak256OfHex(hex) {
8
+ const cleaned = hex.startsWith('0x') ? hex.slice(2) : hex;
9
+ const bytes = new Uint8Array(cleaned.length / 2);
10
+ for (let i = 0; i < bytes.length; i++)
11
+ bytes[i] = parseInt(cleaned.slice(i * 2, i * 2 + 2), 16);
12
+ const out = keccak_256(bytes);
13
+ let s = '0x';
14
+ for (const b of out)
15
+ s += b.toString(16).padStart(2, '0');
16
+ return s;
17
+ }
18
+ export function exactCall(target, selector, opts) {
19
+ if (!/^0x[0-9a-fA-F]{8}$/.test(selector)) {
20
+ throw new Error(`exactCall: selector must be 4 bytes (0x + 8 hex chars); got "${selector}"`);
21
+ }
22
+ if (opts?.calldataHash && !/^0x[0-9a-fA-F]{64}$/.test(opts.calldataHash)) {
23
+ throw new Error(`exactCall: calldataHash must be 32 bytes (0x + 64 hex chars); got "${opts.calldataHash}"`);
24
+ }
25
+ return {
26
+ target,
27
+ selector,
28
+ calldataHash: opts?.calldataHash,
29
+ valueLimit: opts?.valueLimit,
30
+ };
31
+ }
32
+ export function matchesExactCall(call, policy) {
33
+ if (call.to.toLowerCase() !== policy.target.toLowerCase())
34
+ return false;
35
+ if (!call.data.startsWith('0x') || call.data.length < 10)
36
+ return false;
37
+ const callSelector = call.data.slice(0, 10).toLowerCase();
38
+ if (callSelector !== policy.selector.toLowerCase())
39
+ return false;
40
+ if (policy.calldataHash) {
41
+ const computed = keccak256OfHex(call.data);
42
+ if (computed.toLowerCase() !== policy.calldataHash.toLowerCase())
43
+ return false;
44
+ }
45
+ if (policy.valueLimit !== undefined && call.value > policy.valueLimit)
46
+ return false;
47
+ return true;
48
+ }
49
+ //# sourceMappingURL=exact-call.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exact-call.js","sourceRoot":"","sources":["../src/exact-call.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,qEAAqE;AACrE,EAAE;AACF,wEAAwE;AACxE,iCAAiC;AAEjC,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAIhD,SAAS,cAAc,CAAC,GAAQ;IAC9B,MAAM,OAAO,GAAG,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IAC1D,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACjD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,KAAK,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAChG,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IAC9B,IAAI,CAAC,GAAG,IAAI,CAAC;IACb,KAAK,MAAM,CAAC,IAAI,GAAG;QAAE,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC1D,OAAO,CAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,SAAS,CACvB,MAAe,EACf,QAAa,EACb,IAAkD;IAElD,IAAI,CAAC,oBAAoB,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CAAC,gEAAgE,QAAQ,GAAG,CAAC,CAAC;IAC/F,CAAC;IACD,IAAI,IAAI,EAAE,YAAY,IAAI,CAAC,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;QACzE,MAAM,IAAI,KAAK,CAAC,sEAAsE,IAAI,CAAC,YAAY,GAAG,CAAC,CAAC;IAC9G,CAAC;IACD,OAAO;QACL,MAAM;QACN,QAAQ;QACR,YAAY,EAAE,IAAI,EAAE,YAAY;QAChC,UAAU,EAAE,IAAI,EAAE,UAAU;KAC7B,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,IAA+C,EAC/C,MAAuB;IAEvB,IAAI,IAAI,CAAC,EAAE,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE;QAAE,OAAO,KAAK,CAAC;IAExE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,EAAE;QAAE,OAAO,KAAK,CAAC;IACvE,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IAC1D,IAAI,YAAY,KAAK,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE;QAAE,OAAO,KAAK,CAAC;IAEjE,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;QACxB,MAAM,QAAQ,GAAG,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3C,IAAI,QAAQ,CAAC,WAAW,EAAE,KAAK,MAAM,CAAC,YAAY,CAAC,WAAW,EAAE;YAAE,OAAO,KAAK,CAAC;IACjF,CAAC;IAED,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,IAAI,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,UAAU;QAAE,OAAO,KAAK,CAAC;IAEpF,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,8 @@
1
+ export { declareTool } from './classification';
2
+ export { exactCall, matchesExactCall } from './exact-call';
3
+ export { evaluatePolicy } from './decision';
4
+ export { clampTtlForRiskTier, requiredCaveatsForRiskTier, evaluateThresholdPolicy, RISK_TIER_REQUIREMENTS, } from './risk-tier';
5
+ export { lintClassification } from './lint';
6
+ export { ThresholdTier } from './types';
7
+ export type { Address, Hex, RiskTier, ToolClassification, ExactCallPolicy, CaveatLike, DelegationLike, CaveatContext, PolicyContext, PolicyDecision, ThresholdPolicyDecision, LintResult, } from './types';
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EACL,mBAAmB,EACnB,0BAA0B,EAC1B,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AAE5C,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAExC,YAAY,EACV,OAAO,EACP,GAAG,EACH,QAAQ,EACR,kBAAkB,EAClB,eAAe,EACf,UAAU,EACV,cAAc,EACd,aAAa,EACb,aAAa,EACb,cAAc,EACd,uBAAuB,EACvB,UAAU,GACX,MAAM,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,16 @@
1
+ // @agenticprimitives/tool-policy — public API
2
+ //
3
+ // See ../../specs/204-tool-policy.md for the full contract.
4
+ //
5
+ // CRITICAL: This package is protocol-agnostic. It MUST NOT import from any
6
+ // transport SDK (see capability.manifest.json:forbiddenImports for the full
7
+ // list and docs/architecture/decisions/0003-tool-policy-protocol-agnostic.md
8
+ // for why).
9
+ export { declareTool } from './classification';
10
+ export { exactCall, matchesExactCall } from './exact-call';
11
+ export { evaluatePolicy } from './decision';
12
+ export { clampTtlForRiskTier, requiredCaveatsForRiskTier, evaluateThresholdPolicy, RISK_TIER_REQUIREMENTS, } from './risk-tier';
13
+ export { lintClassification } from './lint';
14
+ // ThresholdTier is an enum (value export, not type-only).
15
+ export { ThresholdTier } from './types';
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8CAA8C;AAC9C,EAAE;AACF,4DAA4D;AAC5D,EAAE;AACF,2EAA2E;AAC3E,4EAA4E;AAC5E,6EAA6E;AAC7E,YAAY;AAEZ,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EACL,mBAAmB,EACnB,0BAA0B,EAC1B,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AAC5C,0DAA0D;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC"}
package/dist/lint.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import type { LintResult } from './types';
2
+ interface LintOpts {
3
+ srcDir: string;
4
+ requiredTags: string[];
5
+ optionalTags?: string[];
6
+ tagBlockPattern?: RegExp;
7
+ }
8
+ export declare function lintClassification(opts: LintOpts): Promise<LintResult>;
9
+ export {};
10
+ //# sourceMappingURL=lint.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../src/lint.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAK1C,UAAU,QAAQ;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAgBD,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,UAAU,CAAC,CA4B5E"}
package/dist/lint.js ADDED
@@ -0,0 +1,56 @@
1
+ // lintClassification — scan a source tree for tool definitions and verify
2
+ // each carries the required JSDoc @sa-* tags.
3
+ //
4
+ // v0 implementation: regex-based scan of the source tree. JSDoc parsing
5
+ // proper happens in v0.1; for the demo we focus on tag presence on
6
+ // function/object definitions that look like tools.
7
+ import { readdirSync, readFileSync, statSync } from 'node:fs';
8
+ import { join, extname } from 'node:path';
9
+ const DEFAULT_PATTERN = /\/\*\*([\s\S]*?)\*\//g;
10
+ const TOOL_HEURISTIC = /(?:export\s+(?:const|function)\s+\w+Tool|name:\s*['"`])/;
11
+ function* walk(dir) {
12
+ for (const entry of readdirSync(dir)) {
13
+ if (entry.startsWith('.'))
14
+ continue;
15
+ if (entry === 'node_modules' || entry === 'dist' || entry === 'coverage')
16
+ continue;
17
+ const p = join(dir, entry);
18
+ const st = statSync(p);
19
+ if (st.isDirectory()) {
20
+ yield* walk(p);
21
+ }
22
+ else if (['.ts', '.tsx', '.js', '.jsx'].includes(extname(entry))) {
23
+ yield p;
24
+ }
25
+ }
26
+ }
27
+ export async function lintClassification(opts) {
28
+ const required = new Set(opts.requiredTags);
29
+ const result = { passed: true, errors: [] };
30
+ const blockRe = opts.tagBlockPattern ?? DEFAULT_PATTERN;
31
+ for (const file of walk(opts.srcDir)) {
32
+ const text = readFileSync(file, 'utf8');
33
+ blockRe.lastIndex = 0;
34
+ let m;
35
+ while ((m = blockRe.exec(text)) !== null) {
36
+ const block = m[0];
37
+ const after = text.slice(m.index + block.length, m.index + block.length + 200);
38
+ if (!TOOL_HEURISTIC.test(after))
39
+ continue;
40
+ const present = new Set();
41
+ for (const tag of [...required, ...(opts.optionalTags ?? [])]) {
42
+ const tagRe = new RegExp(`@${tag.replace(/^@/, '').replace(/[.+^${}()|[\]\\]/g, '\\$&')}\\b`);
43
+ if (tagRe.test(block))
44
+ present.add(tag);
45
+ }
46
+ const missing = [...required].filter((t) => !present.has(t));
47
+ if (missing.length > 0) {
48
+ const line = text.slice(0, m.index).split('\n').length;
49
+ result.passed = false;
50
+ result.errors.push({ file, line, missing });
51
+ }
52
+ }
53
+ }
54
+ return result;
55
+ }
56
+ //# sourceMappingURL=lint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lint.js","sourceRoot":"","sources":["../src/lint.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import type { RiskTier, ToolClassification, ThresholdPolicyDecision } from './types';
2
+ export declare function clampTtlForRiskTier(requestedTtlSec: number, risk: RiskTier): number;
3
+ export declare function requiredCaveatsForRiskTier(risk: RiskTier): string[];
4
+ export declare const RISK_TIER_REQUIREMENTS: Record<RiskTier, ThresholdPolicyDecision>;
5
+ /**
6
+ * Map a tool classification to the spec-207 threshold-policy decision.
7
+ *
8
+ * Defaults to `low` (T1 Read) when `@sa-risk-tier` is unset, matching
9
+ * the existing `evaluatePolicy` permissive default for unclassified
10
+ * tools. Callers should run `lintClassification` to catch unset risk
11
+ * tiers as a separate concern; this function intentionally doesn't
12
+ * fail closed on missing classification (that's `evaluatePolicy`'s
13
+ * job).
14
+ */
15
+ export declare function evaluateThresholdPolicy(classification: ToolClassification): ThresholdPolicyDecision;
16
+ //# sourceMappingURL=risk-tier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"risk-tier.d.ts","sourceRoot":"","sources":["../src/risk-tier.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,QAAQ,EACR,kBAAkB,EAClB,uBAAuB,EACxB,MAAM,SAAS,CAAC;AAiBjB,wBAAgB,mBAAmB,CAAC,eAAe,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,MAAM,CAMnF;AAED,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,EAAE,CAEnE;AAqBD,eAAO,MAAM,sBAAsB,EAAE,MAAM,CAAC,QAAQ,EAAE,uBAAuB,CAyB5E,CAAC;AAEF;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CACrC,cAAc,EAAE,kBAAkB,GACjC,uBAAuB,CAGzB"}
@@ -0,0 +1,84 @@
1
+ // Risk-tier helpers. The taxonomy is the binding contract — these helpers
2
+ // surface "what should issuance demand for this tier."
3
+ import { ThresholdTier } from './types';
4
+ const TTL_CLAMP_SECONDS = {
5
+ low: 7 * 24 * 60 * 60, // 7 days
6
+ medium: 7 * 24 * 60 * 60, // 7 days
7
+ high: 24 * 60 * 60, // 1 day
8
+ critical: 60 * 60, // 1 hour
9
+ };
10
+ const REQUIRED_CAVEATS = {
11
+ low: ['timestamp'],
12
+ medium: ['timestamp', 'mcp-tool-scope'],
13
+ high: ['timestamp', 'mcp-tool-scope', 'value', 'data-scope'],
14
+ critical: ['timestamp', 'exact-call'],
15
+ };
16
+ export function clampTtlForRiskTier(requestedTtlSec, risk) {
17
+ if (!Number.isFinite(requestedTtlSec) || requestedTtlSec <= 0) {
18
+ throw new Error('clampTtlForRiskTier: requestedTtlSec must be a positive number');
19
+ }
20
+ const max = TTL_CLAMP_SECONDS[risk];
21
+ return Math.min(requestedTtlSec, max);
22
+ }
23
+ export function requiredCaveatsForRiskTier(risk) {
24
+ return [...REQUIRED_CAVEATS[risk]];
25
+ }
26
+ // ─── Spec 207 threshold-policy mapping ────────────────────────────────
27
+ //
28
+ // `@sa-risk-tier` (string) → `ThresholdPolicyDecision` (the shape
29
+ // `mcp-runtime.withDelegation` consumes when gating delegation
30
+ // verification). T1-T3 are tool-call tiers (the only tiers
31
+ // `evaluateThresholdPolicy` returns); T4-T6 are account-level admin
32
+ // tiers and live entirely in `agent-account`.
33
+ //
34
+ // V0 calibration:
35
+ // - low → T1 Read: 1-of-N, no UV gate, no on-chain blessing.
36
+ // - medium → T2 Write: 1-of-N + passkey UV (hybrid mode).
37
+ // - high → T3 Value: quorum caveat required + passkey UV.
38
+ // - critical → T3 Value + on-chain `acceptSessionDelegation` blessing.
39
+ //
40
+ // Future refinements: argument-level caveats (spec 208) will let
41
+ // individual tool arguments shift the tier (e.g. `target` in a known
42
+ // allowlist drops the on-chain blessing requirement). Until then this
43
+ // flat mapping is the single source of truth.
44
+ export const RISK_TIER_REQUIREMENTS = {
45
+ low: {
46
+ tier: ThresholdTier.Read,
47
+ requiresQuorum: false,
48
+ requiresUv: false,
49
+ requiresAcceptedOnChain: false,
50
+ },
51
+ medium: {
52
+ tier: ThresholdTier.Write,
53
+ requiresQuorum: false,
54
+ requiresUv: true,
55
+ requiresAcceptedOnChain: false,
56
+ },
57
+ high: {
58
+ tier: ThresholdTier.Value,
59
+ requiresQuorum: true,
60
+ requiresUv: true,
61
+ requiresAcceptedOnChain: false,
62
+ },
63
+ critical: {
64
+ tier: ThresholdTier.Value,
65
+ requiresQuorum: true,
66
+ requiresUv: true,
67
+ requiresAcceptedOnChain: true,
68
+ },
69
+ };
70
+ /**
71
+ * Map a tool classification to the spec-207 threshold-policy decision.
72
+ *
73
+ * Defaults to `low` (T1 Read) when `@sa-risk-tier` is unset, matching
74
+ * the existing `evaluatePolicy` permissive default for unclassified
75
+ * tools. Callers should run `lintClassification` to catch unset risk
76
+ * tiers as a separate concern; this function intentionally doesn't
77
+ * fail closed on missing classification (that's `evaluatePolicy`'s
78
+ * job).
79
+ */
80
+ export function evaluateThresholdPolicy(classification) {
81
+ const risk = classification['@sa-risk-tier'] ?? 'low';
82
+ return RISK_TIER_REQUIREMENTS[risk];
83
+ }
84
+ //# sourceMappingURL=risk-tier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"risk-tier.js","sourceRoot":"","sources":["../src/risk-tier.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,uDAAuD;AAOvD,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAExC,MAAM,iBAAiB,GAA6B;IAClD,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAS,SAAS;IACvC,MAAM,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,EAAM,SAAS;IACvC,IAAI,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,EAAY,QAAQ;IACtC,QAAQ,EAAE,EAAE,GAAG,EAAE,EAAa,SAAS;CACxC,CAAC;AAEF,MAAM,gBAAgB,GAA+B;IACnD,GAAG,EAAE,CAAC,WAAW,CAAC;IAClB,MAAM,EAAE,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC,IAAI,EAAE,CAAC,WAAW,EAAE,gBAAgB,EAAE,OAAO,EAAE,YAAY,CAAC;IAC5D,QAAQ,EAAE,CAAC,WAAW,EAAE,YAAY,CAAC;CACtC,CAAC;AAEF,MAAM,UAAU,mBAAmB,CAAC,eAAuB,EAAE,IAAc;IACzE,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,eAAe,IAAI,CAAC,EAAE,CAAC;QAC9D,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAC;IACpF,CAAC;IACD,MAAM,GAAG,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACpC,OAAO,IAAI,CAAC,GAAG,CAAC,eAAe,EAAE,GAAG,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,IAAc;IACvD,OAAO,CAAC,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC;AACrC,CAAC;AAED,yEAAyE;AACzE,EAAE;AACF,kEAAkE;AAClE,+DAA+D;AAC/D,2DAA2D;AAC3D,oEAAoE;AACpE,8CAA8C;AAC9C,EAAE;AACF,kBAAkB;AAClB,+DAA+D;AAC/D,4DAA4D;AAC5D,4DAA4D;AAC5D,yEAAyE;AACzE,EAAE;AACF,iEAAiE;AACjE,qEAAqE;AACrE,sEAAsE;AACtE,8CAA8C;AAE9C,MAAM,CAAC,MAAM,sBAAsB,GAA8C;IAC/E,GAAG,EAAE;QACH,IAAI,EAAE,aAAa,CAAC,IAAI;QACxB,cAAc,EAAE,KAAK;QACrB,UAAU,EAAE,KAAK;QACjB,uBAAuB,EAAE,KAAK;KAC/B;IACD,MAAM,EAAE;QACN,IAAI,EAAE,aAAa,CAAC,KAAK;QACzB,cAAc,EAAE,KAAK;QACrB,UAAU,EAAE,IAAI;QAChB,uBAAuB,EAAE,KAAK;KAC/B;IACD,IAAI,EAAE;QACJ,IAAI,EAAE,aAAa,CAAC,KAAK;QACzB,cAAc,EAAE,IAAI;QACpB,UAAU,EAAE,IAAI;QAChB,uBAAuB,EAAE,KAAK;KAC/B;IACD,QAAQ,EAAE;QACR,IAAI,EAAE,aAAa,CAAC,KAAK;QACzB,cAAc,EAAE,IAAI;QACpB,UAAU,EAAE,IAAI;QAChB,uBAAuB,EAAE,IAAI;KAC9B;CACF,CAAC;AAEF;;;;;;;;;GASG;AACH,MAAM,UAAU,uBAAuB,CACrC,cAAkC;IAElC,MAAM,IAAI,GAAG,cAAc,CAAC,eAAe,CAAC,IAAI,KAAK,CAAC;IACtD,OAAO,sBAAsB,CAAC,IAAI,CAAC,CAAC;AACtC,CAAC"}
@@ -0,0 +1,95 @@
1
+ import type { Address, Hex } from '@agenticprimitives/types';
2
+ export type { Address, Hex };
3
+ export type RiskTier = 'low' | 'medium' | 'high' | 'critical';
4
+ export interface ToolClassification {
5
+ '@sa-tool': 'delegation-verified' | 'service-only' | 'bootstrap' | 'dev-only';
6
+ '@sa-auth': 'session-token' | 'service-hmac' | 'none' | 'none-with-csrf';
7
+ '@sa-validation'?: 'shape-check' | 'json-schema' | 'none-no-body' | 'none-path-params' | 'wallet-action-canonical';
8
+ '@sa-risk-tier'?: RiskTier;
9
+ '@sa-owner'?: string;
10
+ '@sa-rate-limit'?: string;
11
+ '@sa-prod-gate'?: 'enabled' | 'disabled';
12
+ }
13
+ export interface ExactCallPolicy {
14
+ target: Address;
15
+ selector: Hex;
16
+ calldataHash?: Hex;
17
+ valueLimit?: bigint;
18
+ }
19
+ export interface CaveatLike {
20
+ enforcer: Address;
21
+ terms: Hex;
22
+ }
23
+ export interface DelegationLike {
24
+ delegator: Address;
25
+ delegate: Address;
26
+ caveats: CaveatLike[];
27
+ }
28
+ export interface CaveatContext {
29
+ timestamp: number;
30
+ mcpTool?: string;
31
+ target?: Address;
32
+ selector?: Hex;
33
+ value?: bigint;
34
+ principal?: Address;
35
+ }
36
+ export interface PolicyContext {
37
+ toolName: string;
38
+ classification: ToolClassification;
39
+ delegation?: DelegationLike;
40
+ caveatContext?: CaveatContext;
41
+ callerKind: 'user-session' | 'agent-session' | 'service';
42
+ callDetails?: {
43
+ to: Address;
44
+ data: Hex;
45
+ value: bigint;
46
+ };
47
+ }
48
+ export type PolicyDecision = {
49
+ decision: 'allow';
50
+ } | {
51
+ decision: 'deny';
52
+ reason: string;
53
+ } | {
54
+ decision: 'requires-consent';
55
+ promptId: string;
56
+ risk: RiskTier;
57
+ };
58
+ export interface LintResult {
59
+ passed: boolean;
60
+ errors: Array<{
61
+ file: string;
62
+ line: number;
63
+ missing: string[];
64
+ }>;
65
+ }
66
+ export declare enum ThresholdTier {
67
+ Read = 1,// T1 — view-shape MCP delegation, no on-chain mutation
68
+ Write = 2,// T2 — mutates state but no value transfer above T3 ceiling
69
+ Value = 3,// T3 — token / native value transfer; requires quorum + (high-value) on-chain blessing
70
+ Admin = 4,// T4 — owner / passkey / guardian / mode mutation; account-level (not exposed by evaluateThresholdPolicy)
71
+ Critical = 5,// T5 — impl upgrade / DM change / paymaster change; account-level + timelock
72
+ Recovery = 6
73
+ }
74
+ /**
75
+ * Decision returned by `evaluateThresholdPolicy(classification)` —
76
+ * the shape mcp-runtime's withDelegation threads into
77
+ * `verifyDelegationToken` so the verify path knows what to enforce.
78
+ *
79
+ * - `tier`: which spec 207 § 5 tier the tool falls into. T1-T3 only;
80
+ * T4-T6 are account-level admin tiers, not tool-call tiers.
81
+ * - `requiresQuorum`: the delegation MUST carry a QuorumEnforcer caveat.
82
+ * True for T3+ (per spec § 5 multisig/org rows).
83
+ * - `requiresUv`: at least one signer must have presented a passkey UV
84
+ * (user-verification) flag. True for T2+ (per spec § 5 hybrid rows).
85
+ * - `requiresAcceptedOnChain`: the account must have called
86
+ * `acceptSessionDelegation(hash)` for this delegation. True for the
87
+ * high-value T3 path (per spec § 6 + § 5 critical-risk-tier).
88
+ */
89
+ export interface ThresholdPolicyDecision {
90
+ tier: ThresholdTier;
91
+ requiresQuorum: boolean;
92
+ requiresUv: boolean;
93
+ requiresAcceptedOnChain: boolean;
94
+ }
95
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,18 @@
1
+ // ─── Spec 207 threshold-policy tiers ──────────────────────────────────
2
+ //
3
+ // Coexists with the string `RiskTier` above. `RiskTier` is a *tool
4
+ // classification* concept (how risky is this tool?); `ThresholdTier` is
5
+ // the *delegation-time gating* tier from spec 207 § 5 (what kind of
6
+ // authorization does this tier demand?). The two map cleanly via
7
+ // `RISK_TIER_REQUIREMENTS` below, but they serve different layers and
8
+ // stay distinct in the type system.
9
+ export var ThresholdTier;
10
+ (function (ThresholdTier) {
11
+ ThresholdTier[ThresholdTier["Read"] = 1] = "Read";
12
+ ThresholdTier[ThresholdTier["Write"] = 2] = "Write";
13
+ ThresholdTier[ThresholdTier["Value"] = 3] = "Value";
14
+ ThresholdTier[ThresholdTier["Admin"] = 4] = "Admin";
15
+ ThresholdTier[ThresholdTier["Critical"] = 5] = "Critical";
16
+ ThresholdTier[ThresholdTier["Recovery"] = 6] = "Recovery";
17
+ })(ThresholdTier || (ThresholdTier = {}));
18
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AA+DA,yEAAyE;AACzE,EAAE;AACF,mEAAmE;AACnE,wEAAwE;AACxE,oEAAoE;AACpE,iEAAiE;AACjE,sEAAsE;AACtE,oCAAoC;AAEpC,MAAM,CAAN,IAAY,aAOX;AAPD,WAAY,aAAa;IACvB,iDAAQ,CAAA;IACR,mDAAS,CAAA;IACT,mDAAS,CAAA;IACT,mDAAS,CAAA;IACT,yDAAY,CAAA;IACZ,yDAAY,CAAA;AACd,CAAC,EAPW,aAAa,KAAb,aAAa,QAOxB"}
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@agenticprimitives/tool-policy",
3
+ "version": "0.1.0-alpha.2",
4
+ "description": "Protocol-agnostic classification, risk tiers, and exact-call policy. Consumable by MCP, A2A, LangGraph, and Vercel AI tool runtimes.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/agentictrustlabs/agenticprimitives.git",
9
+ "directory": "packages/tool-policy"
10
+ },
11
+ "homepage": "https://github.com/agentictrustlabs/agenticprimitives/tree/master/packages/tool-policy",
12
+ "bugs": {
13
+ "url": "https://github.com/agentictrustlabs/agenticprimitives/issues"
14
+ },
15
+ "type": "module",
16
+ "main": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ },
23
+ "./lint": {
24
+ "types": "./dist/lint.d.ts",
25
+ "import": "./dist/lint.js"
26
+ }
27
+ },
28
+ "files": [
29
+ "LICENSE",
30
+ "dist",
31
+ "spec.md",
32
+ "README.md"
33
+ ],
34
+ "scripts": {
35
+ "build": "tsc -p tsconfig.build.json",
36
+ "typecheck": "tsc -p tsconfig.json --noEmit",
37
+ "test": "vitest run",
38
+ "test:unit": "vitest run test/unit",
39
+ "test:integration": "vitest run test/integration --passWithNoTests",
40
+ "test:watch": "vitest",
41
+ "clean": "rm -rf dist"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "dependencies": {
47
+ "@noble/hashes": "^1.5.0"
48
+ },
49
+ "peerDependencies": {
50
+ "@agenticprimitives/types": "workspace:*"
51
+ },
52
+ "devDependencies": {
53
+ "@types/node": "^22.7.0",
54
+ "vitest": "^2.1.0"
55
+ },
56
+ "keywords": [
57
+ "policy",
58
+ "classification",
59
+ "risk-tier",
60
+ "exact-call",
61
+ "agentic",
62
+ "tool-policy"
63
+ ]
64
+ }
package/spec.md ADDED
@@ -0,0 +1,6 @@
1
+ # @agenticprimitives/tool-policy — spec
2
+
3
+ The authoritative specification lives at:
4
+ **[`../../specs/204-tool-policy.md`](../../specs/204-tool-policy.md)**
5
+
6
+ Do not edit a divergent copy here.