agentcheck-sdk 1.0.0 → 2.0.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.
- package/dist/cache.d.ts +78 -0
- package/dist/cache.js +155 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +24 -1
- package/dist/integrations/autogen.d.ts +50 -0
- package/dist/integrations/autogen.js +93 -0
- package/dist/integrations/crewai.d.ts +49 -0
- package/dist/integrations/crewai.js +107 -0
- package/dist/integrations/index.d.ts +8 -0
- package/dist/integrations/index.js +17 -0
- package/dist/integrations/langchain.d.ts +63 -0
- package/dist/integrations/langchain.js +104 -0
- package/dist/pipeline.d.ts +24 -0
- package/dist/pipeline.js +84 -48
- package/dist/pqc/dsse.d.ts +47 -0
- package/dist/pqc/dsse.js +113 -0
- package/dist/pqc/index.d.ts +11 -0
- package/dist/pqc/index.js +18 -0
- package/dist/pqc/signer.d.ts +44 -0
- package/dist/pqc/signer.js +97 -0
- package/dist/pqc/verifier.d.ts +49 -0
- package/dist/pqc/verifier.js +93 -0
- package/dist/router.d.ts +78 -0
- package/dist/router.js +102 -0
- package/dist/safety.d.ts +66 -7
- package/dist/safety.js +89 -3
- package/dist/scope-engine.js +2 -2
- package/dist/semantic.d.ts +14 -2
- package/dist/semantic.js +22 -10
- package/dist/templates.d.ts +130 -1
- package/dist/templates.js +275 -12
- package/dist/trust.d.ts +97 -0
- package/dist/trust.js +146 -0
- package/package.json +2 -2
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* PQC signer for DSSE envelopes.
|
|
4
|
+
*
|
|
5
|
+
* Signs audit records with HMAC-SHA256 locally via Web Crypto API.
|
|
6
|
+
* ML-DSA-87 signing is delegated to the fides-rs server so that
|
|
7
|
+
* private keys never exist in JavaScript memory.
|
|
8
|
+
*
|
|
9
|
+
* Tier: Atom (be-atom-agentcheck-pqc-signer-ts) - single responsibility: signing.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.PqcSigner = void 0;
|
|
13
|
+
const dsse_1 = require("./dsse");
|
|
14
|
+
class PqcSigner {
|
|
15
|
+
/**
|
|
16
|
+
* @param hmacKey Optional HMAC-SHA256 secret for local signing.
|
|
17
|
+
* @param serverUrl Optional fides-rs base URL for ML-DSA-87 server-side signing.
|
|
18
|
+
* @param apiKey Optional API key for authenticating with the signing server.
|
|
19
|
+
*/
|
|
20
|
+
constructor(opts = {}) {
|
|
21
|
+
this.hmacKey = opts.hmacKey;
|
|
22
|
+
this.serverUrl = opts.serverUrl;
|
|
23
|
+
this.apiKey = opts.apiKey;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Create a DSSE envelope with HMAC-SHA256 signature.
|
|
27
|
+
*
|
|
28
|
+
* If no hmacKey is configured, the envelope is returned unsigned.
|
|
29
|
+
*
|
|
30
|
+
* @param payload Object to sign and encode into the envelope.
|
|
31
|
+
* @param keyId Key identifier to embed in the signature.
|
|
32
|
+
* @returns Signed DsseEnvelope (or unsigned if no key).
|
|
33
|
+
*/
|
|
34
|
+
async signHmac(payload, keyId = "hmac:sha256") {
|
|
35
|
+
const envelope = dsse_1.DsseEnvelope.create(payload, dsse_1.DSSE_PAYLOAD_TYPE);
|
|
36
|
+
if (!this.hmacKey) {
|
|
37
|
+
console.warn("signHmac called without hmacKey - envelope will be unsigned");
|
|
38
|
+
return envelope;
|
|
39
|
+
}
|
|
40
|
+
const pae = envelope.pae();
|
|
41
|
+
const keyBytes = new TextEncoder().encode(this.hmacKey);
|
|
42
|
+
const cryptoKey = await crypto.subtle.importKey("raw", keyBytes, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
43
|
+
const sigRaw = await crypto.subtle.sign("HMAC", cryptoKey, new Uint8Array(pae));
|
|
44
|
+
const sigB64 = base64Encode(new Uint8Array(sigRaw));
|
|
45
|
+
const sig = new dsse_1.DsseSignature(keyId, sigB64);
|
|
46
|
+
return envelope.withSignature(sig);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Request ML-DSA-87 signing from the fides-rs server.
|
|
50
|
+
*
|
|
51
|
+
* Returns null if the server is unavailable or not configured.
|
|
52
|
+
*
|
|
53
|
+
* @param payload Object to sign with ML-DSA-87 on the server.
|
|
54
|
+
* @returns Signed DsseEnvelope, or null if server signing is unavailable.
|
|
55
|
+
*/
|
|
56
|
+
async signPqc(payload) {
|
|
57
|
+
if (!this.serverUrl) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const envelope = dsse_1.DsseEnvelope.create(payload, dsse_1.DSSE_PAYLOAD_TYPE);
|
|
62
|
+
const headers = {
|
|
63
|
+
"Content-Type": "application/json",
|
|
64
|
+
};
|
|
65
|
+
if (this.apiKey) {
|
|
66
|
+
headers["X-API-Key"] = this.apiKey;
|
|
67
|
+
}
|
|
68
|
+
const resp = await fetch(`${this.serverUrl}/api/v1/auth/sign`, {
|
|
69
|
+
method: "POST",
|
|
70
|
+
headers,
|
|
71
|
+
body: JSON.stringify({ envelope: envelope.toDict() }),
|
|
72
|
+
});
|
|
73
|
+
if (!resp.ok) {
|
|
74
|
+
console.warn(`PQC signing server returned HTTP ${resp.status}`);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const result = (await resp.json());
|
|
78
|
+
const envelopeData = result.envelope;
|
|
79
|
+
if (!envelopeData) {
|
|
80
|
+
console.warn("PQC signing server returned no envelope");
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
return dsse_1.DsseEnvelope.fromDict(envelopeData);
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
console.warn("PQC server signing failed:", err);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
exports.PqcSigner = PqcSigner;
|
|
92
|
+
function base64Encode(bytes) {
|
|
93
|
+
if (typeof Buffer !== "undefined") {
|
|
94
|
+
return Buffer.from(bytes).toString("base64");
|
|
95
|
+
}
|
|
96
|
+
return btoa(String.fromCharCode(...bytes));
|
|
97
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PQC signature verifier for DSSE envelopes.
|
|
3
|
+
*
|
|
4
|
+
* Verifies HMAC-SHA256 signatures locally via Web Crypto API.
|
|
5
|
+
* ML-DSA-87 verification is delegated to the fides-rs server since
|
|
6
|
+
* no stable JS binding for ML-DSA-87 is currently available.
|
|
7
|
+
*
|
|
8
|
+
* Tier: Atom (be-atom-agentcheck-pqc-verifier-ts) - single responsibility: verification.
|
|
9
|
+
*/
|
|
10
|
+
import type { AgentCheckClient } from "../client";
|
|
11
|
+
import { DsseEnvelope } from "./dsse";
|
|
12
|
+
export declare class PqcVerifier {
|
|
13
|
+
private readonly hmacKey;
|
|
14
|
+
private readonly publicKey;
|
|
15
|
+
/**
|
|
16
|
+
* @param hmacKey Optional HMAC-SHA256 secret for local verification.
|
|
17
|
+
* @param publicKey Optional ML-DSA-87 public key bytes (reserved for future use).
|
|
18
|
+
*/
|
|
19
|
+
constructor(opts?: {
|
|
20
|
+
hmacKey?: string;
|
|
21
|
+
publicKey?: Uint8Array;
|
|
22
|
+
});
|
|
23
|
+
/**
|
|
24
|
+
* Verify all signatures on the envelope.
|
|
25
|
+
*
|
|
26
|
+
* Attempts HMAC-SHA256 verification locally for signatures whose keyid
|
|
27
|
+
* starts with 'hmac:'. ML-DSA-87 signatures require server-side verification.
|
|
28
|
+
*
|
|
29
|
+
* @returns true if at least one signature verified, false if all failed,
|
|
30
|
+
* null if verification could not be performed (no key configured).
|
|
31
|
+
*/
|
|
32
|
+
verify(envelope: DsseEnvelope): Promise<boolean | null>;
|
|
33
|
+
/**
|
|
34
|
+
* Verify HMAC-SHA256 signature using Web Crypto API.
|
|
35
|
+
*
|
|
36
|
+
* @param pae Pre-Authentication Encoding bytes.
|
|
37
|
+
* @param sigB64 Base64-encoded expected HMAC digest.
|
|
38
|
+
* @returns true if signature matches.
|
|
39
|
+
*/
|
|
40
|
+
verifyHmac(pae: Uint8Array, sigB64: string): Promise<boolean>;
|
|
41
|
+
/**
|
|
42
|
+
* Delegate verification to the fides-rs server.
|
|
43
|
+
*
|
|
44
|
+
* @param client AgentCheckClient connected to the fides-rs server.
|
|
45
|
+
* @param envelope DSSE envelope to verify.
|
|
46
|
+
* @returns true if server confirms the signature is valid.
|
|
47
|
+
*/
|
|
48
|
+
verifyViaServer(client: AgentCheckClient, envelope: DsseEnvelope): Promise<boolean>;
|
|
49
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* PQC signature verifier for DSSE envelopes.
|
|
4
|
+
*
|
|
5
|
+
* Verifies HMAC-SHA256 signatures locally via Web Crypto API.
|
|
6
|
+
* ML-DSA-87 verification is delegated to the fides-rs server since
|
|
7
|
+
* no stable JS binding for ML-DSA-87 is currently available.
|
|
8
|
+
*
|
|
9
|
+
* Tier: Atom (be-atom-agentcheck-pqc-verifier-ts) - single responsibility: verification.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.PqcVerifier = void 0;
|
|
13
|
+
class PqcVerifier {
|
|
14
|
+
/**
|
|
15
|
+
* @param hmacKey Optional HMAC-SHA256 secret for local verification.
|
|
16
|
+
* @param publicKey Optional ML-DSA-87 public key bytes (reserved for future use).
|
|
17
|
+
*/
|
|
18
|
+
constructor(opts = {}) {
|
|
19
|
+
this.hmacKey = opts.hmacKey;
|
|
20
|
+
this.publicKey = opts.publicKey;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Verify all signatures on the envelope.
|
|
24
|
+
*
|
|
25
|
+
* Attempts HMAC-SHA256 verification locally for signatures whose keyid
|
|
26
|
+
* starts with 'hmac:'. ML-DSA-87 signatures require server-side verification.
|
|
27
|
+
*
|
|
28
|
+
* @returns true if at least one signature verified, false if all failed,
|
|
29
|
+
* null if verification could not be performed (no key configured).
|
|
30
|
+
*/
|
|
31
|
+
async verify(envelope) {
|
|
32
|
+
if (envelope.signatures.length === 0) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
const pae = envelope.pae();
|
|
36
|
+
let checkedAny = false;
|
|
37
|
+
let verifiedAny = false;
|
|
38
|
+
for (const sig of envelope.signatures) {
|
|
39
|
+
if (sig.keyid.startsWith("hmac:") && this.hmacKey) {
|
|
40
|
+
checkedAny = true;
|
|
41
|
+
const ok = await this.verifyHmac(pae, sig.sig);
|
|
42
|
+
if (ok)
|
|
43
|
+
verifiedAny = true;
|
|
44
|
+
}
|
|
45
|
+
else if (sig.keyid.startsWith("ML-DSA-87:") || sig.keyid.startsWith("mldsa:")) {
|
|
46
|
+
// Cannot verify ML-DSA-87 locally in JS - caller should use verifyViaServer
|
|
47
|
+
checkedAny = false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (!checkedAny)
|
|
51
|
+
return null;
|
|
52
|
+
return verifiedAny;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Verify HMAC-SHA256 signature using Web Crypto API.
|
|
56
|
+
*
|
|
57
|
+
* @param pae Pre-Authentication Encoding bytes.
|
|
58
|
+
* @param sigB64 Base64-encoded expected HMAC digest.
|
|
59
|
+
* @returns true if signature matches.
|
|
60
|
+
*/
|
|
61
|
+
async verifyHmac(pae, sigB64) {
|
|
62
|
+
if (!this.hmacKey)
|
|
63
|
+
return false;
|
|
64
|
+
const keyBytes = new TextEncoder().encode(this.hmacKey);
|
|
65
|
+
const cryptoKey = await crypto.subtle.importKey("raw", keyBytes, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
66
|
+
const expectedRaw = await crypto.subtle.sign("HMAC", cryptoKey, new Uint8Array(pae));
|
|
67
|
+
const expectedB64 = base64Encode(new Uint8Array(expectedRaw));
|
|
68
|
+
return expectedB64 === sigB64;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Delegate verification to the fides-rs server.
|
|
72
|
+
*
|
|
73
|
+
* @param client AgentCheckClient connected to the fides-rs server.
|
|
74
|
+
* @param envelope DSSE envelope to verify.
|
|
75
|
+
* @returns true if server confirms the signature is valid.
|
|
76
|
+
*/
|
|
77
|
+
async verifyViaServer(client, envelope) {
|
|
78
|
+
try {
|
|
79
|
+
const result = await client.request("POST", "/api/v1/auth/verify", { envelope: envelope.toDict() });
|
|
80
|
+
return result.verified === true;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
exports.PqcVerifier = PqcVerifier;
|
|
88
|
+
function base64Encode(bytes) {
|
|
89
|
+
if (typeof Buffer !== "undefined") {
|
|
90
|
+
return Buffer.from(bytes).toString("base64");
|
|
91
|
+
}
|
|
92
|
+
return btoa(String.fromCharCode(...bytes));
|
|
93
|
+
}
|
package/dist/router.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Layer Router - Dynamic safety layer selection based on trust and context.
|
|
3
|
+
*
|
|
4
|
+
* Tier: Atom (single responsibility: decide which safety layers to execute)
|
|
5
|
+
*
|
|
6
|
+
* Maps an agent's trust tier to an ordered list of layer names. The
|
|
7
|
+
* SafetyStack and VerificationPipeline read this list to skip unnecessary
|
|
8
|
+
* layers for high-trust agents while applying all layers to low-trust or
|
|
9
|
+
* risky actions.
|
|
10
|
+
*
|
|
11
|
+
* Default routing table:
|
|
12
|
+
* high: scope_engine, budget_tracker
|
|
13
|
+
* medium: scope_engine, semantic_verifier, budget_tracker, pattern_monitor
|
|
14
|
+
* low: scope_engine, semantic_verifier, budget_tracker,
|
|
15
|
+
* pattern_monitor, human_escalation
|
|
16
|
+
*
|
|
17
|
+
* Context overrides:
|
|
18
|
+
* - riskLevel == "critical": human_escalation is always appended.
|
|
19
|
+
* - amount > pqcAmountThreshold: "pqc_signing" flag is appended.
|
|
20
|
+
*/
|
|
21
|
+
import { TrustScore } from "./trust";
|
|
22
|
+
/** Routing rules: tier label -> ordered list of layer name strings. */
|
|
23
|
+
export type RoutingRules = Partial<Record<"high" | "medium" | "low", string[]>>;
|
|
24
|
+
/** Options for LayerRouter constructor. */
|
|
25
|
+
export interface LayerRouterConfig {
|
|
26
|
+
/** Override default tier->layers mapping. */
|
|
27
|
+
rules?: RoutingRules;
|
|
28
|
+
/** Monetary threshold above which PQC signing is required. Default: 10000. */
|
|
29
|
+
pqcAmountThreshold?: number;
|
|
30
|
+
}
|
|
31
|
+
export declare class LayerRouter {
|
|
32
|
+
/**
|
|
33
|
+
* Determine which safety layers to run based on trust and action context.
|
|
34
|
+
*
|
|
35
|
+
* Usage:
|
|
36
|
+
* const router = new LayerRouter();
|
|
37
|
+
* const layers = router.route(trustScore, "order_parts", { amount: 5000 });
|
|
38
|
+
* // ["scope_engine", "budget_tracker", "pattern_monitor"]
|
|
39
|
+
*/
|
|
40
|
+
/** Default routing rules per trust tier. */
|
|
41
|
+
static readonly DEFAULT_RULES: Record<string, string[]>;
|
|
42
|
+
private rules;
|
|
43
|
+
private pqcThreshold;
|
|
44
|
+
constructor(config?: LayerRouterConfig);
|
|
45
|
+
/**
|
|
46
|
+
* Return the ordered list of layer names to execute.
|
|
47
|
+
*
|
|
48
|
+
* Resolution order:
|
|
49
|
+
* 1. Base layers from trust tier.
|
|
50
|
+
* 2. If riskLevel == "critical", ensure "human_escalation" is included.
|
|
51
|
+
* 3. If amount > pqcThreshold, append "pqc_signing".
|
|
52
|
+
*
|
|
53
|
+
* @param trust - TrustScore for the acting agent.
|
|
54
|
+
* @param action - The action being attempted (reserved for future per-action rules).
|
|
55
|
+
* @param opts.amount - Optional monetary amount of the action.
|
|
56
|
+
* @param opts.riskLevel - Optional contextual risk label. "critical" forces
|
|
57
|
+
* human escalation regardless of trust tier.
|
|
58
|
+
* @returns Ordered array of layer name strings to execute.
|
|
59
|
+
*/
|
|
60
|
+
route(trust: TrustScore, action: string, opts?: {
|
|
61
|
+
amount?: number;
|
|
62
|
+
riskLevel?: string;
|
|
63
|
+
}): string[];
|
|
64
|
+
/**
|
|
65
|
+
* Add or override the routing rule for a given tier.
|
|
66
|
+
*
|
|
67
|
+
* @param tier - One of "high", "medium", or "low".
|
|
68
|
+
* @param layers - Ordered list of layer names for this tier.
|
|
69
|
+
*/
|
|
70
|
+
addRule(tier: string, layers: string[]): void;
|
|
71
|
+
/**
|
|
72
|
+
* Check whether PQC signing is required based on the action amount.
|
|
73
|
+
*
|
|
74
|
+
* @param amount - Monetary value of the action. undefined or 0 returns false.
|
|
75
|
+
* @returns True if amount exceeds the configured PQC threshold.
|
|
76
|
+
*/
|
|
77
|
+
requiresPqc(amount?: number): boolean;
|
|
78
|
+
}
|
package/dist/router.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Layer Router - Dynamic safety layer selection based on trust and context.
|
|
4
|
+
*
|
|
5
|
+
* Tier: Atom (single responsibility: decide which safety layers to execute)
|
|
6
|
+
*
|
|
7
|
+
* Maps an agent's trust tier to an ordered list of layer names. The
|
|
8
|
+
* SafetyStack and VerificationPipeline read this list to skip unnecessary
|
|
9
|
+
* layers for high-trust agents while applying all layers to low-trust or
|
|
10
|
+
* risky actions.
|
|
11
|
+
*
|
|
12
|
+
* Default routing table:
|
|
13
|
+
* high: scope_engine, budget_tracker
|
|
14
|
+
* medium: scope_engine, semantic_verifier, budget_tracker, pattern_monitor
|
|
15
|
+
* low: scope_engine, semantic_verifier, budget_tracker,
|
|
16
|
+
* pattern_monitor, human_escalation
|
|
17
|
+
*
|
|
18
|
+
* Context overrides:
|
|
19
|
+
* - riskLevel == "critical": human_escalation is always appended.
|
|
20
|
+
* - amount > pqcAmountThreshold: "pqc_signing" flag is appended.
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.LayerRouter = void 0;
|
|
24
|
+
class LayerRouter {
|
|
25
|
+
constructor(config = {}) {
|
|
26
|
+
// Start from defaults then overlay caller-supplied rules
|
|
27
|
+
this.rules = { ...LayerRouter.DEFAULT_RULES };
|
|
28
|
+
if (config.rules) {
|
|
29
|
+
for (const [tier, layers] of Object.entries(config.rules)) {
|
|
30
|
+
this.rules[tier] = [...layers];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
this.pqcThreshold = config.pqcAmountThreshold ?? 10000.0;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Return the ordered list of layer names to execute.
|
|
37
|
+
*
|
|
38
|
+
* Resolution order:
|
|
39
|
+
* 1. Base layers from trust tier.
|
|
40
|
+
* 2. If riskLevel == "critical", ensure "human_escalation" is included.
|
|
41
|
+
* 3. If amount > pqcThreshold, append "pqc_signing".
|
|
42
|
+
*
|
|
43
|
+
* @param trust - TrustScore for the acting agent.
|
|
44
|
+
* @param action - The action being attempted (reserved for future per-action rules).
|
|
45
|
+
* @param opts.amount - Optional monetary amount of the action.
|
|
46
|
+
* @param opts.riskLevel - Optional contextual risk label. "critical" forces
|
|
47
|
+
* human escalation regardless of trust tier.
|
|
48
|
+
* @returns Ordered array of layer name strings to execute.
|
|
49
|
+
*/
|
|
50
|
+
route(trust, action, opts = {}) {
|
|
51
|
+
const tier = this.rules[trust.tier] ? trust.tier : "medium";
|
|
52
|
+
const layers = [...this.rules[tier]];
|
|
53
|
+
if (opts.riskLevel === "critical" && !layers.includes("human_escalation")) {
|
|
54
|
+
layers.push("human_escalation");
|
|
55
|
+
}
|
|
56
|
+
if (this.requiresPqc(opts.amount) && !layers.includes("pqc_signing")) {
|
|
57
|
+
layers.push("pqc_signing");
|
|
58
|
+
}
|
|
59
|
+
return layers;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Add or override the routing rule for a given tier.
|
|
63
|
+
*
|
|
64
|
+
* @param tier - One of "high", "medium", or "low".
|
|
65
|
+
* @param layers - Ordered list of layer names for this tier.
|
|
66
|
+
*/
|
|
67
|
+
addRule(tier, layers) {
|
|
68
|
+
this.rules[tier] = [...layers];
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Check whether PQC signing is required based on the action amount.
|
|
72
|
+
*
|
|
73
|
+
* @param amount - Monetary value of the action. undefined or 0 returns false.
|
|
74
|
+
* @returns True if amount exceeds the configured PQC threshold.
|
|
75
|
+
*/
|
|
76
|
+
requiresPqc(amount) {
|
|
77
|
+
if (amount === undefined)
|
|
78
|
+
return false;
|
|
79
|
+
return amount > this.pqcThreshold;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
exports.LayerRouter = LayerRouter;
|
|
83
|
+
/**
|
|
84
|
+
* Determine which safety layers to run based on trust and action context.
|
|
85
|
+
*
|
|
86
|
+
* Usage:
|
|
87
|
+
* const router = new LayerRouter();
|
|
88
|
+
* const layers = router.route(trustScore, "order_parts", { amount: 5000 });
|
|
89
|
+
* // ["scope_engine", "budget_tracker", "pattern_monitor"]
|
|
90
|
+
*/
|
|
91
|
+
/** Default routing rules per trust tier. */
|
|
92
|
+
LayerRouter.DEFAULT_RULES = {
|
|
93
|
+
high: ["scope_engine", "budget_tracker"],
|
|
94
|
+
medium: ["scope_engine", "semantic_verifier", "budget_tracker", "pattern_monitor"],
|
|
95
|
+
low: [
|
|
96
|
+
"scope_engine",
|
|
97
|
+
"semantic_verifier",
|
|
98
|
+
"budget_tracker",
|
|
99
|
+
"pattern_monitor",
|
|
100
|
+
"human_escalation",
|
|
101
|
+
],
|
|
102
|
+
};
|
package/dist/safety.d.ts
CHANGED
|
@@ -5,8 +5,15 @@
|
|
|
5
5
|
* Layer 2: BudgetTracker - "How much total?"
|
|
6
6
|
* Layer 3: PatternMonitor - "Is this normal?"
|
|
7
7
|
* Layer 4: HumanEscalation - "Should a human decide?"
|
|
8
|
+
*
|
|
9
|
+
* Phase 3 addition: SafetyStack accepts optional LayerRouter and TrustEngine
|
|
10
|
+
* parameters. When provided, the router determines which layers to run for
|
|
11
|
+
* each check call based on the agent's current trust score. When omitted the
|
|
12
|
+
* original fixed-order behaviour is preserved (backwards compatible).
|
|
8
13
|
*/
|
|
9
14
|
import { ScopeEngine, StructuredScope, VerificationResult } from "./scope-engine";
|
|
15
|
+
import { LayerRouter } from "./router";
|
|
16
|
+
import { TrustEngine } from "./trust";
|
|
10
17
|
export declare class BudgetTracker {
|
|
11
18
|
private dailyLimit?;
|
|
12
19
|
private monthlyLimit?;
|
|
@@ -53,17 +60,69 @@ export interface SafetyResult {
|
|
|
53
60
|
reason: string;
|
|
54
61
|
alerts: PatternAlert[];
|
|
55
62
|
}
|
|
63
|
+
export interface SafetyStackConfig {
|
|
64
|
+
scopeEngine?: ScopeEngine;
|
|
65
|
+
budget?: BudgetTracker;
|
|
66
|
+
pattern?: PatternMonitor;
|
|
67
|
+
escalation?: HumanEscalation;
|
|
68
|
+
/** Optional router for dynamic layer selection (Phase 3). */
|
|
69
|
+
router?: LayerRouter;
|
|
70
|
+
/** Optional trust engine for agent trust scores (Phase 3). */
|
|
71
|
+
trustEngine?: TrustEngine;
|
|
72
|
+
}
|
|
73
|
+
export interface CheckOpts {
|
|
74
|
+
amount?: number;
|
|
75
|
+
/** Agent identifier - required for dynamic routing. */
|
|
76
|
+
agentId?: string;
|
|
77
|
+
/** Risk label forwarded to the router (e.g. "critical"). */
|
|
78
|
+
riskLevel?: string;
|
|
79
|
+
}
|
|
56
80
|
export declare class SafetyStack {
|
|
81
|
+
/**
|
|
82
|
+
* Combine all safety layers into a single check.
|
|
83
|
+
*
|
|
84
|
+
* Basic usage (fixed order, all configured layers run):
|
|
85
|
+
* const stack = new SafetyStack({ budget, pattern, escalation });
|
|
86
|
+
* const result = stack.check(scope, "order_parts", { amount: 8000 });
|
|
87
|
+
*
|
|
88
|
+
* Phase 3 usage (dynamic routing based on agent trust):
|
|
89
|
+
* const stack = new SafetyStack({ budget, pattern, escalation, router, trustEngine });
|
|
90
|
+
* const result = stack.check(scope, "order_parts", { amount: 8000, agentId: "bot-001" });
|
|
91
|
+
* stack.recordOutcome("bot-001", result.allowed);
|
|
92
|
+
*/
|
|
57
93
|
private scopeEngine;
|
|
58
94
|
private budget?;
|
|
59
95
|
private pattern?;
|
|
60
96
|
private escalation?;
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
97
|
+
private router?;
|
|
98
|
+
private trustEngine?;
|
|
99
|
+
constructor(opts?: SafetyStackConfig);
|
|
100
|
+
/**
|
|
101
|
+
* Run safety layers and return a combined result.
|
|
102
|
+
*
|
|
103
|
+
* When a router and trustEngine are configured and agentId is provided,
|
|
104
|
+
* only the layers specified by the router for the agent's current trust
|
|
105
|
+
* tier are executed. Otherwise all configured layers run in fixed order.
|
|
106
|
+
*
|
|
107
|
+
* @param scope - Parsed scope object or free-text string.
|
|
108
|
+
* @param action - The action being attempted.
|
|
109
|
+
* @param opts.amount - Optional monetary amount.
|
|
110
|
+
* @param opts.agentId - Optional agent identifier. Required for dynamic routing.
|
|
111
|
+
* @param opts.riskLevel - Optional risk label forwarded to the router.
|
|
112
|
+
* @returns SafetyResult with allowed status, passed layers, and any alerts.
|
|
113
|
+
*/
|
|
114
|
+
check(scope: StructuredScope | string, action: string, opts?: CheckOpts | number): SafetyResult;
|
|
115
|
+
private checkFixed;
|
|
116
|
+
private checkDynamic;
|
|
117
|
+
/** Record execution for budget tracking and pattern monitoring. */
|
|
68
118
|
recordExecution(action: string, amount?: number): void;
|
|
119
|
+
/**
|
|
120
|
+
* Record an agent execution outcome to update its trust score.
|
|
121
|
+
*
|
|
122
|
+
* Delegates to the TrustEngine if one was configured. No-ops otherwise.
|
|
123
|
+
*
|
|
124
|
+
* @param agentId - The agent identifier.
|
|
125
|
+
* @param success - True if the execution succeeded, false otherwise.
|
|
126
|
+
*/
|
|
127
|
+
recordOutcome(agentId: string, success: boolean): void;
|
|
69
128
|
}
|
package/dist/safety.js
CHANGED
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
* Layer 2: BudgetTracker - "How much total?"
|
|
7
7
|
* Layer 3: PatternMonitor - "Is this normal?"
|
|
8
8
|
* Layer 4: HumanEscalation - "Should a human decide?"
|
|
9
|
+
*
|
|
10
|
+
* Phase 3 addition: SafetyStack accepts optional LayerRouter and TrustEngine
|
|
11
|
+
* parameters. When provided, the router determines which layers to run for
|
|
12
|
+
* each check call based on the agent's current trust score. When omitted the
|
|
13
|
+
* original fixed-order behaviour is preserved (backwards compatible).
|
|
9
14
|
*/
|
|
10
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
16
|
exports.SafetyStack = exports.HumanEscalation = exports.PatternMonitor = exports.BudgetTracker = void 0;
|
|
@@ -26,13 +31,13 @@ class BudgetTracker {
|
|
|
26
31
|
if (this.dailyCountLimit && (this.dailyCounts[dayKey] || 0) >= this.dailyCountLimit) {
|
|
27
32
|
return { allowed: false, reason: `Daily action count limit reached (${this.dailyCountLimit})` };
|
|
28
33
|
}
|
|
29
|
-
if (this.dailyLimit && amount > 0) {
|
|
34
|
+
if (this.dailyLimit !== undefined && amount > 0) {
|
|
30
35
|
const projected = (this.dailyTotals[dayKey] || 0) + amount;
|
|
31
36
|
if (projected > this.dailyLimit) {
|
|
32
37
|
return { allowed: false, reason: `Daily budget exceeded: ${projected} > ${this.dailyLimit}` };
|
|
33
38
|
}
|
|
34
39
|
}
|
|
35
|
-
if (this.monthlyLimit && amount > 0) {
|
|
40
|
+
if (this.monthlyLimit !== undefined && amount > 0) {
|
|
36
41
|
const projected = (this.monthlyTotals[monthKey] || 0) + amount;
|
|
37
42
|
if (projected > this.monthlyLimit) {
|
|
38
43
|
return { allowed: false, reason: `Monthly budget exceeded: ${projected} > ${this.monthlyLimit}` };
|
|
@@ -41,6 +46,8 @@ class BudgetTracker {
|
|
|
41
46
|
return { allowed: true, reason: "Within budget" };
|
|
42
47
|
}
|
|
43
48
|
recordUsage(action, amount = 0) {
|
|
49
|
+
if (amount < 0)
|
|
50
|
+
return; // Prevent budget gaming via negative amounts
|
|
44
51
|
const dayKey = new Date().toISOString().slice(0, 10);
|
|
45
52
|
const monthKey = dayKey.slice(0, 7);
|
|
46
53
|
this.dailyTotals[dayKey] = (this.dailyTotals[dayKey] || 0) + amount;
|
|
@@ -123,8 +130,34 @@ class SafetyStack {
|
|
|
123
130
|
this.budget = opts.budget;
|
|
124
131
|
this.pattern = opts.pattern;
|
|
125
132
|
this.escalation = opts.escalation;
|
|
133
|
+
this.router = opts.router;
|
|
134
|
+
this.trustEngine = opts.trustEngine;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Run safety layers and return a combined result.
|
|
138
|
+
*
|
|
139
|
+
* When a router and trustEngine are configured and agentId is provided,
|
|
140
|
+
* only the layers specified by the router for the agent's current trust
|
|
141
|
+
* tier are executed. Otherwise all configured layers run in fixed order.
|
|
142
|
+
*
|
|
143
|
+
* @param scope - Parsed scope object or free-text string.
|
|
144
|
+
* @param action - The action being attempted.
|
|
145
|
+
* @param opts.amount - Optional monetary amount.
|
|
146
|
+
* @param opts.agentId - Optional agent identifier. Required for dynamic routing.
|
|
147
|
+
* @param opts.riskLevel - Optional risk label forwarded to the router.
|
|
148
|
+
* @returns SafetyResult with allowed status, passed layers, and any alerts.
|
|
149
|
+
*/
|
|
150
|
+
check(scope, action, opts = {}) {
|
|
151
|
+
// Support legacy signature: check(scope, action, amount: number)
|
|
152
|
+
const amount = typeof opts === "number" ? opts : (opts.amount ?? 0);
|
|
153
|
+
const agentId = typeof opts === "object" ? opts.agentId : undefined;
|
|
154
|
+
const riskLevel = typeof opts === "object" ? opts.riskLevel : undefined;
|
|
155
|
+
if (this.router && this.trustEngine && agentId) {
|
|
156
|
+
return this.checkDynamic(scope, action, amount, agentId, riskLevel);
|
|
157
|
+
}
|
|
158
|
+
return this.checkFixed(scope, action, amount);
|
|
126
159
|
}
|
|
127
|
-
|
|
160
|
+
checkFixed(scope, action, amount) {
|
|
128
161
|
const passed = [];
|
|
129
162
|
const alerts = [];
|
|
130
163
|
const r1 = this.scopeEngine.verify(scope, action, { amount });
|
|
@@ -153,9 +186,62 @@ class SafetyStack {
|
|
|
153
186
|
}
|
|
154
187
|
return { allowed: true, passedLayers: passed, reason: "All safety layers passed", alerts };
|
|
155
188
|
}
|
|
189
|
+
checkDynamic(scope, action, amount, agentId, riskLevel) {
|
|
190
|
+
const trust = this.trustEngine.getScore(agentId);
|
|
191
|
+
const layerNames = this.router.route(trust, action, { amount, riskLevel });
|
|
192
|
+
const passed = [];
|
|
193
|
+
const alerts = [];
|
|
194
|
+
for (const name of layerNames) {
|
|
195
|
+
if (name === "scope_engine") {
|
|
196
|
+
const r = this.scopeEngine.verify(scope, action, { amount });
|
|
197
|
+
if (!r.allowed)
|
|
198
|
+
return { allowed: false, passedLayers: passed, failedLayer: name, reason: r.reason, alerts };
|
|
199
|
+
passed.push(name);
|
|
200
|
+
}
|
|
201
|
+
else if (name === "budget_tracker" && this.budget) {
|
|
202
|
+
const r = this.budget.check(action, amount);
|
|
203
|
+
if (!r.allowed)
|
|
204
|
+
return { allowed: false, passedLayers: passed, failedLayer: name, reason: r.reason, alerts };
|
|
205
|
+
passed.push(name);
|
|
206
|
+
}
|
|
207
|
+
else if (name === "pattern_monitor" && this.pattern) {
|
|
208
|
+
const pa = this.pattern.check(action, amount);
|
|
209
|
+
alerts.push(...pa);
|
|
210
|
+
const critical = pa.filter(a => a.level === "critical");
|
|
211
|
+
if (critical.length)
|
|
212
|
+
return { allowed: false, passedLayers: passed, failedLayer: name, reason: critical[0].message, alerts };
|
|
213
|
+
passed.push(name);
|
|
214
|
+
}
|
|
215
|
+
else if (name === "human_escalation" && this.escalation) {
|
|
216
|
+
const r = this.escalation.check(action, amount);
|
|
217
|
+
if (!r.allowed)
|
|
218
|
+
return { allowed: false, passedLayers: passed, failedLayer: name, reason: r.reason, alerts };
|
|
219
|
+
passed.push(name);
|
|
220
|
+
}
|
|
221
|
+
// "semantic_verifier" and "pqc_signing" are handled by VerificationPipeline
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
allowed: true,
|
|
225
|
+
passedLayers: passed,
|
|
226
|
+
reason: `All routed layers passed (tier=${trust.tier})`,
|
|
227
|
+
alerts,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/** Record execution for budget tracking and pattern monitoring. */
|
|
156
231
|
recordExecution(action, amount = 0) {
|
|
157
232
|
this.budget?.recordUsage(action, amount);
|
|
158
233
|
this.pattern?.record(action, amount);
|
|
159
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* Record an agent execution outcome to update its trust score.
|
|
237
|
+
*
|
|
238
|
+
* Delegates to the TrustEngine if one was configured. No-ops otherwise.
|
|
239
|
+
*
|
|
240
|
+
* @param agentId - The agent identifier.
|
|
241
|
+
* @param success - True if the execution succeeded, false otherwise.
|
|
242
|
+
*/
|
|
243
|
+
recordOutcome(agentId, success) {
|
|
244
|
+
this.trustEngine?.recordOutcome(agentId, success);
|
|
245
|
+
}
|
|
160
246
|
}
|
|
161
247
|
exports.SafetyStack = SafetyStack;
|
package/dist/scope-engine.js
CHANGED
|
@@ -34,8 +34,8 @@ class ScopeEngine {
|
|
|
34
34
|
if (scope.denied?.includes(action)) {
|
|
35
35
|
return { allowed: false, reason: `Action '${action}' is in denied list` };
|
|
36
36
|
}
|
|
37
|
-
// Allowed list
|
|
38
|
-
if (scope.allowed
|
|
37
|
+
// Allowed list (empty array = nothing allowed, undefined = no whitelist)
|
|
38
|
+
if (scope.allowed !== undefined && !scope.allowed.includes(action)) {
|
|
39
39
|
return { allowed: false, reason: `Action '${action}' not in allowed list: ${scope.allowed.join(", ")}` };
|
|
40
40
|
}
|
|
41
41
|
// Amount limits
|