@decentrys/agent 0.1.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/LICENSE +21 -0
- package/README.md +62 -0
- package/dist/browser/decentrys-agent.js +2051 -0
- package/dist/browser/decentrys-agent.mjs +2026 -0
- package/dist/client.d.ts +194 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +303 -0
- package/dist/client.js.map +1 -0
- package/dist/explain.d.ts +63 -0
- package/dist/explain.d.ts.map +1 -0
- package/dist/explain.js +144 -0
- package/dist/explain.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/ledger.d.ts +166 -0
- package/dist/ledger.d.ts.map +1 -0
- package/dist/ledger.js +218 -0
- package/dist/ledger.js.map +1 -0
- package/dist/model.d.ts +256 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +74 -0
- package/dist/model.js.map +1 -0
- package/dist/policy.d.ts +137 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +845 -0
- package/dist/policy.js.map +1 -0
- package/package.json +64 -0
- package/src/client.test.ts +351 -0
- package/src/client.ts +392 -0
- package/src/explain.test.ts +148 -0
- package/src/explain.ts +204 -0
- package/src/index.ts +5 -0
- package/src/ledger.test.ts +192 -0
- package/src/ledger.ts +303 -0
- package/src/model.ts +324 -0
- package/src/policy.test.ts +762 -0
- package/src/policy.ts +951 -0
package/dist/model.d.ts
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentGuard — the types.
|
|
3
|
+
*
|
|
4
|
+
* Protect informs a person, who then decides. AgentGuard sits in front of
|
|
5
|
+
* something with no judgement that will do exactly what it was told, at
|
|
6
|
+
* machine speed, as many times as its loop runs. Three consequences shape
|
|
7
|
+
* everything in this file:
|
|
8
|
+
*
|
|
9
|
+
* 1. **A warning is not a control.** Protect deliberately warns and never
|
|
10
|
+
* blocks, because the person holding the keys should decide. An agent
|
|
11
|
+
* cannot heed a warning — a warning it ignores is an allow with extra
|
|
12
|
+
* words. So AgentGuard's outcomes are `allow`, `require_human_approval`
|
|
13
|
+
* and `deny`, and the middle one means *stop and hand this to a person*.
|
|
14
|
+
*
|
|
15
|
+
* 2. **The operator is not the agent.** Limits are set by a human ahead of
|
|
16
|
+
* time. The agent must not be able to widen them — not by argument, not by
|
|
17
|
+
* mutating the object it was handed, not by passing an override. That is
|
|
18
|
+
* enforced structurally in `policy.ts` (`sealPolicy`, `narrowPolicy`),
|
|
19
|
+
* not by convention.
|
|
20
|
+
*
|
|
21
|
+
* 3. **Every decision must survive being questioned later.** "The agent spent
|
|
22
|
+
* the treasury" is answered by an audit record that names each rule, the
|
|
23
|
+
* limit it enforced and the value it observed. An allow that cannot be
|
|
24
|
+
* explained is as bad as a wrong one, so a passing rule is recorded just
|
|
25
|
+
* as loudly as a failing one.
|
|
26
|
+
*
|
|
27
|
+
* The risk model is *not* reimplemented here. Classification, the seven risk
|
|
28
|
+
* levels and `applyPolicy` come from `@decentrys/protect` unchanged; this
|
|
29
|
+
* package adds the limits an autonomous actor needs and a human does not.
|
|
30
|
+
*/
|
|
31
|
+
import type { Assessment, Policy, RiskLevel } from '@decentrys/protect';
|
|
32
|
+
export declare const AGENT_MODEL_VERSION = "agent-1.0.0";
|
|
33
|
+
/**
|
|
34
|
+
* The kinds of action a policy can talk about.
|
|
35
|
+
*
|
|
36
|
+
* These are coarse on purpose. "May swap, may not approve" is a sentence an
|
|
37
|
+
* operator can write and check; "may call selector 0x095ea7b3" is not. An
|
|
38
|
+
* action whose type the integrator cannot determine is `unknown`, which is a
|
|
39
|
+
* first-class value rather than a default — see `deniedActions` below.
|
|
40
|
+
*/
|
|
41
|
+
export declare const AGENT_ACTION_TYPES: readonly ["transfer", "swap", "approve", "contract_call", "bridge", "stake", "unstake", "deploy", "sign_message", "unknown"];
|
|
42
|
+
export type AgentActionType = (typeof AGENT_ACTION_TYPES)[number];
|
|
43
|
+
export interface AgentAction {
|
|
44
|
+
/** Which agent proposes it. Limits are held per agent, not per process. */
|
|
45
|
+
agentId: string;
|
|
46
|
+
chain: string;
|
|
47
|
+
type: AgentActionType;
|
|
48
|
+
/** The wallet the agent signs with. */
|
|
49
|
+
from: string;
|
|
50
|
+
/** Counterparty or contract. Absent for a deploy or a bare message signature. */
|
|
51
|
+
to?: string;
|
|
52
|
+
/** Token contract, for a transfer, swap or approval. */
|
|
53
|
+
token?: string;
|
|
54
|
+
/** Base units, as a string. Numbers lose precision at these magnitudes. */
|
|
55
|
+
value?: string;
|
|
56
|
+
/**
|
|
57
|
+
* The action's value in USD, as the integrator prices it.
|
|
58
|
+
*
|
|
59
|
+
* **`undefined` is not zero.** An action with no value at all (a message
|
|
60
|
+
* signature, a zero-value call) should say `0`; leaving this out means "I do
|
|
61
|
+
* not know what this is worth", and a value cap cannot be enforced against
|
|
62
|
+
* an unknown. See `VALUE_UNKNOWN` in `policy.ts` for what happens then, and
|
|
63
|
+
* why it is the only defensible answer for an autonomous signer.
|
|
64
|
+
*
|
|
65
|
+
* AgentGuard never prices anything itself. Pricing belongs to whoever runs
|
|
66
|
+
* the agent, with the feed and the staleness rules they are willing to be
|
|
67
|
+
* accountable for.
|
|
68
|
+
*/
|
|
69
|
+
valueUsd?: number;
|
|
70
|
+
/** EVM calldata. */
|
|
71
|
+
data?: string;
|
|
72
|
+
/** Non-EVM families carry their serialized payload here. */
|
|
73
|
+
raw?: string;
|
|
74
|
+
/** The site or service that asked for this, when there is one. */
|
|
75
|
+
origin?: string;
|
|
76
|
+
/** Base units, or `unlimited`. Only meaningful for `type: 'approve'`. */
|
|
77
|
+
approvalAmount?: string;
|
|
78
|
+
/** What the agent believes it is doing, in its own words. Recorded, never trusted. */
|
|
79
|
+
intent?: string;
|
|
80
|
+
/**
|
|
81
|
+
* Set by the caller so a retried action is not counted twice against a
|
|
82
|
+
* cumulative cap. Two calls carrying the same key reuse one reservation.
|
|
83
|
+
*/
|
|
84
|
+
idempotencyKey?: string;
|
|
85
|
+
proposedAt?: string;
|
|
86
|
+
}
|
|
87
|
+
/** A cumulative limit: how much may be committed inside a rolling window. */
|
|
88
|
+
export interface SpendWindow {
|
|
89
|
+
/** A name an operator recognises in an audit record — 'daily', 'per-hour'. */
|
|
90
|
+
label: string;
|
|
91
|
+
windowMs: number;
|
|
92
|
+
maxValueUsd: number;
|
|
93
|
+
}
|
|
94
|
+
/** A rate ceiling: how many actions may be admitted inside a rolling window. */
|
|
95
|
+
export interface RateWindow {
|
|
96
|
+
label: string;
|
|
97
|
+
windowMs: number;
|
|
98
|
+
maxActions: number;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* What an operator can express.
|
|
102
|
+
*
|
|
103
|
+
* Every field is optional and every omitted field means *unconstrained on that
|
|
104
|
+
* axis* — with one deliberate exception, `allowUnlimitedApprovals`, which
|
|
105
|
+
* defaults to refusing. An unlimited approval is the single action that turns
|
|
106
|
+
* a bounded mistake into an unbounded one, and defaulting it open would make
|
|
107
|
+
* the safe configuration the one you have to remember to write.
|
|
108
|
+
*/
|
|
109
|
+
export interface AgentPolicy {
|
|
110
|
+
/** Free text for the operator's own change control. Recorded in every decision. */
|
|
111
|
+
version?: string;
|
|
112
|
+
/**
|
|
113
|
+
* Bind this policy to one agent. When set, an action from a different
|
|
114
|
+
* `agentId` is denied rather than evaluated — a policy leaking across agents
|
|
115
|
+
* is how one agent's generous limits become another's.
|
|
116
|
+
*/
|
|
117
|
+
agentId?: string;
|
|
118
|
+
/**
|
|
119
|
+
* Risk level → action, exactly the `Policy` type Protect uses. Merged over
|
|
120
|
+
* `DEFAULT_AGENT_RISK_POLICY`; see that constant for why the agent default
|
|
121
|
+
* differs from the consumer one.
|
|
122
|
+
*/
|
|
123
|
+
risk?: Policy;
|
|
124
|
+
/** Shorthand: deny anything strictly above this level. Applied on top of `risk`. */
|
|
125
|
+
maxRiskLevel?: RiskLevel;
|
|
126
|
+
maxValuePerActionUsd?: number;
|
|
127
|
+
/** Rolling cumulative caps. Several may run at once — per-hour and per-day. */
|
|
128
|
+
spendWindows?: SpendWindow[];
|
|
129
|
+
/**
|
|
130
|
+
* When present, an allowlist: any counterparty not in it is denied. Absent
|
|
131
|
+
* means unconstrained, which is a real choice an operator may make and not
|
|
132
|
+
* one AgentGuard makes for them.
|
|
133
|
+
*/
|
|
134
|
+
allowedCounterparties?: string[];
|
|
135
|
+
/** Always denied, even if also allowlisted. Denial wins. */
|
|
136
|
+
blockedCounterparties?: string[];
|
|
137
|
+
/** When present, an allowlist over the contract the action calls. */
|
|
138
|
+
allowedContracts?: string[];
|
|
139
|
+
/** When present, an allowlist over `token`. */
|
|
140
|
+
allowedTokens?: string[];
|
|
141
|
+
allowedActions?: AgentActionType[];
|
|
142
|
+
deniedActions?: AgentActionType[];
|
|
143
|
+
allowedChains?: string[];
|
|
144
|
+
/** Default `false`. Set true only if an unbounded allowance is genuinely intended. */
|
|
145
|
+
allowUnlimitedApprovals?: boolean;
|
|
146
|
+
/** An agent stuck in a loop is a real failure mode, not a hypothetical one. */
|
|
147
|
+
rateWindows?: RateWindow[];
|
|
148
|
+
/** Above this value, a human decides even when every other rule passes. */
|
|
149
|
+
humanApprovalAboveUsd?: number;
|
|
150
|
+
/** At or above this level, a human decides even when the risk policy allows. */
|
|
151
|
+
humanApprovalAtRiskLevel?: RiskLevel;
|
|
152
|
+
/** What to do when Decentrys could not be reached. Default `escalate`. */
|
|
153
|
+
failMode?: AgentFailMode;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* What AgentGuard does when the risk check could not run.
|
|
157
|
+
*
|
|
158
|
+
* Protect offers `open | warn | closed` and defaults to `warn`, which is right
|
|
159
|
+
* for a wallet: tell the person, let them decide. **`warn` is not offered
|
|
160
|
+
* here, because there is nobody to warn.** A warning delivered to an
|
|
161
|
+
* autonomous process is an allow that logs.
|
|
162
|
+
*
|
|
163
|
+
* - `open` — proceed on operator-set limits alone.
|
|
164
|
+
* - `escalate` — the local policy still applies and still binds; only the
|
|
165
|
+
* external risk check is missing, so a human decides. **Default.**
|
|
166
|
+
* - `closed` — stop.
|
|
167
|
+
*
|
|
168
|
+
* `escalate` is the default because it is the only one of the three that
|
|
169
|
+
* loses nothing. `open` signs unscreened transactions during exactly the
|
|
170
|
+
* window an attacker would choose to create. `closed` halts the agent
|
|
171
|
+
* completely, which for a liquidation or a market-making agent is its own
|
|
172
|
+
* loss and pushes operators to configure `open` to avoid it. `escalate`
|
|
173
|
+
* spends a human's attention instead of the treasury, and where an integrator
|
|
174
|
+
* has no approval path it degrades to a stop — the safe direction.
|
|
175
|
+
*
|
|
176
|
+
* Note that a local rule that *denies* still denies during an outage. An
|
|
177
|
+
* unreachable Decentrys removes one input; it does not suspend the operator's
|
|
178
|
+
* limits.
|
|
179
|
+
*/
|
|
180
|
+
export type AgentFailMode = 'open' | 'escalate' | 'closed';
|
|
181
|
+
/**
|
|
182
|
+
* Three outcomes, never two.
|
|
183
|
+
*
|
|
184
|
+
* Collapsing `require_human_approval` into `deny` loses the difference between
|
|
185
|
+
* "this must not happen" and "a person needs to look at this", and an operator
|
|
186
|
+
* who cannot see that difference in the log cannot tune the policy.
|
|
187
|
+
*/
|
|
188
|
+
export type AgentVerdict = 'allow' | 'require_human_approval' | 'deny';
|
|
189
|
+
/** The rules AgentGuard can apply. Stable identifiers — they end up in logs. */
|
|
190
|
+
export declare const AGENT_RULES: readonly ["AGENT_BINDING", "ACTION_TYPE", "CHAIN", "COUNTERPARTY", "CONTRACT", "TOKEN", "APPROVAL_ALLOWANCE", "VALUE_PER_ACTION", "CUMULATIVE_SPEND", "RATE_LIMIT", "RISK_LEVEL", "ASSESSMENT_AVAILABILITY", "HUMAN_APPROVAL_THRESHOLD"];
|
|
191
|
+
export type AgentRuleId = (typeof AGENT_RULES)[number];
|
|
192
|
+
/**
|
|
193
|
+
* `not_configured` and `not_applicable` are distinct and both are recorded.
|
|
194
|
+
*
|
|
195
|
+
* "There was no counterparty allowlist" and "there was one and this
|
|
196
|
+
* counterparty was on it" produce the same allow and mean completely
|
|
197
|
+
* different things to whoever reads the record afterwards.
|
|
198
|
+
*/
|
|
199
|
+
export type RuleOutcome = 'pass' | 'fail' | 'escalate' | 'not_applicable' | 'not_configured';
|
|
200
|
+
export interface RuleEvaluation {
|
|
201
|
+
rule: AgentRuleId;
|
|
202
|
+
outcome: RuleOutcome;
|
|
203
|
+
/** One sentence naming the limit and what was seen against it. */
|
|
204
|
+
statement: string;
|
|
205
|
+
/** The configured bound, when there was one. */
|
|
206
|
+
limit?: string | number;
|
|
207
|
+
/** What this action presented against that bound. */
|
|
208
|
+
observed?: string | number;
|
|
209
|
+
}
|
|
210
|
+
export interface AgentDecision {
|
|
211
|
+
/** Stable id for this decision, carried into the reservation and the log. */
|
|
212
|
+
decisionId: string;
|
|
213
|
+
agentId: string;
|
|
214
|
+
verdict: AgentVerdict;
|
|
215
|
+
/** The failing or escalating rules, in evaluation order. Empty on a clean allow. */
|
|
216
|
+
reasons: string[];
|
|
217
|
+
/**
|
|
218
|
+
* Every rule that was considered, passing ones included.
|
|
219
|
+
*
|
|
220
|
+
* This is what makes an *allow* explainable. A record that lists only what
|
|
221
|
+
* went wrong cannot answer "why was this permitted", which is the question
|
|
222
|
+
* asked after the money is gone.
|
|
223
|
+
*/
|
|
224
|
+
evaluations: RuleEvaluation[];
|
|
225
|
+
/** The Protect assessment this decision used, when one was obtained. */
|
|
226
|
+
assessment?: Assessment;
|
|
227
|
+
/** Why no assessment was available, when none was. */
|
|
228
|
+
assessmentUnavailable?: string;
|
|
229
|
+
/** Fingerprint of the sealed policy in force, so the record is reproducible. */
|
|
230
|
+
policyFingerprint: string;
|
|
231
|
+
policyVersion?: string;
|
|
232
|
+
/**
|
|
233
|
+
* Present on `allow` and `require_human_approval`. Value and rate budget is
|
|
234
|
+
* held against this id until it is confirmed or released; see `ledger.ts`.
|
|
235
|
+
*/
|
|
236
|
+
reservationId?: string;
|
|
237
|
+
/** A digest of the action, so a log entry can be tied to what was proposed. */
|
|
238
|
+
actionDigest: string;
|
|
239
|
+
modelVersion: string;
|
|
240
|
+
decidedAt: string;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* What actually happened to a reserved action.
|
|
244
|
+
*
|
|
245
|
+
* Recorded separately from the decision, because "AgentGuard allowed it" and
|
|
246
|
+
* "the agent then executed it" are different facts and an audit trail that
|
|
247
|
+
* conflates them cannot show an allow that was never used.
|
|
248
|
+
*/
|
|
249
|
+
export interface AgentOutcomeRecord {
|
|
250
|
+
decisionId: string;
|
|
251
|
+
state: 'confirmed' | 'released' | 'expired';
|
|
252
|
+
txHash?: string;
|
|
253
|
+
note?: string;
|
|
254
|
+
at: string;
|
|
255
|
+
}
|
|
256
|
+
//# sourceMappingURL=model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"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"}
|
package/dist/model.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* AgentGuard — the types.
|
|
4
|
+
*
|
|
5
|
+
* Protect informs a person, who then decides. AgentGuard sits in front of
|
|
6
|
+
* something with no judgement that will do exactly what it was told, at
|
|
7
|
+
* machine speed, as many times as its loop runs. Three consequences shape
|
|
8
|
+
* everything in this file:
|
|
9
|
+
*
|
|
10
|
+
* 1. **A warning is not a control.** Protect deliberately warns and never
|
|
11
|
+
* blocks, because the person holding the keys should decide. An agent
|
|
12
|
+
* cannot heed a warning — a warning it ignores is an allow with extra
|
|
13
|
+
* words. So AgentGuard's outcomes are `allow`, `require_human_approval`
|
|
14
|
+
* and `deny`, and the middle one means *stop and hand this to a person*.
|
|
15
|
+
*
|
|
16
|
+
* 2. **The operator is not the agent.** Limits are set by a human ahead of
|
|
17
|
+
* time. The agent must not be able to widen them — not by argument, not by
|
|
18
|
+
* mutating the object it was handed, not by passing an override. That is
|
|
19
|
+
* enforced structurally in `policy.ts` (`sealPolicy`, `narrowPolicy`),
|
|
20
|
+
* not by convention.
|
|
21
|
+
*
|
|
22
|
+
* 3. **Every decision must survive being questioned later.** "The agent spent
|
|
23
|
+
* the treasury" is answered by an audit record that names each rule, the
|
|
24
|
+
* limit it enforced and the value it observed. An allow that cannot be
|
|
25
|
+
* explained is as bad as a wrong one, so a passing rule is recorded just
|
|
26
|
+
* as loudly as a failing one.
|
|
27
|
+
*
|
|
28
|
+
* The risk model is *not* reimplemented here. Classification, the seven risk
|
|
29
|
+
* levels and `applyPolicy` come from `@decentrys/protect` unchanged; this
|
|
30
|
+
* package adds the limits an autonomous actor needs and a human does not.
|
|
31
|
+
*/
|
|
32
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
33
|
+
exports.AGENT_RULES = exports.AGENT_ACTION_TYPES = exports.AGENT_MODEL_VERSION = void 0;
|
|
34
|
+
exports.AGENT_MODEL_VERSION = 'agent-1.0.0';
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// What an agent proposes to do
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
/**
|
|
39
|
+
* The kinds of action a policy can talk about.
|
|
40
|
+
*
|
|
41
|
+
* These are coarse on purpose. "May swap, may not approve" is a sentence an
|
|
42
|
+
* operator can write and check; "may call selector 0x095ea7b3" is not. An
|
|
43
|
+
* action whose type the integrator cannot determine is `unknown`, which is a
|
|
44
|
+
* first-class value rather than a default — see `deniedActions` below.
|
|
45
|
+
*/
|
|
46
|
+
exports.AGENT_ACTION_TYPES = [
|
|
47
|
+
'transfer',
|
|
48
|
+
'swap',
|
|
49
|
+
'approve',
|
|
50
|
+
'contract_call',
|
|
51
|
+
'bridge',
|
|
52
|
+
'stake',
|
|
53
|
+
'unstake',
|
|
54
|
+
'deploy',
|
|
55
|
+
'sign_message',
|
|
56
|
+
'unknown',
|
|
57
|
+
];
|
|
58
|
+
/** The rules AgentGuard can apply. Stable identifiers — they end up in logs. */
|
|
59
|
+
exports.AGENT_RULES = [
|
|
60
|
+
'AGENT_BINDING',
|
|
61
|
+
'ACTION_TYPE',
|
|
62
|
+
'CHAIN',
|
|
63
|
+
'COUNTERPARTY',
|
|
64
|
+
'CONTRACT',
|
|
65
|
+
'TOKEN',
|
|
66
|
+
'APPROVAL_ALLOWANCE',
|
|
67
|
+
'VALUE_PER_ACTION',
|
|
68
|
+
'CUMULATIVE_SPEND',
|
|
69
|
+
'RATE_LIMIT',
|
|
70
|
+
'RISK_LEVEL',
|
|
71
|
+
'ASSESSMENT_AVAILABILITY',
|
|
72
|
+
'HUMAN_APPROVAL_THRESHOLD',
|
|
73
|
+
];
|
|
74
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;;;AAIU,QAAA,mBAAmB,GAAG,aAAa,CAAC;AAEjD,8EAA8E;AAC9E,+BAA+B;AAC/B,8EAA8E;AAE9E;;;;;;;GAOG;AACU,QAAA,kBAAkB,GAAG;IAChC,UAAU;IACV,MAAM;IACN,SAAS;IACT,eAAe;IACf,QAAQ;IACR,OAAO;IACP,SAAS;IACT,QAAQ;IACR,cAAc;IACd,SAAS;CACD,CAAC;AAqLX,gFAAgF;AACnE,QAAA,WAAW,GAAG;IACzB,eAAe;IACf,aAAa;IACb,OAAO;IACP,cAAc;IACd,UAAU;IACV,OAAO;IACP,oBAAoB;IACpB,kBAAkB;IAClB,kBAAkB;IAClB,YAAY;IACZ,YAAY;IACZ,yBAAyB;IACzB,0BAA0B;CAClB,CAAC"}
|
package/dist/policy.d.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The policy engine.
|
|
3
|
+
*
|
|
4
|
+
* Two things live here, and keeping them together is deliberate.
|
|
5
|
+
*
|
|
6
|
+
* **Sealing** is how "the agent cannot widen its own limits" stops being a
|
|
7
|
+
* convention. An operator writes an `AgentPolicy` and calls `sealPolicy()`.
|
|
8
|
+
* What comes back is deep-frozen, cloned away from the caller's own arrays,
|
|
9
|
+
* and carries a fingerprint of its contents. `AgentGuard` accepts nothing
|
|
10
|
+
* else, exposes no setter, and records the fingerprint on every decision. An
|
|
11
|
+
* agent handed a reference to that object can read it and cannot change it,
|
|
12
|
+
* and a policy that changed between two decisions cannot pretend it did not.
|
|
13
|
+
*
|
|
14
|
+
* The only way to derive a new policy is `narrowPolicy()`, which takes the
|
|
15
|
+
* stricter of every field. There is no widening path in this file at all —
|
|
16
|
+
* not a guarded one, not an admin one. A capability that does not exist
|
|
17
|
+
* cannot be reached by a sufficiently persuasive prompt.
|
|
18
|
+
*
|
|
19
|
+
* **Evaluation** is a pure function. `evaluatePolicy()` performs no I/O and
|
|
20
|
+
* reads no clock it was not given, so the same action, policy, assessment and
|
|
21
|
+
* usage always produce the same decision. That is what makes an audit record
|
|
22
|
+
* checkable rather than merely readable: an investigator can re-run it.
|
|
23
|
+
*/
|
|
24
|
+
import { type Assessment, type Policy } from '@decentrys/protect';
|
|
25
|
+
import { type AgentAction, type AgentDecision, type AgentPolicy } from './model';
|
|
26
|
+
import type { AgentUsage } from './ledger';
|
|
27
|
+
/**
|
|
28
|
+
* How risk levels map to behaviour when nobody is watching.
|
|
29
|
+
*
|
|
30
|
+
* Protect's consumer default warns from CAUTION upward and blocks only
|
|
31
|
+
* `KNOWN_MALICIOUS`, because a person is reading the warning. Neither half of
|
|
32
|
+
* that transfers:
|
|
33
|
+
*
|
|
34
|
+
* - **`warn` is not available to an agent.** It is mapped out entirely; see
|
|
35
|
+
* `sealPolicy`, which refuses a `warn` in an agent risk policy rather than
|
|
36
|
+
* quietly reinterpreting it.
|
|
37
|
+
* - **CAUTION stays `allow`, and that is not laziness.** CAUTION is reached
|
|
38
|
+
* by a single SIGNIFICANT *capability* — a pausable token, an upgradeable
|
|
39
|
+
* router. Most serious protocols have one. Escalating there would send a
|
|
40
|
+
* human hundreds of approvals a day, and a human who approves 500
|
|
41
|
+
* escalations approves the 501st unread. The Sentinel stage recorded the
|
|
42
|
+
* same failure for alerts; it is worse here, because the 501st is the one
|
|
43
|
+
* that empties the treasury. The capability is still recorded on the
|
|
44
|
+
* decision, so it is visible without being an interrupt.
|
|
45
|
+
* - **ELEVATED_RISK upward escalates**, because at that point actual threat
|
|
46
|
+
* signals with evidence exist and a person can weigh them.
|
|
47
|
+
* - **`KNOWN_MALICIOUS` denies, and a denial is not escalatable.** It is the
|
|
48
|
+
* one level that requires analyst-verified evidence, and there is no value
|
|
49
|
+
* at which signing to confirmed malicious infrastructure is the right call.
|
|
50
|
+
*
|
|
51
|
+
* `CRITICAL_THREAT` escalates rather than denies for the reason Protect draws
|
|
52
|
+
* the same line: severe evidence that no analyst has confirmed is exactly the
|
|
53
|
+
* case where a person should look at the evidence.
|
|
54
|
+
*/
|
|
55
|
+
export declare const DEFAULT_AGENT_RISK_POLICY: Required<Policy>;
|
|
56
|
+
/**
|
|
57
|
+
* The brand, as a real runtime symbol rather than a `declare`d type-only one.
|
|
58
|
+
*
|
|
59
|
+
* A type-only brand is erased at compile time, so an object cast to
|
|
60
|
+
* `SealedPolicy` would satisfy every check and carry no mark at all. This
|
|
61
|
+
* symbol is module-private and never exported, so the only way to obtain an
|
|
62
|
+
* object bearing it is to call `sealPolicy()` — which clones and freezes.
|
|
63
|
+
*/
|
|
64
|
+
declare const SEALED_POLICY: unique symbol;
|
|
65
|
+
export interface SealedPolicy {
|
|
66
|
+
/**
|
|
67
|
+
* Structural, not decorative. A plain object literal is not assignable to
|
|
68
|
+
* `SealedPolicy`, so there is no way to hand `AgentGuard` a policy that did
|
|
69
|
+
* not go through `sealPolicy()` — including from inside a tool an agent
|
|
70
|
+
* controls.
|
|
71
|
+
*/
|
|
72
|
+
readonly [SEALED_POLICY]: true;
|
|
73
|
+
readonly policy: Readonly<AgentPolicy>;
|
|
74
|
+
/**
|
|
75
|
+
* A content fingerprint. Not a cryptographic commitment — it exists so two
|
|
76
|
+
* decisions can be shown to have run under the same rules, and so a policy
|
|
77
|
+
* that changed mid-incident cannot be described afterwards as unchanged.
|
|
78
|
+
*/
|
|
79
|
+
readonly fingerprint: string;
|
|
80
|
+
/**
|
|
81
|
+
* Configurations that are legal but deserve to be said out loud — a policy
|
|
82
|
+
* that permits confirmed-malicious counterparties, or one with no limits at
|
|
83
|
+
* all. Surfaced rather than refused: policy belongs to the integrator.
|
|
84
|
+
*/
|
|
85
|
+
readonly warnings: readonly string[];
|
|
86
|
+
}
|
|
87
|
+
export declare function sealPolicy(policy: AgentPolicy): SealedPolicy;
|
|
88
|
+
/**
|
|
89
|
+
* Derive a stricter policy. There is no counterpart that loosens one.
|
|
90
|
+
*
|
|
91
|
+
* Every field is combined by taking the tighter side: caps become the minimum,
|
|
92
|
+
* allowlists intersect, blocklists union, windows accumulate, and
|
|
93
|
+
* `allowUnlimitedApprovals` is true only if both sides say so. So a
|
|
94
|
+
* restriction supplied at call time — including one an agent composed itself —
|
|
95
|
+
* can only ever reduce what is permitted.
|
|
96
|
+
*/
|
|
97
|
+
export declare function narrowPolicy(base: SealedPolicy, restriction: AgentPolicy): SealedPolicy;
|
|
98
|
+
export interface EvaluatePolicyInput {
|
|
99
|
+
action: AgentAction;
|
|
100
|
+
policy: SealedPolicy;
|
|
101
|
+
/** The Protect assessment, when one was obtained. */
|
|
102
|
+
assessment?: Assessment | null;
|
|
103
|
+
/** Why no assessment was obtained. Drives the `failMode` rule. */
|
|
104
|
+
assessmentUnavailable?: string;
|
|
105
|
+
/** Committed spend and admitted actions so far. Absent means none recorded. */
|
|
106
|
+
usage?: AgentUsage;
|
|
107
|
+
now?: Date;
|
|
108
|
+
/** Supplied by the caller so a decision and its reservation share an id. */
|
|
109
|
+
decisionId?: string;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Evaluate an action against a sealed policy.
|
|
113
|
+
*
|
|
114
|
+
* Every rule is evaluated, including after one has already failed. Stopping at
|
|
115
|
+
* the first failure would produce a record saying an action exceeded its value
|
|
116
|
+
* cap while omitting that its counterparty was also confirmed malicious — and
|
|
117
|
+
* an operator reading that would tune the cap and ship the same hole.
|
|
118
|
+
*
|
|
119
|
+
* The verdict is the worst outcome any rule reached: one `fail` denies, one
|
|
120
|
+
* `escalate` sends it to a person, and everything else allows.
|
|
121
|
+
*/
|
|
122
|
+
export declare function evaluatePolicy(input: EvaluatePolicyInput): AgentDecision;
|
|
123
|
+
/** `unlimited`, or a number large enough that no real supply could exhaust it. */
|
|
124
|
+
export declare function isUnlimitedAllowance(amount: string): boolean;
|
|
125
|
+
export declare function describeDuration(ms: number): string;
|
|
126
|
+
/**
|
|
127
|
+
* FNV-1a over canonical JSON.
|
|
128
|
+
*
|
|
129
|
+
* A change detector, not a commitment. It answers "were these two decisions
|
|
130
|
+
* made under the same rules" without a dependency and without a claim it
|
|
131
|
+
* cannot support; anyone needing tamper evidence should sign the record.
|
|
132
|
+
*/
|
|
133
|
+
export declare function fingerprint(value: unknown): string;
|
|
134
|
+
/** `crypto.randomUUID` where it exists; a monotonic fallback everywhere else. */
|
|
135
|
+
export declare function newDecisionId(): string;
|
|
136
|
+
export {};
|
|
137
|
+
//# sourceMappingURL=policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../src/policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAEL,KAAK,UAAU,EAAE,KAAK,MAAM,EAC7B,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAEL,KAAK,WAAW,EAAwB,KAAK,aAAa,EAC1D,KAAK,WAAW,EAEjB,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAM3C;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,yBAAyB,EAAE,QAAQ,CAAC,MAAM,CAQtD,CAAC;AAqBF;;;;;;;GAOG;AACH,QAAA,MAAM,aAAa,EAAE,OAAO,MAA+C,CAAC;AAE5E,MAAM,WAAW,YAAY;IAC3B;;;;;OAKG;IACH,QAAQ,CAAC,CAAC,aAAa,CAAC,EAAE,IAAI,CAAC;IAC/B,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,WAAW,CAAC,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,WAAW,GAAG,YAAY,CAwC5D;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,WAAW,GAAG,YAAY,CAgCvF;AAMD,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,WAAW,CAAC;IACpB,MAAM,EAAE,YAAY,CAAC;IACrB,qDAAqD;IACrD,UAAU,CAAC,EAAE,UAAU,GAAG,IAAI,CAAC;IAC/B,kEAAkE;IAClE,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,+EAA+E;IAC/E,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,GAAG,CAAC,EAAE,IAAI,CAAC;IACX,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,mBAAmB,GAAG,aAAa,CA2ExE;AA4LD,kFAAkF;AAClF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAY5D;AAoPD,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAMnD;AAiJD;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAQlD;AAaD,iFAAiF;AACjF,wBAAgB,aAAa,IAAI,MAAM,CAKtC"}
|