@agentguard-run/spend 0.14.0 → 0.15.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/CHANGELOG.md +11 -0
- package/dist/cli/serve.d.ts +1 -0
- package/dist/cli/serve.d.ts.map +1 -1
- package/dist/cli/serve.js +8 -4
- package/dist/cli/serve.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/operating-pack.d.ts +88 -0
- package/dist/operating-pack.d.ts.map +1 -0
- package/dist/operating-pack.js +329 -0
- package/dist/operating-pack.js.map +1 -0
- package/dist/telemetry.js +1 -1
- package/dist/workflow/context.d.ts.map +1 -1
- package/dist/workflow/context.js +6 -3
- package/dist/workflow/context.js.map +1 -1
- package/dist/workflow/receipt.d.ts +22 -6
- package/dist/workflow/receipt.d.ts.map +1 -1
- package/dist/workflow/receipt.js +109 -22
- package/dist/workflow/receipt.js.map +1 -1
- package/dist/workflow/types.d.ts +7 -0
- package/dist/workflow/types.d.ts.map +1 -1
- package/package.json +8 -2
- package/src/operating-pack.ts +397 -0
- package/src/workflow/context.ts +6 -3
- package/src/workflow/receipt.ts +134 -30
- package/src/workflow/types.ts +7 -0
package/dist/workflow/receipt.js
CHANGED
|
@@ -11,6 +11,8 @@ exports.verifyReceipt = verifyReceipt;
|
|
|
11
11
|
exports.roundUsd = roundUsd;
|
|
12
12
|
const crypto_1 = require("crypto");
|
|
13
13
|
const decision_log_1 = require("../decision-log");
|
|
14
|
+
const ED25519_PKCS8_SEED_PREFIX = Buffer.from('302e020100300506032b657004220420', 'hex');
|
|
15
|
+
const ED25519_SPKI_PUBLIC_PREFIX = Buffer.from('302a300506032b6570032100', 'hex');
|
|
14
16
|
function canonicalJSONStringify(value) {
|
|
15
17
|
return (0, decision_log_1.canonicalJson)(value);
|
|
16
18
|
}
|
|
@@ -20,25 +22,26 @@ function sha256(input) {
|
|
|
20
22
|
function workflowReceiptId(prefix = 'ag_r') {
|
|
21
23
|
return `${prefix}_${(0, crypto_1.randomUUID)().replace(/-/g, '').slice(0, 16)}`;
|
|
22
24
|
}
|
|
23
|
-
function signReceiptPayload(payload) {
|
|
24
|
-
|
|
25
|
+
async function signReceiptPayload(payload, signingKeys) {
|
|
26
|
+
const resolved = resolveWorkflowSigningKey(signingKeys);
|
|
27
|
+
const canonical = (0, decision_log_1.canonicalJson)(payload);
|
|
28
|
+
return {
|
|
29
|
+
signature: (0, crypto_1.sign)(null, Buffer.from(canonical, 'utf8'), resolved.privateKey).toString('hex'),
|
|
30
|
+
public_key_fingerprint: resolved.publicKeyFingerprint,
|
|
31
|
+
public_key_hex: resolved.publicKeyHex,
|
|
32
|
+
};
|
|
25
33
|
}
|
|
26
|
-
function buildReceipt(args) {
|
|
34
|
+
async function buildReceipt(args) {
|
|
27
35
|
if (!Number.isFinite(args.cost_usd) || args.cost_usd < 0) {
|
|
28
36
|
throw new Error('Workflow receipt cost must be a finite non-negative number');
|
|
29
37
|
}
|
|
30
|
-
const publicKeyFingerprint = (0, crypto_1.createHash)('sha256')
|
|
31
|
-
.update(args.workflow_id || args.user_id || 'agentguard-workflow')
|
|
32
|
-
.digest('hex')
|
|
33
|
-
.slice(0, 16);
|
|
34
38
|
const unsigned = {
|
|
35
|
-
receipt_id: workflowReceiptId(args.is_checkpoint ? 'ag_cp' : 'ag_r'),
|
|
39
|
+
receipt_id: args.receipt_id ?? workflowReceiptId(args.is_checkpoint ? 'ag_cp' : 'ag_r'),
|
|
36
40
|
schema_version: 2,
|
|
37
41
|
outcome_name: args.outcome_name,
|
|
38
42
|
user_id: args.user_id,
|
|
39
43
|
cost_usd: roundUsd(args.cost_usd),
|
|
40
|
-
signed_at: new Date().toISOString(),
|
|
41
|
-
public_key_fingerprint: publicKeyFingerprint,
|
|
44
|
+
signed_at: args.signed_at ?? new Date().toISOString(),
|
|
42
45
|
workflow_id: args.workflow_id,
|
|
43
46
|
parent_receipt_id: args.parent_receipt_id,
|
|
44
47
|
chain_validation_hash: args.chain_validation_hash,
|
|
@@ -50,27 +53,111 @@ function buildReceipt(args) {
|
|
|
50
53
|
receipt_type: args.receipt_type ?? (args.is_checkpoint ? 'checkpoint' : 'outcome'),
|
|
51
54
|
cancelled_reason: args.cancelled_reason ?? null,
|
|
52
55
|
};
|
|
53
|
-
return { ...unsigned,
|
|
56
|
+
return { ...unsigned, ...(await signReceiptPayload(unsigned, args.signingKeys)) };
|
|
54
57
|
}
|
|
55
|
-
function buildReceiptV3(args) {
|
|
56
|
-
const v2 = buildReceipt(args);
|
|
58
|
+
async function buildReceiptV3(args) {
|
|
59
|
+
const v2 = await buildReceipt(args);
|
|
57
60
|
const unsigned = {
|
|
58
|
-
...v2,
|
|
61
|
+
...stripReceiptSignature(v2),
|
|
59
62
|
schema_version: 3,
|
|
60
63
|
provenance: args.provenance,
|
|
61
64
|
};
|
|
62
|
-
|
|
63
|
-
return { ...withoutSignature, signature: signReceiptPayload(withoutSignature) };
|
|
65
|
+
return { ...unsigned, ...(await signReceiptPayload(unsigned, args.signingKeys)) };
|
|
64
66
|
}
|
|
65
|
-
function verifyAnyReceipt(receipt) {
|
|
66
|
-
|
|
67
|
-
return signReceiptPayload(unsigned) === receipt.signature;
|
|
67
|
+
function verifyAnyReceipt(receipt, options = {}) {
|
|
68
|
+
return verifyReceipt(receipt, options);
|
|
68
69
|
}
|
|
69
|
-
function verifyReceipt(receipt) {
|
|
70
|
-
const { signature:
|
|
71
|
-
|
|
70
|
+
function verifyReceipt(receipt, options = {}) {
|
|
71
|
+
const { signature, public_key_hex: embeddedPublicKeyHex, public_key_fingerprint: fingerprint, ...unsigned } = receipt;
|
|
72
|
+
if (!signature || !/^[0-9a-fA-F]{128}$/.test(String(signature)))
|
|
73
|
+
return false;
|
|
74
|
+
const publicKeyHex = String(options.publicKeyHex || embeddedPublicKeyHex || '').toLowerCase();
|
|
75
|
+
if (!/^[0-9a-f]{64}$/.test(publicKeyHex))
|
|
76
|
+
return false;
|
|
77
|
+
const allowed = normalizeAllowedSignerKeys(options.allowedSignerPublicKeysHex);
|
|
78
|
+
if (allowed && !allowed.has(publicKeyHex))
|
|
79
|
+
return false;
|
|
80
|
+
const publicKeyRaw = Buffer.from(publicKeyHex, 'hex');
|
|
81
|
+
if (fingerprint && computePublicKeyFingerprint(publicKeyRaw) !== String(fingerprint))
|
|
82
|
+
return false;
|
|
83
|
+
const publicKey = (0, crypto_1.createPublicKey)({ key: Buffer.concat([ED25519_SPKI_PUBLIC_PREFIX, publicKeyRaw]), format: 'der', type: 'spki' });
|
|
84
|
+
try {
|
|
85
|
+
return (0, crypto_1.verify)(null, Buffer.from((0, decision_log_1.canonicalJson)(unsigned), 'utf8'), publicKey, Buffer.from(signature, 'hex'));
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
72
90
|
}
|
|
73
91
|
function roundUsd(value) {
|
|
74
92
|
return Math.round(value * 1_000_000) / 1_000_000;
|
|
75
93
|
}
|
|
94
|
+
function stripReceiptSignature(receipt) {
|
|
95
|
+
const { signature: _signature, public_key_fingerprint: _fingerprint, public_key_hex: _publicKeyHex, ...unsigned } = receipt;
|
|
96
|
+
return unsigned;
|
|
97
|
+
}
|
|
98
|
+
function resolveWorkflowSigningKey(signingKeys) {
|
|
99
|
+
if (signingKeys)
|
|
100
|
+
return keyFromSeed(signingKeys.privateKey, signingKeys.publicKey);
|
|
101
|
+
const raw = process.env.AGENTGUARD_SIGNING_KEY_ED25519_PRIVATE;
|
|
102
|
+
if (!raw)
|
|
103
|
+
throw new Error('Workflow receipt signing key is required');
|
|
104
|
+
return keyFromEnvironment(raw);
|
|
105
|
+
}
|
|
106
|
+
function keyFromSeed(seed, expectedPublicKey) {
|
|
107
|
+
if (seed.length !== 32)
|
|
108
|
+
throw new Error(`Ed25519 private seed must be 32 bytes, got ${seed.length} bytes`);
|
|
109
|
+
const privateKey = (0, crypto_1.createPrivateKey)({ key: Buffer.concat([ED25519_PKCS8_SEED_PREFIX, Buffer.from(seed)]), format: 'der', type: 'pkcs8' });
|
|
110
|
+
const publicKeyRaw = rawPublicKeyFromPrivate(privateKey);
|
|
111
|
+
if (expectedPublicKey && Buffer.compare(publicKeyRaw, Buffer.from(expectedPublicKey)) !== 0) {
|
|
112
|
+
throw new Error('Workflow receipt public key does not match private seed');
|
|
113
|
+
}
|
|
114
|
+
return resolved(privateKey, publicKeyRaw);
|
|
115
|
+
}
|
|
116
|
+
function keyFromEnvironment(raw) {
|
|
117
|
+
const value = raw.trim();
|
|
118
|
+
let privateKey;
|
|
119
|
+
if (value.startsWith('-----BEGIN')) {
|
|
120
|
+
privateKey = (0, crypto_1.createPrivateKey)(value);
|
|
121
|
+
}
|
|
122
|
+
else if (value.startsWith('{')) {
|
|
123
|
+
privateKey = (0, crypto_1.createPrivateKey)({ key: JSON.parse(value), format: 'jwk' });
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
const clean = value.replace(/^0x/, '');
|
|
127
|
+
let bytes;
|
|
128
|
+
if (/^[0-9a-fA-F]+$/.test(clean) && clean.length % 2 === 0)
|
|
129
|
+
bytes = Buffer.from(clean, 'hex');
|
|
130
|
+
else
|
|
131
|
+
bytes = Buffer.from(value, 'base64');
|
|
132
|
+
privateKey = bytes.length === 32
|
|
133
|
+
? (0, crypto_1.createPrivateKey)({ key: Buffer.concat([ED25519_PKCS8_SEED_PREFIX, bytes]), format: 'der', type: 'pkcs8' })
|
|
134
|
+
: (0, crypto_1.createPrivateKey)({ key: bytes, format: 'der', type: 'pkcs8' });
|
|
135
|
+
}
|
|
136
|
+
return resolved(privateKey, rawPublicKeyFromPrivate(privateKey));
|
|
137
|
+
}
|
|
138
|
+
function rawPublicKeyFromPrivate(privateKey) {
|
|
139
|
+
const der = (0, crypto_1.createPublicKey)(privateKey).export({ format: 'der', type: 'spki' });
|
|
140
|
+
return Buffer.from(der).subarray(-32);
|
|
141
|
+
}
|
|
142
|
+
function resolved(privateKey, publicKeyRaw) {
|
|
143
|
+
return {
|
|
144
|
+
privateKey,
|
|
145
|
+
publicKeyRaw,
|
|
146
|
+
publicKeyHex: publicKeyRaw.toString('hex'),
|
|
147
|
+
publicKeyFingerprint: computePublicKeyFingerprint(publicKeyRaw),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function computePublicKeyFingerprint(publicKeyRaw) {
|
|
151
|
+
return (0, crypto_1.createHash)('sha256').update(publicKeyRaw).digest('hex').slice(0, 16);
|
|
152
|
+
}
|
|
153
|
+
function normalizeAllowedSignerKeys(explicit) {
|
|
154
|
+
const values = [...(explicit ?? [])];
|
|
155
|
+
const envAllowed = process.env.AGENTGUARD_ALLOWED_SIGNER_PUBKEYS_HEX;
|
|
156
|
+
if (envAllowed)
|
|
157
|
+
values.push(...envAllowed.split(','));
|
|
158
|
+
if (values.length === 0)
|
|
159
|
+
return null;
|
|
160
|
+
const clean = values.map((value) => String(value).trim().toLowerCase()).filter((value) => /^[0-9a-f]{64}$/.test(value));
|
|
161
|
+
return new Set(clean);
|
|
162
|
+
}
|
|
76
163
|
//# sourceMappingURL=receipt.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"receipt.js","sourceRoot":"","sources":["../../src/workflow/receipt.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"receipt.js","sourceRoot":"","sources":["../../src/workflow/receipt.ts"],"names":[],"mappings":"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"}
|
package/dist/workflow/types.d.ts
CHANGED
|
@@ -12,6 +12,12 @@ export interface WorkflowConfig {
|
|
|
12
12
|
user_id?: string;
|
|
13
13
|
api_base_url?: string;
|
|
14
14
|
license_key?: string;
|
|
15
|
+
signingKeys?: {
|
|
16
|
+
/** 32-byte Ed25519 secret seed. */
|
|
17
|
+
privateKey: Uint8Array;
|
|
18
|
+
/** 32-byte Ed25519 public key. */
|
|
19
|
+
publicKey: Uint8Array;
|
|
20
|
+
};
|
|
15
21
|
post_json?: (url: string, body: unknown, headers: Record<string, string>) => Promise<unknown>;
|
|
16
22
|
get_json?: (url: string, headers: Record<string, string>) => Promise<unknown>;
|
|
17
23
|
}
|
|
@@ -34,6 +40,7 @@ export interface ReceiptV2 {
|
|
|
34
40
|
signed_at: string;
|
|
35
41
|
signature: string;
|
|
36
42
|
public_key_fingerprint: string;
|
|
43
|
+
public_key_hex: string;
|
|
37
44
|
workflow_id: string | null;
|
|
38
45
|
parent_receipt_id: string | null;
|
|
39
46
|
chain_validation_hash: string | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/workflow/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/workflow/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentguard-run/spend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "All terminology and labels used in AgentGuard materials are descriptive of software functionality only, not legal definitions or guarantees of compliance. Terms like receipt, audit log, evidence, audit trail, and attestation refer solely to cryptographically-signed records produced by the software. Full functional-use disclaimer in README.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -109,6 +109,11 @@
|
|
|
109
109
|
"types": "./dist/frameworks/openrouter.d.ts",
|
|
110
110
|
"require": "./dist/frameworks/openrouter.js",
|
|
111
111
|
"default": "./dist/frameworks/openrouter.js"
|
|
112
|
+
},
|
|
113
|
+
"./operating-pack": {
|
|
114
|
+
"types": "./dist/operating-pack.d.ts",
|
|
115
|
+
"require": "./dist/operating-pack.js",
|
|
116
|
+
"default": "./dist/operating-pack.js"
|
|
112
117
|
}
|
|
113
118
|
},
|
|
114
119
|
"bin": {
|
|
@@ -142,7 +147,8 @@
|
|
|
142
147
|
"src/byo",
|
|
143
148
|
"src/cli/byo.ts",
|
|
144
149
|
"docs/patents/CLAIM_MAPPING.md",
|
|
145
|
-
"src/frameworks"
|
|
150
|
+
"src/frameworks",
|
|
151
|
+
"src/operating-pack.ts"
|
|
146
152
|
],
|
|
147
153
|
"scripts": {
|
|
148
154
|
"build": "tsc",
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
import { randomUUID } from 'crypto';
|
|
2
|
+
import { canonicalJson, computeSignerFingerprint, sha256Hex } from './decision-log';
|
|
3
|
+
import { signDagNode, verifyReceiptDag, type ReceiptCapabilityLevel, type ReceiptDagEnvelope, type ReceiptDagNode } from './receipts/dag';
|
|
4
|
+
import type { SpendDecision } from './types';
|
|
5
|
+
|
|
6
|
+
export type OperatingPackVertical = 'law';
|
|
7
|
+
export type OperatingPackActionKind =
|
|
8
|
+
| 'intake'
|
|
9
|
+
| 'conflict_check'
|
|
10
|
+
| 'document_summary'
|
|
11
|
+
| 'billing_reconciliation'
|
|
12
|
+
| 'client_update_draft'
|
|
13
|
+
| 'send_client_email'
|
|
14
|
+
| 'trust_account_action';
|
|
15
|
+
|
|
16
|
+
export interface OperatingPackSigningKeys {
|
|
17
|
+
privateKey: Uint8Array;
|
|
18
|
+
publicKey: Uint8Array;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface OperatingPackReviewerCascadeOptions {
|
|
22
|
+
enabled?: boolean;
|
|
23
|
+
drafterModel?: string;
|
|
24
|
+
reviewerModel?: string;
|
|
25
|
+
triggerThreshold?: number;
|
|
26
|
+
drafterMaxCostCents?: number;
|
|
27
|
+
reviewerMaxCostCents?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface OperatingPackOptions {
|
|
31
|
+
vertical: OperatingPackVertical;
|
|
32
|
+
workflowId: string;
|
|
33
|
+
signingKeys: OperatingPackSigningKeys;
|
|
34
|
+
approvedCapabilities?: ReceiptCapabilityLevel[];
|
|
35
|
+
reviewerCascade?: OperatingPackReviewerCascadeOptions;
|
|
36
|
+
maxWorkflowCostCents?: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface OperatingPackAction {
|
|
40
|
+
actionId: string;
|
|
41
|
+
kind: OperatingPackActionKind;
|
|
42
|
+
capability?: ReceiptCapabilityLevel;
|
|
43
|
+
highRisk?: boolean;
|
|
44
|
+
highRiskClassifierScore?: number;
|
|
45
|
+
keywordRisk?: boolean;
|
|
46
|
+
modelIds?: string[];
|
|
47
|
+
tokenCounts?: { inputTokens?: number; outputTokens?: number };
|
|
48
|
+
totalCostCents?: number;
|
|
49
|
+
metadata?: Record<string, unknown>;
|
|
50
|
+
reviewerVerdict?: 'drafter_only' | 'review_required' | 'approved' | 'rejected';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface OperatingPackGuardResult {
|
|
54
|
+
allowed: boolean;
|
|
55
|
+
reason: string;
|
|
56
|
+
node: ReceiptDagNode;
|
|
57
|
+
dag: ReceiptDagEnvelope;
|
|
58
|
+
reviewerCascadeTriggered: boolean;
|
|
59
|
+
spentCents: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface OperatingPackSkillTemplate {
|
|
63
|
+
slug: string;
|
|
64
|
+
title: string;
|
|
65
|
+
capability: ReceiptCapabilityLevel;
|
|
66
|
+
reviewerCascade: boolean;
|
|
67
|
+
markdown: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface OperatingPackDefinition {
|
|
71
|
+
vertical: OperatingPackVertical;
|
|
72
|
+
title: string;
|
|
73
|
+
counselReviewPending: boolean;
|
|
74
|
+
agentsMd: string;
|
|
75
|
+
skills: Record<string, OperatingPackSkillTemplate>;
|
|
76
|
+
sourceOfTruth: string;
|
|
77
|
+
checklist: string[];
|
|
78
|
+
capabilityTable: Array<{ capability: ReceiptCapabilityLevel; use: string; blockedExamples: string[] }>;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const CAPABILITY_RANK: Record<ReceiptCapabilityLevel, number> = {
|
|
82
|
+
READ_ONLY: 0,
|
|
83
|
+
TRANSACT: 1,
|
|
84
|
+
ADMIN: 2,
|
|
85
|
+
ORCHESTRATE: 3,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const DATA_PLANE_KEYS = /^(title|summary|prompt|completion|content|messages|input|output|text|raw|source|notes|draft|body|transcript|document|client_content|clientContent|matter_description|email_body|file_text)$/i;
|
|
89
|
+
const SAFE_METADATA_KEYS = new Set([
|
|
90
|
+
'action_id',
|
|
91
|
+
'action_kind',
|
|
92
|
+
'billing_code',
|
|
93
|
+
'capability',
|
|
94
|
+
'cost_cents',
|
|
95
|
+
'duration_ms',
|
|
96
|
+
'event',
|
|
97
|
+
'jurisdiction',
|
|
98
|
+
'matter_hash',
|
|
99
|
+
'model',
|
|
100
|
+
'model_id',
|
|
101
|
+
'outcome',
|
|
102
|
+
'practice_area',
|
|
103
|
+
'review_status',
|
|
104
|
+
'risk_score',
|
|
105
|
+
'token_count',
|
|
106
|
+
'workflow_id',
|
|
107
|
+
]);
|
|
108
|
+
|
|
109
|
+
export const LAW_OPERATING_PACK: OperatingPackDefinition = {
|
|
110
|
+
vertical: 'law',
|
|
111
|
+
title: 'AgentGuard Law Operating Pack',
|
|
112
|
+
counselReviewPending: true,
|
|
113
|
+
agentsMd: `# AgentGuard Law Operating Pack\n\nCounsel review pending: this file is operating scaffolding for legal workflows and should be reviewed by your counsel before production use.\n\n## Guardrails\n\n- ABA Model Rule 1.6: protect client information. Do not paste client content into tools that are not approved for the matter.\n- Conflicts: run conflict checks before intake work advances to drafting or client communication.\n- No unauthorized practice of law: agents prepare drafts and metadata. A licensed attorney reviews legal judgment.\n- Draft before send: client-facing emails and filings are drafts until a human approves dispatch.\n- Retention: keep signed AgentGuard receipts and matter metadata according to firm policy. Do not store prompt or completion content in receipts.\n- Trust account actions: trust transfers, disbursement instructions, and ledger changes require ADMIN capability.\n\n## Capability Map\n\n- READ_ONLY: intake classification, conflict pre-checks, document summaries, billing reconciliation previews.\n- TRANSACT: create a client-update draft or prepare a dispatch packet.\n- ADMIN: trust-account actions, retention policy changes, capability changes.\n- ORCHESTRATE: assign or revoke agent work across matters.\n`,
|
|
114
|
+
skills: {
|
|
115
|
+
intake: {
|
|
116
|
+
slug: 'intake',
|
|
117
|
+
title: 'Intake Skill',
|
|
118
|
+
capability: 'READ_ONLY',
|
|
119
|
+
reviewerCascade: false,
|
|
120
|
+
markdown: `# Intake Skill\n\nPurpose: classify a new inquiry using matter metadata only.\n\nInputs: jurisdiction, practice area, referral source, matter hash, urgency flag.\n\nRules:\n- Do not ask the model to decide representation.\n- Do not place client narrative text in AgentGuard receipts.\n- Output a routing label, missing-information checklist, and conflict-check reminder.\n`,
|
|
121
|
+
},
|
|
122
|
+
'conflict-check': {
|
|
123
|
+
slug: 'conflict-check',
|
|
124
|
+
title: 'Conflict Check Skill',
|
|
125
|
+
capability: 'READ_ONLY',
|
|
126
|
+
reviewerCascade: true,
|
|
127
|
+
markdown: `# Conflict Check Skill\n\nPurpose: prepare a conflict pre-check packet from firm metadata.\n\nInputs: matter hash, party hashes, jurisdiction, practice area, intake source.\n\nRules:\n- Match on firm-approved metadata systems only.\n- Refuse if party identity data is incomplete.\n- Reviewer Cascade triggers when a possible match, former-client marker, adverse-party marker, or trust-account marker appears.\n`,
|
|
128
|
+
},
|
|
129
|
+
'document-summary': {
|
|
130
|
+
slug: 'document-summary',
|
|
131
|
+
title: 'Document Summary Skill',
|
|
132
|
+
capability: 'READ_ONLY',
|
|
133
|
+
reviewerCascade: true,
|
|
134
|
+
markdown: `# Document Summary Skill\n\nPurpose: produce a working summary for attorney review.\n\nInputs: document hash, document type, jurisdiction, matter hash, token counts.\n\nRules:\n- Cite document sections or refuse when citations are unavailable.\n- Mark all output as draft work product for attorney review.\n- Reviewer Cascade triggers for settlement, privilege, indemnity, non-compete, waiver, trust, sanctions, or deadline markers.\n`,
|
|
135
|
+
},
|
|
136
|
+
'billing-reconciliation': {
|
|
137
|
+
slug: 'billing-reconciliation',
|
|
138
|
+
title: 'Billing Reconciliation Skill',
|
|
139
|
+
capability: 'READ_ONLY',
|
|
140
|
+
reviewerCascade: false,
|
|
141
|
+
markdown: `# Billing Reconciliation Skill\n\nPurpose: compare time-entry metadata against matter budgets and billing codes.\n\nInputs: billing code, matter hash, time-entry hashes, amount cents, policy cap.\n\nRules:\n- Do not move money.\n- Do not alter trust ledgers.\n- Escalate to ADMIN before any trust-account action.\n`,
|
|
142
|
+
},
|
|
143
|
+
'client-update-draft': {
|
|
144
|
+
slug: 'client-update-draft',
|
|
145
|
+
title: 'Client Update Draft Skill',
|
|
146
|
+
capability: 'TRANSACT',
|
|
147
|
+
reviewerCascade: true,
|
|
148
|
+
markdown: `# Client Update Draft Skill\n\nPurpose: draft a client update for attorney review.\n\nInputs: matter hash, milestone label, jurisdiction, practice area, cited record hashes.\n\nRules:\n- Draft before send. Never dispatch directly.\n- Include a review status field before any client-facing use.\n- Reviewer Cascade triggers for deadline, settlement, privilege, fee, trust, or adverse-party markers.\n`,
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
sourceOfTruth: `# Source Of Truth Layout\n\nUse this local layout inside the firm environment.\n\n- matters/<matter-hash>/metadata.json\n- matters/<matter-hash>/parties.json\n- matters/<matter-hash>/documents/<document-hash>.json\n- matters/<matter-hash>/receipts/*.json\n- policies/agentguard-policy.yaml\n- logs/agentguard-decisions.ndjson\n\nAgentGuard receipts store metadata, hashes, costs, model identifiers, capability decisions, and signatures. They do not store prompt or completion content.\n`,
|
|
152
|
+
checklist: [
|
|
153
|
+
'Review AGENTS.md with firm counsel before production use.',
|
|
154
|
+
'Define matter hash and party hash conventions.',
|
|
155
|
+
'Map each skill to a capability tier.',
|
|
156
|
+
'Enable Reviewer Cascade for client updates, conflicts, and document summaries.',
|
|
157
|
+
'Set a per-matter and per-day spend cap.',
|
|
158
|
+
'Store signed receipts with matter metadata only.',
|
|
159
|
+
'Test trust-account actions with ADMIN capability denied by default.',
|
|
160
|
+
],
|
|
161
|
+
capabilityTable: [
|
|
162
|
+
{ capability: 'READ_ONLY', use: 'Classify, summarize, reconcile, and prepare metadata-only packets.', blockedExamples: ['Sending client email', 'Trust ledger update'] },
|
|
163
|
+
{ capability: 'TRANSACT', use: 'Create dispatch-ready drafts that still require human approval.', blockedExamples: ['Trust transfer', 'Policy change'] },
|
|
164
|
+
{ capability: 'ADMIN', use: 'Trust-account actions, retention changes, and capability grants.', blockedExamples: ['Unreviewed client dispatch'] },
|
|
165
|
+
{ capability: 'ORCHESTRATE', use: 'Assign, revoke, or coordinate multiple matter agents.', blockedExamples: ['Bypassing capability review'] },
|
|
166
|
+
],
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
export function createOperatingPackEnvelope(opts: OperatingPackOptions): OperatingPackEnvelope {
|
|
170
|
+
return new OperatingPackEnvelope(opts);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export class OperatingPackEnvelope {
|
|
174
|
+
private readonly nodes: ReceiptDagNode[] = [];
|
|
175
|
+
private readonly approvedCapabilities: Set<ReceiptCapabilityLevel>;
|
|
176
|
+
private spentCents = 0;
|
|
177
|
+
|
|
178
|
+
constructor(private readonly opts: OperatingPackOptions) {
|
|
179
|
+
if (opts.vertical !== 'law') throw new Error('Only the law operating pack is bundled in this release');
|
|
180
|
+
if (!safeLabel(opts.workflowId)) throw new Error('Operating Pack workflowId is required');
|
|
181
|
+
this.approvedCapabilities = new Set(opts.approvedCapabilities ?? ['READ_ONLY']);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
getPack(): OperatingPackDefinition {
|
|
185
|
+
return LAW_OPERATING_PACK;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async guardAction(input: OperatingPackAction): Promise<OperatingPackGuardResult> {
|
|
189
|
+
assertAction(input);
|
|
190
|
+
assertMetadataOnly(input.metadata ?? {});
|
|
191
|
+
const capability = input.capability ?? capabilityForAction(input.kind);
|
|
192
|
+
const cost = safeNonNegativeInteger(input.totalCostCents);
|
|
193
|
+
const spendExceeded = Number.isSafeInteger(this.opts.maxWorkflowCostCents) && this.opts.maxWorkflowCostCents! >= 0 && this.spentCents + cost > this.opts.maxWorkflowCostCents!;
|
|
194
|
+
const capabilityAllowed = this.hasCapability(capability);
|
|
195
|
+
const allowed = capabilityAllowed && !spendExceeded;
|
|
196
|
+
const reason = !capabilityAllowed ? `Capability ${capability} is required for ${input.kind}` : spendExceeded ? 'Operating Pack workflow spend cap reached' : 'Operating Pack action allowed';
|
|
197
|
+
if (allowed) this.spentCents += cost;
|
|
198
|
+
const triggers = allowed ? reviewerCascadeTriggers(input, this.opts) : [];
|
|
199
|
+
const node = await this.signActionNode(input, capability, allowed, reason, triggers);
|
|
200
|
+
this.nodes.push(node);
|
|
201
|
+
return {
|
|
202
|
+
allowed,
|
|
203
|
+
reason,
|
|
204
|
+
node,
|
|
205
|
+
dag: this.getReceiptDag(),
|
|
206
|
+
reviewerCascadeTriggered: triggers.length > 0,
|
|
207
|
+
spentCents: this.spentCents,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
getReceiptDag(): ReceiptDagEnvelope {
|
|
212
|
+
const root = this.nodes[this.nodes.length - 1];
|
|
213
|
+
return { nodes: [...this.nodes], rootId: root?.entryHash ?? '0'.repeat(64), workflowId: workflowHash(this.opts.workflowId) };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async verifyDag(publicKeyHex?: string): Promise<Awaited<ReturnType<typeof verifyReceiptDag>>> {
|
|
217
|
+
return verifyReceiptDag(this.getReceiptDag(), publicKeyHex ?? Buffer.from(this.opts.signingKeys.publicKey).toString('hex'));
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
outboundPayloads(): Record<string, unknown>[] {
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
signerFingerprint(): string {
|
|
225
|
+
return computeSignerFingerprint(this.opts.signingKeys.publicKey);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
private hasCapability(capability: ReceiptCapabilityLevel): boolean {
|
|
229
|
+
for (const granted of this.approvedCapabilities) {
|
|
230
|
+
if (CAPABILITY_RANK[granted] >= CAPABILITY_RANK[capability]) return true;
|
|
231
|
+
}
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
private async signActionNode(input: OperatingPackAction, capability: ReceiptCapabilityLevel, allowed: boolean, reason: string, triggers: string[]): Promise<ReceiptDagNode> {
|
|
236
|
+
const decision = operatingPackDecision(input, this.opts, capability, allowed, reason, triggers, this.spentCents);
|
|
237
|
+
return signDagNode({
|
|
238
|
+
sequence: this.nodes.length,
|
|
239
|
+
decision,
|
|
240
|
+
privateKey: this.opts.signingKeys.privateKey,
|
|
241
|
+
publicKey: this.opts.signingKeys.publicKey,
|
|
242
|
+
parents: this.nodes.length ? [this.nodes[this.nodes.length - 1] as ReceiptDagNode] : [],
|
|
243
|
+
workflowId: workflowHash(this.opts.workflowId),
|
|
244
|
+
trustScore: allowed ? (triggers.length ? 0.9 : 0.94) : 0.2,
|
|
245
|
+
blocked: !allowed,
|
|
246
|
+
capability,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function operatingPackDecision(input: OperatingPackAction, opts: OperatingPackOptions, capability: ReceiptCapabilityLevel, allowed: boolean, reason: string, triggers: string[], spentCents: number): SpendDecision {
|
|
252
|
+
const cost = safeNonNegativeInteger(input.totalCostCents);
|
|
253
|
+
const inputTokens = safeNonNegativeInteger(input.tokenCounts?.inputTokens);
|
|
254
|
+
const outputTokens = safeNonNegativeInteger(input.tokenCounts?.outputTokens);
|
|
255
|
+
const receipt: Record<string, unknown> = {
|
|
256
|
+
type: 'agent_operating_pack_action',
|
|
257
|
+
vertical: opts.vertical,
|
|
258
|
+
counselReviewPending: LAW_OPERATING_PACK.counselReviewPending,
|
|
259
|
+
workflowHash: workflowHash(opts.workflowId),
|
|
260
|
+
actionId: safeLabel(input.actionId) ?? hashValue(input.actionId).slice(0, 16),
|
|
261
|
+
actionKind: input.kind,
|
|
262
|
+
capability,
|
|
263
|
+
blocked: !allowed,
|
|
264
|
+
modelIds: safeModelIds(input.modelIds ?? []),
|
|
265
|
+
inputTokens,
|
|
266
|
+
outputTokens,
|
|
267
|
+
totalCostCents: cost,
|
|
268
|
+
metadata: filterReceiptMetadata({ ...(input.metadata ?? {}), action_kind: input.kind, capability }),
|
|
269
|
+
};
|
|
270
|
+
if (triggers.length > 0) receipt.reviewerCascade = buildReviewerCascadeReceipt(input, opts, triggers);
|
|
271
|
+
return {
|
|
272
|
+
decisionId: randomUUID(),
|
|
273
|
+
timestamp: new Date().toISOString(),
|
|
274
|
+
action: allowed ? 'allow' : 'block',
|
|
275
|
+
triggeredCap: allowed ? null : { window: 'per_call', amountCents: cost, action: 'block', reason },
|
|
276
|
+
triggeredScopeKey: null,
|
|
277
|
+
projectedCents: cost,
|
|
278
|
+
windowSpendBefore: Math.max(0, spentCents - (allowed ? cost : 0)),
|
|
279
|
+
windowSpendAfter: spentCents,
|
|
280
|
+
provider: 'unknown',
|
|
281
|
+
modelRequested: 'operating-pack',
|
|
282
|
+
modelResolved: 'operating-pack',
|
|
283
|
+
policyId: 'agentguard-operating-pack-law',
|
|
284
|
+
policyVersion: 1,
|
|
285
|
+
enforcementMode: 'enforce',
|
|
286
|
+
reasons: [reason],
|
|
287
|
+
entryType: 'outcome',
|
|
288
|
+
outcomeReceipt: receipt,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function buildReviewerCascadeReceipt(input: OperatingPackAction, opts: OperatingPackOptions, triggers: string[]): Record<string, unknown> {
|
|
293
|
+
const cascade = opts.reviewerCascade ?? {};
|
|
294
|
+
return {
|
|
295
|
+
outcome: input.kind,
|
|
296
|
+
drafter: {
|
|
297
|
+
model: safeLabel(cascade.drafterModel) ?? 'openai/gpt-4o-mini',
|
|
298
|
+
maxCostCents: safeNonNegativeInteger(cascade.drafterMaxCostCents ?? 25),
|
|
299
|
+
},
|
|
300
|
+
reviewer: {
|
|
301
|
+
model: safeLabel(cascade.reviewerModel) ?? 'anthropic/claude-sonnet-4-6',
|
|
302
|
+
maxCostCents: safeNonNegativeInteger(cascade.reviewerMaxCostCents ?? 75),
|
|
303
|
+
},
|
|
304
|
+
triggerFired: triggers,
|
|
305
|
+
reviewerVerdict: safeLabel(input.reviewerVerdict) ?? 'review_required',
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function reviewerCascadeTriggers(input: OperatingPackAction, opts: OperatingPackOptions): string[] {
|
|
310
|
+
if (opts.reviewerCascade?.enabled === false) return [];
|
|
311
|
+
const threshold = opts.reviewerCascade?.triggerThreshold ?? 0.55;
|
|
312
|
+
const triggers: string[] = [];
|
|
313
|
+
if (input.highRisk === true) triggers.push('high_risk_action');
|
|
314
|
+
if (input.keywordRisk === true) triggers.push('keyword_prefilter');
|
|
315
|
+
if (typeof input.highRiskClassifierScore === 'number' && input.highRiskClassifierScore >= threshold) triggers.push(`risk_score_above:${threshold}`);
|
|
316
|
+
if (['conflict_check', 'document_summary', 'client_update_draft', 'send_client_email', 'trust_account_action'].includes(input.kind)) triggers.push('law_pack_default');
|
|
317
|
+
return [...new Set(triggers)].sort();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function capabilityForAction(kind: OperatingPackActionKind): ReceiptCapabilityLevel {
|
|
321
|
+
if (kind === 'trust_account_action') return 'ADMIN';
|
|
322
|
+
if (kind === 'send_client_email' || kind === 'client_update_draft') return 'TRANSACT';
|
|
323
|
+
return 'READ_ONLY';
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function assertAction(input: OperatingPackAction): void {
|
|
327
|
+
assertPlainRecord(input, 'action');
|
|
328
|
+
if (!safeLabel(input.actionId)) throw new Error('Operating Pack actionId is required');
|
|
329
|
+
const allowedKinds: OperatingPackActionKind[] = ['intake', 'conflict_check', 'document_summary', 'billing_reconciliation', 'client_update_draft', 'send_client_email', 'trust_account_action'];
|
|
330
|
+
if (!allowedKinds.includes(input.kind)) throw new Error('Operating Pack action kind is not supported');
|
|
331
|
+
if (input.capability && !(input.capability in CAPABILITY_RANK)) throw new Error('Operating Pack capability is not supported');
|
|
332
|
+
if (input.tokenCounts) validateTokens(input.tokenCounts.inputTokens ?? 0, input.tokenCounts.outputTokens ?? 0);
|
|
333
|
+
if (input.totalCostCents != null && (!Number.isSafeInteger(input.totalCostCents) || input.totalCostCents < 0)) throw new Error('Operating Pack totalCostCents must be a non-negative safe integer');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export function assertOperatingPackMetadataOnly(value: unknown, path = 'metadata'): void {
|
|
337
|
+
assertMetadataOnly(value, path);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function assertMetadataOnly(value: unknown, path = 'metadata'): void {
|
|
341
|
+
if (value == null) return;
|
|
342
|
+
if (Array.isArray(value)) {
|
|
343
|
+
for (let i = 0; i < value.length; i++) assertMetadataOnly(value[i], `${path}[${i}]`);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (typeof value !== 'object') return;
|
|
347
|
+
for (const [key, child] of Object.entries(value as Record<string, unknown>)) {
|
|
348
|
+
if (DATA_PLANE_KEYS.test(key)) throw new Error(`Operating Pack metadata cannot include data-plane field: ${path}.${key}`);
|
|
349
|
+
assertMetadataOnly(child, `${path}.${key}`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function filterReceiptMetadata(metadata: Record<string, unknown>): Record<string, unknown> {
|
|
354
|
+
assertMetadataOnly(metadata);
|
|
355
|
+
const out: Record<string, unknown> = {};
|
|
356
|
+
for (const [key, value] of Object.entries(metadata)) {
|
|
357
|
+
if (!SAFE_METADATA_KEYS.has(key)) continue;
|
|
358
|
+
if (value == null || typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') out[key] = value;
|
|
359
|
+
}
|
|
360
|
+
return out;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function assertPlainRecord(value: unknown, name: string): void {
|
|
364
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`Operating Pack ${name} must be an object`);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function validateTokens(inputTokens: number, outputTokens: number): void {
|
|
368
|
+
if (!Number.isSafeInteger(inputTokens) || inputTokens < 0) throw new Error('inputTokens must be a non-negative safe integer');
|
|
369
|
+
if (!Number.isSafeInteger(outputTokens) || outputTokens < 0) throw new Error('outputTokens must be a non-negative safe integer');
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function workflowHash(value: string): string {
|
|
373
|
+
return hashValue(`workflow:${value}`);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function hashValue(value: string): string {
|
|
377
|
+
return sha256Hex(canonicalJson({ value }));
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function safeLabel(value: unknown): string | undefined {
|
|
381
|
+
if (typeof value !== 'string') return undefined;
|
|
382
|
+
const trimmed = value.trim();
|
|
383
|
+
if (!trimmed || !/^[A-Za-z0-9_.:/-]{1,180}$/.test(trimmed)) return undefined;
|
|
384
|
+
return trimmed;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function safeModelIds(models: string[]): string[] {
|
|
388
|
+
return models.map((model) => safeLabel(model)).filter(Boolean) as string[];
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function safeNonNegativeInteger(value: unknown): number {
|
|
392
|
+
return Number.isSafeInteger(value) && (value as number) >= 0 ? value as number : 0;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export function operatingPackSignerFingerprint(opts: OperatingPackOptions): string | null {
|
|
396
|
+
return opts.signingKeys ? computeSignerFingerprint(opts.signingKeys.publicKey) : null;
|
|
397
|
+
}
|