@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/client.d.ts
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AgentGuard — the client an operator wires in front of an agent.
|
|
3
|
+
*
|
|
4
|
+
* The contract differs from both of its neighbours, and the difference is the
|
|
5
|
+
* product.
|
|
6
|
+
*
|
|
7
|
+
* - **Protect never throws and never blocks.** It sits between a person and a
|
|
8
|
+
* signing screen; a security service having a bad minute must not cost
|
|
9
|
+
* someone their transaction, and the person decides.
|
|
10
|
+
* - **Sentinel throws.** It is management tooling, and an operator who
|
|
11
|
+
* believes they are monitored when they are not is worse off than one who
|
|
12
|
+
* saw an error.
|
|
13
|
+
* - **AgentGuard returns a verdict, and the verdict can be `deny`.** Nothing
|
|
14
|
+
* here throws on a call path either — an exception thrown at an agent is a
|
|
15
|
+
* condition it will handle by retrying, or by taking the branch that skips
|
|
16
|
+
* the guard. A decision object it must read is harder to route around.
|
|
17
|
+
*
|
|
18
|
+
* The one place this does throw is the constructor, on a missing credential or
|
|
19
|
+
* an unsealed policy. That is a wiring error made by a human at deploy time,
|
|
20
|
+
* not an event during an agent's run.
|
|
21
|
+
*/
|
|
22
|
+
import { Decentrys, type Assessment, type CallOptions, type FetchLike, type Transport } from '@decentrys/protect';
|
|
23
|
+
import type { AgentAction, AgentDecision, AgentOutcomeRecord, AgentPolicy } from './model';
|
|
24
|
+
import { type SealedPolicy } from './policy';
|
|
25
|
+
import { type AgentUsage, type ReservationLedger } from './ledger';
|
|
26
|
+
import { type AgentActionExplanation } from './explain';
|
|
27
|
+
export declare const AGENT_SDK_VERSION = "0.1.0";
|
|
28
|
+
export interface AgentGuardConfig {
|
|
29
|
+
/**
|
|
30
|
+
* The operator's policy, already sealed.
|
|
31
|
+
*
|
|
32
|
+
* Typed as `SealedPolicy`, so a plain object cannot be passed here — an
|
|
33
|
+
* agent that constructs its own limits and hands them over does not
|
|
34
|
+
* typecheck and would not survive `sealPolicy`'s cloning if it did.
|
|
35
|
+
*/
|
|
36
|
+
policy: SealedPolicy;
|
|
37
|
+
/** An existing Protect client. Supply this or `apiKey`. */
|
|
38
|
+
protect?: Decentrys;
|
|
39
|
+
apiKey?: string;
|
|
40
|
+
baseUrl?: string;
|
|
41
|
+
timeoutMs?: number;
|
|
42
|
+
retries?: number;
|
|
43
|
+
fetch?: FetchLike;
|
|
44
|
+
/** For tests, and for operators routing through their own gateway. */
|
|
45
|
+
transport?: Transport;
|
|
46
|
+
/**
|
|
47
|
+
* Where cumulative spend and rate state lives. Defaults to an in-process
|
|
48
|
+
* `SpendLedger` — read its file comment before running more than one
|
|
49
|
+
* instance, because a per-process cap on N processes is an N-times cap.
|
|
50
|
+
*
|
|
51
|
+
* A source that can only be read (see `ReservationLedger`) disables holds;
|
|
52
|
+
* `reservesBudget` reports which of the two this guard got.
|
|
53
|
+
*/
|
|
54
|
+
ledger?: AgentUsage | ReservationLedger;
|
|
55
|
+
/** How many decisions to keep in memory for `auditLog()` / `explain()`. */
|
|
56
|
+
auditLogEntries?: number;
|
|
57
|
+
/**
|
|
58
|
+
* Called with every decision, before it is returned.
|
|
59
|
+
*
|
|
60
|
+
* The in-memory log is a convenience, not an audit trail: it dies with the
|
|
61
|
+
* process. This hook is where a real one is written. It is invoked
|
|
62
|
+
* synchronously and its exceptions are swallowed — a logging sink must not
|
|
63
|
+
* be able to change a verdict, in either direction.
|
|
64
|
+
*/
|
|
65
|
+
onDecision?: (decision: AgentDecision) => void;
|
|
66
|
+
}
|
|
67
|
+
export interface AssessAgentTransactionOptions extends CallOptions {
|
|
68
|
+
/**
|
|
69
|
+
* A per-call restriction. It can only *narrow* — see `narrowPolicy`. There
|
|
70
|
+
* is no parameter anywhere in this class that widens a policy, which is what
|
|
71
|
+
* makes "the agent cannot raise its own limits" a property of the code
|
|
72
|
+
* rather than a rule someone has to remember.
|
|
73
|
+
*/
|
|
74
|
+
restrict?: AgentPolicy;
|
|
75
|
+
/** An assessment the caller already holds, to avoid a second round trip. */
|
|
76
|
+
assessment?: Assessment;
|
|
77
|
+
now?: Date;
|
|
78
|
+
}
|
|
79
|
+
interface AuditEntry {
|
|
80
|
+
decision: AgentDecision;
|
|
81
|
+
outcome?: AgentOutcomeRecord;
|
|
82
|
+
}
|
|
83
|
+
export declare class AgentGuard {
|
|
84
|
+
private readonly protect;
|
|
85
|
+
private readonly sealed;
|
|
86
|
+
private readonly ledgerImpl;
|
|
87
|
+
private readonly reserving;
|
|
88
|
+
private readonly auditEntries;
|
|
89
|
+
private readonly auditLimit;
|
|
90
|
+
private readonly onDecision;
|
|
91
|
+
constructor(config: AgentGuardConfig);
|
|
92
|
+
/** The policy in force. Frozen — reading it is safe, changing it is impossible. */
|
|
93
|
+
get policy(): SealedPolicy;
|
|
94
|
+
/** Configurations that are legal but worth an operator's attention. */
|
|
95
|
+
get policyWarnings(): readonly string[];
|
|
96
|
+
/**
|
|
97
|
+
* Whether admitted actions hold budget before they execute.
|
|
98
|
+
*
|
|
99
|
+
* False when the guard was given a read-only usage source. Cumulative caps
|
|
100
|
+
* then count settled history only, so two actions that each fit under the
|
|
101
|
+
* cap can both be admitted and together exceed it. Exposed rather than
|
|
102
|
+
* assumed, because the difference is invisible until it costs something.
|
|
103
|
+
*/
|
|
104
|
+
get reservesBudget(): boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Assess a proposed action and decide whether the agent may sign it.
|
|
107
|
+
*
|
|
108
|
+
* Screening happens first and always — including when a local rule has
|
|
109
|
+
* already failed. A record saying an action exceeded its value cap, while
|
|
110
|
+
* omitting that its counterparty was confirmed malicious, would send an
|
|
111
|
+
* operator to raise the cap and ship the same hole.
|
|
112
|
+
*
|
|
113
|
+
* On `allow` or `require_human_approval` the action's value and its slot in
|
|
114
|
+
* the rate window are held immediately; call `confirm()` when it lands or
|
|
115
|
+
* `release()` when it does not. The hold is taken synchronously in the same
|
|
116
|
+
* turn as the evaluation, so two concurrent calls in one process cannot both
|
|
117
|
+
* pass a cap that only one of them fits under. Across processes they can —
|
|
118
|
+
* see `ledger.ts`, which says so plainly.
|
|
119
|
+
*/
|
|
120
|
+
assessAgentTransaction(action: AgentAction, options?: AssessAgentTransactionOptions): Promise<AgentDecision>;
|
|
121
|
+
/**
|
|
122
|
+
* Decide without going to the network.
|
|
123
|
+
*
|
|
124
|
+
* For an operator replaying a decision against a different policy, and for
|
|
125
|
+
* an agent runtime that already holds an assessment. It applies the same
|
|
126
|
+
* rules; what it cannot do is discover that a counterparty was labelled
|
|
127
|
+
* since the assessment was taken.
|
|
128
|
+
*/
|
|
129
|
+
evaluate(action: AgentAction, options?: {
|
|
130
|
+
assessment?: Assessment;
|
|
131
|
+
assessmentUnavailable?: string;
|
|
132
|
+
restrict?: AgentPolicy;
|
|
133
|
+
now?: Date;
|
|
134
|
+
}): AgentDecision;
|
|
135
|
+
/** The agent signed and broadcast it. The held value becomes committed. */
|
|
136
|
+
confirm(decisionId: string, options?: {
|
|
137
|
+
txHash?: string;
|
|
138
|
+
now?: Date;
|
|
139
|
+
}): AgentOutcomeRecord | null;
|
|
140
|
+
/** The agent did not proceed. The held value returns to the budget. */
|
|
141
|
+
release(decisionId: string, options?: {
|
|
142
|
+
note?: string;
|
|
143
|
+
now?: Date;
|
|
144
|
+
}): AgentOutcomeRecord | null;
|
|
145
|
+
/**
|
|
146
|
+
* Explain a decision, by id or by value.
|
|
147
|
+
*
|
|
148
|
+
* Returns `null` for an id this process never made or no longer holds —
|
|
149
|
+
* stated rather than reconstructed, because an explanation assembled from a
|
|
150
|
+
* decision that is no longer in hand is a guess wearing a record's clothes.
|
|
151
|
+
*/
|
|
152
|
+
explain(decision: AgentDecision | string): AgentActionExplanation | null;
|
|
153
|
+
/**
|
|
154
|
+
* Recent decisions, newest last.
|
|
155
|
+
*
|
|
156
|
+
* In memory and bounded; it does not survive a restart. Use `onDecision` for
|
|
157
|
+
* a trail that does. Saying which of the two this is matters — an operator
|
|
158
|
+
* who believes this is the audit log will discover otherwise at the worst
|
|
159
|
+
* possible moment.
|
|
160
|
+
*/
|
|
161
|
+
auditLog(filter?: {
|
|
162
|
+
agentId?: string;
|
|
163
|
+
verdict?: AgentDecision['verdict'];
|
|
164
|
+
}): AuditEntry[];
|
|
165
|
+
/**
|
|
166
|
+
* A guard for a narrower task, sharing this one's ledger and client.
|
|
167
|
+
*
|
|
168
|
+
* The shared ledger is the point: a sub-task that spends against a tighter
|
|
169
|
+
* per-task cap still spends against the agent's daily one.
|
|
170
|
+
*/
|
|
171
|
+
withPolicy(restriction: AgentPolicy): AgentGuard;
|
|
172
|
+
/**
|
|
173
|
+
* Route the action to the right Protect endpoint.
|
|
174
|
+
*
|
|
175
|
+
* An approval is assessed on its spender *and its allowance together*,
|
|
176
|
+
* which is a different question from "is this transaction dangerous" and
|
|
177
|
+
* the one an unlimited approval turns on.
|
|
178
|
+
*/
|
|
179
|
+
private screen;
|
|
180
|
+
private record;
|
|
181
|
+
private attachOutcome;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Distinguish "Decentrys answered, and found nothing" from "Decentrys did not
|
|
185
|
+
* answer".
|
|
186
|
+
*
|
|
187
|
+
* Protect never throws: an unreachable service produces an assessment whose
|
|
188
|
+
* `unknowns` say so. Both of those are `NO_CRITICAL_RISK_DETECTED` on the
|
|
189
|
+
* surface, and for an autonomous signer treating the second as the first is
|
|
190
|
+
* the whole failure — it is a clean bill of health issued by an outage.
|
|
191
|
+
*/
|
|
192
|
+
export declare function unavailableReason(assessment: Assessment | undefined): string | undefined;
|
|
193
|
+
export {};
|
|
194
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* AgentGuard — the client an operator wires in front of an agent.
|
|
4
|
+
*
|
|
5
|
+
* The contract differs from both of its neighbours, and the difference is the
|
|
6
|
+
* product.
|
|
7
|
+
*
|
|
8
|
+
* - **Protect never throws and never blocks.** It sits between a person and a
|
|
9
|
+
* signing screen; a security service having a bad minute must not cost
|
|
10
|
+
* someone their transaction, and the person decides.
|
|
11
|
+
* - **Sentinel throws.** It is management tooling, and an operator who
|
|
12
|
+
* believes they are monitored when they are not is worse off than one who
|
|
13
|
+
* saw an error.
|
|
14
|
+
* - **AgentGuard returns a verdict, and the verdict can be `deny`.** Nothing
|
|
15
|
+
* here throws on a call path either — an exception thrown at an agent is a
|
|
16
|
+
* condition it will handle by retrying, or by taking the branch that skips
|
|
17
|
+
* the guard. A decision object it must read is harder to route around.
|
|
18
|
+
*
|
|
19
|
+
* The one place this does throw is the constructor, on a missing credential or
|
|
20
|
+
* an unsealed policy. That is a wiring error made by a human at deploy time,
|
|
21
|
+
* not an event during an agent's run.
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.AgentGuard = exports.AGENT_SDK_VERSION = void 0;
|
|
25
|
+
exports.unavailableReason = unavailableReason;
|
|
26
|
+
const protect_1 = require("@decentrys/protect");
|
|
27
|
+
const policy_1 = require("./policy");
|
|
28
|
+
const ledger_1 = require("./ledger");
|
|
29
|
+
const explain_1 = require("./explain");
|
|
30
|
+
exports.AGENT_SDK_VERSION = '0.1.0';
|
|
31
|
+
const DEFAULT_AUDIT_LOG_ENTRIES = 1_000;
|
|
32
|
+
class AgentGuard {
|
|
33
|
+
protect;
|
|
34
|
+
sealed;
|
|
35
|
+
ledgerImpl;
|
|
36
|
+
reserving;
|
|
37
|
+
auditEntries = [];
|
|
38
|
+
auditLimit;
|
|
39
|
+
onDecision;
|
|
40
|
+
constructor(config) {
|
|
41
|
+
if (!config.policy || typeof config.policy !== 'object' || typeof config.policy.fingerprint !== 'string') {
|
|
42
|
+
throw new Error('AgentGuard: a sealed policy is required. Build one with sealPolicy({ ... }) at wiring time, '
|
|
43
|
+
+ 'from a human-owned configuration — not from anything the agent produces.');
|
|
44
|
+
}
|
|
45
|
+
if (config.protect) {
|
|
46
|
+
this.protect = config.protect;
|
|
47
|
+
}
|
|
48
|
+
else if (config.apiKey || config.transport) {
|
|
49
|
+
const protectConfig = {
|
|
50
|
+
apiKey: config.apiKey ?? 'transport-provided',
|
|
51
|
+
// Protect's own failMode only shapes the wording of an unavailable
|
|
52
|
+
// assessment; AgentGuard's `failMode` decides what actually happens.
|
|
53
|
+
// Pinning it to `open` keeps the two from being applied twice.
|
|
54
|
+
failMode: 'open',
|
|
55
|
+
...(config.baseUrl === undefined ? {} : { baseUrl: config.baseUrl }),
|
|
56
|
+
...(config.timeoutMs === undefined ? {} : { timeoutMs: config.timeoutMs }),
|
|
57
|
+
...(config.retries === undefined ? {} : { retries: config.retries }),
|
|
58
|
+
...(config.fetch === undefined ? {} : { fetch: config.fetch }),
|
|
59
|
+
...(config.transport === undefined ? {} : { transport: config.transport }),
|
|
60
|
+
};
|
|
61
|
+
this.protect = new protect_1.Decentrys(protectConfig);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
throw new Error('AgentGuard: pass an apiKey or an existing Decentrys client. Screening a counterparty is the '
|
|
65
|
+
+ 'part of this that no local policy can do — an allowlist cannot tell you the address on it '
|
|
66
|
+
+ 'was labelled malicious yesterday.');
|
|
67
|
+
}
|
|
68
|
+
this.sealed = config.policy;
|
|
69
|
+
this.ledgerImpl = config.ledger ?? new ledger_1.SpendLedger();
|
|
70
|
+
this.reserving = (0, ledger_1.isReservationLedger)(this.ledgerImpl) ? this.ledgerImpl : null;
|
|
71
|
+
this.auditLimit = config.auditLogEntries ?? DEFAULT_AUDIT_LOG_ENTRIES;
|
|
72
|
+
this.onDecision = config.onDecision;
|
|
73
|
+
}
|
|
74
|
+
/** The policy in force. Frozen — reading it is safe, changing it is impossible. */
|
|
75
|
+
get policy() {
|
|
76
|
+
return this.sealed;
|
|
77
|
+
}
|
|
78
|
+
/** Configurations that are legal but worth an operator's attention. */
|
|
79
|
+
get policyWarnings() {
|
|
80
|
+
return this.sealed.warnings;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Whether admitted actions hold budget before they execute.
|
|
84
|
+
*
|
|
85
|
+
* False when the guard was given a read-only usage source. Cumulative caps
|
|
86
|
+
* then count settled history only, so two actions that each fit under the
|
|
87
|
+
* cap can both be admitted and together exceed it. Exposed rather than
|
|
88
|
+
* assumed, because the difference is invisible until it costs something.
|
|
89
|
+
*/
|
|
90
|
+
get reservesBudget() {
|
|
91
|
+
return this.reserving !== null;
|
|
92
|
+
}
|
|
93
|
+
// -------------------------------------------------------------------------
|
|
94
|
+
// The main path
|
|
95
|
+
// -------------------------------------------------------------------------
|
|
96
|
+
/**
|
|
97
|
+
* Assess a proposed action and decide whether the agent may sign it.
|
|
98
|
+
*
|
|
99
|
+
* Screening happens first and always — including when a local rule has
|
|
100
|
+
* already failed. A record saying an action exceeded its value cap, while
|
|
101
|
+
* omitting that its counterparty was confirmed malicious, would send an
|
|
102
|
+
* operator to raise the cap and ship the same hole.
|
|
103
|
+
*
|
|
104
|
+
* On `allow` or `require_human_approval` the action's value and its slot in
|
|
105
|
+
* the rate window are held immediately; call `confirm()` when it lands or
|
|
106
|
+
* `release()` when it does not. The hold is taken synchronously in the same
|
|
107
|
+
* turn as the evaluation, so two concurrent calls in one process cannot both
|
|
108
|
+
* pass a cap that only one of them fits under. Across processes they can —
|
|
109
|
+
* see `ledger.ts`, which says so plainly.
|
|
110
|
+
*/
|
|
111
|
+
async assessAgentTransaction(action, options = {}) {
|
|
112
|
+
const policy = options.restrict ? (0, policy_1.narrowPolicy)(this.sealed, options.restrict) : this.sealed;
|
|
113
|
+
const decisionId = (0, policy_1.newDecisionId)();
|
|
114
|
+
let assessment = options.assessment;
|
|
115
|
+
if (!assessment) {
|
|
116
|
+
assessment = await this.screen(action, options);
|
|
117
|
+
}
|
|
118
|
+
const unavailable = unavailableReason(assessment);
|
|
119
|
+
// Everything from here down is synchronous. That is deliberate: an `await`
|
|
120
|
+
// between the evaluation and the reservation is the gap through which an
|
|
121
|
+
// agent's parallel calls each see the same untouched budget.
|
|
122
|
+
const decision = (0, policy_1.evaluatePolicy)({
|
|
123
|
+
action,
|
|
124
|
+
policy,
|
|
125
|
+
assessment: unavailable ? null : assessment,
|
|
126
|
+
...(unavailable === undefined ? {} : { assessmentUnavailable: unavailable }),
|
|
127
|
+
usage: this.ledgerImpl,
|
|
128
|
+
decisionId,
|
|
129
|
+
...(options.now === undefined ? {} : { now: options.now }),
|
|
130
|
+
});
|
|
131
|
+
if (decision.verdict !== 'deny' && this.reserving) {
|
|
132
|
+
const reservation = this.reserving.reserve({
|
|
133
|
+
decisionId,
|
|
134
|
+
agentId: action.agentId,
|
|
135
|
+
// Reaching here with no stated value means no value-based rule was
|
|
136
|
+
// configured; if one had been, this would already be a denial.
|
|
137
|
+
valueUsd: action.valueUsd ?? 0,
|
|
138
|
+
...(action.idempotencyKey === undefined ? {} : { idempotencyKey: action.idempotencyKey }),
|
|
139
|
+
...(options.now === undefined ? {} : { now: options.now }),
|
|
140
|
+
});
|
|
141
|
+
decision.reservationId = reservation.decisionId;
|
|
142
|
+
}
|
|
143
|
+
this.record(decision);
|
|
144
|
+
return decision;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Decide without going to the network.
|
|
148
|
+
*
|
|
149
|
+
* For an operator replaying a decision against a different policy, and for
|
|
150
|
+
* an agent runtime that already holds an assessment. It applies the same
|
|
151
|
+
* rules; what it cannot do is discover that a counterparty was labelled
|
|
152
|
+
* since the assessment was taken.
|
|
153
|
+
*/
|
|
154
|
+
evaluate(action, options = {}) {
|
|
155
|
+
const policy = options.restrict ? (0, policy_1.narrowPolicy)(this.sealed, options.restrict) : this.sealed;
|
|
156
|
+
const decision = (0, policy_1.evaluatePolicy)({
|
|
157
|
+
action,
|
|
158
|
+
policy,
|
|
159
|
+
assessment: options.assessment ?? null,
|
|
160
|
+
...(options.assessment
|
|
161
|
+
? {}
|
|
162
|
+
: { assessmentUnavailable: options.assessmentUnavailable ?? 'no assessment was supplied to evaluate()' }),
|
|
163
|
+
usage: this.ledgerImpl,
|
|
164
|
+
...(options.now === undefined ? {} : { now: options.now }),
|
|
165
|
+
});
|
|
166
|
+
this.record(decision);
|
|
167
|
+
return decision;
|
|
168
|
+
}
|
|
169
|
+
// -------------------------------------------------------------------------
|
|
170
|
+
// Settling
|
|
171
|
+
// -------------------------------------------------------------------------
|
|
172
|
+
/** The agent signed and broadcast it. The held value becomes committed. */
|
|
173
|
+
confirm(decisionId, options = {}) {
|
|
174
|
+
const outcome = this.reserving?.confirm(decisionId, options) ?? null;
|
|
175
|
+
if (outcome)
|
|
176
|
+
this.attachOutcome(outcome);
|
|
177
|
+
return outcome;
|
|
178
|
+
}
|
|
179
|
+
/** The agent did not proceed. The held value returns to the budget. */
|
|
180
|
+
release(decisionId, options = {}) {
|
|
181
|
+
const outcome = this.reserving?.release(decisionId, options) ?? null;
|
|
182
|
+
if (outcome)
|
|
183
|
+
this.attachOutcome(outcome);
|
|
184
|
+
return outcome;
|
|
185
|
+
}
|
|
186
|
+
// -------------------------------------------------------------------------
|
|
187
|
+
// The trail
|
|
188
|
+
// -------------------------------------------------------------------------
|
|
189
|
+
/**
|
|
190
|
+
* Explain a decision, by id or by value.
|
|
191
|
+
*
|
|
192
|
+
* Returns `null` for an id this process never made or no longer holds —
|
|
193
|
+
* stated rather than reconstructed, because an explanation assembled from a
|
|
194
|
+
* decision that is no longer in hand is a guess wearing a record's clothes.
|
|
195
|
+
*/
|
|
196
|
+
explain(decision) {
|
|
197
|
+
if (typeof decision !== 'string')
|
|
198
|
+
return (0, explain_1.explainAgentAction)(decision);
|
|
199
|
+
const entry = this.auditEntries.find((e) => e.decision.decisionId === decision);
|
|
200
|
+
return entry ? (0, explain_1.explainAgentAction)(entry.decision) : null;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Recent decisions, newest last.
|
|
204
|
+
*
|
|
205
|
+
* In memory and bounded; it does not survive a restart. Use `onDecision` for
|
|
206
|
+
* a trail that does. Saying which of the two this is matters — an operator
|
|
207
|
+
* who believes this is the audit log will discover otherwise at the worst
|
|
208
|
+
* possible moment.
|
|
209
|
+
*/
|
|
210
|
+
auditLog(filter = {}) {
|
|
211
|
+
return this.auditEntries
|
|
212
|
+
.filter((e) => (!filter.agentId || e.decision.agentId === filter.agentId))
|
|
213
|
+
.filter((e) => (!filter.verdict || e.decision.verdict === filter.verdict))
|
|
214
|
+
.map((e) => ({ decision: e.decision, ...(e.outcome ? { outcome: e.outcome } : {}) }));
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* A guard for a narrower task, sharing this one's ledger and client.
|
|
218
|
+
*
|
|
219
|
+
* The shared ledger is the point: a sub-task that spends against a tighter
|
|
220
|
+
* per-task cap still spends against the agent's daily one.
|
|
221
|
+
*/
|
|
222
|
+
withPolicy(restriction) {
|
|
223
|
+
return new AgentGuard({
|
|
224
|
+
policy: (0, policy_1.narrowPolicy)(this.sealed, restriction),
|
|
225
|
+
protect: this.protect,
|
|
226
|
+
ledger: this.ledgerImpl,
|
|
227
|
+
auditLogEntries: this.auditLimit,
|
|
228
|
+
...(this.onDecision === undefined ? {} : { onDecision: this.onDecision }),
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
// -------------------------------------------------------------------------
|
|
232
|
+
// Internals
|
|
233
|
+
// -------------------------------------------------------------------------
|
|
234
|
+
/**
|
|
235
|
+
* Route the action to the right Protect endpoint.
|
|
236
|
+
*
|
|
237
|
+
* An approval is assessed on its spender *and its allowance together*,
|
|
238
|
+
* which is a different question from "is this transaction dangerous" and
|
|
239
|
+
* the one an unlimited approval turns on.
|
|
240
|
+
*/
|
|
241
|
+
async screen(action, options) {
|
|
242
|
+
const call = {
|
|
243
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
244
|
+
...(options.skipCache === undefined ? {} : { skipCache: options.skipCache }),
|
|
245
|
+
};
|
|
246
|
+
if (action.type === 'approve' && action.token && action.to) {
|
|
247
|
+
const result = await this.protect.screenApproval({
|
|
248
|
+
chain: action.chain,
|
|
249
|
+
owner: action.from,
|
|
250
|
+
spender: action.to,
|
|
251
|
+
token: action.token,
|
|
252
|
+
...(action.approvalAmount === undefined ? {} : { amount: action.approvalAmount }),
|
|
253
|
+
}, call);
|
|
254
|
+
return result.assessment;
|
|
255
|
+
}
|
|
256
|
+
const result = await this.protect.assessTransaction({
|
|
257
|
+
chain: action.chain,
|
|
258
|
+
from: action.from,
|
|
259
|
+
...(action.to === undefined ? {} : { to: action.to }),
|
|
260
|
+
...(action.value === undefined ? {} : { value: action.value }),
|
|
261
|
+
...(action.data === undefined ? {} : { data: action.data }),
|
|
262
|
+
...(action.raw === undefined ? {} : { raw: action.raw }),
|
|
263
|
+
...(action.origin === undefined ? {} : { origin: action.origin }),
|
|
264
|
+
}, call);
|
|
265
|
+
return result.assessment;
|
|
266
|
+
}
|
|
267
|
+
record(decision) {
|
|
268
|
+
this.auditEntries.push({ decision });
|
|
269
|
+
while (this.auditEntries.length > this.auditLimit)
|
|
270
|
+
this.auditEntries.shift();
|
|
271
|
+
if (!this.onDecision)
|
|
272
|
+
return;
|
|
273
|
+
try {
|
|
274
|
+
this.onDecision(decision);
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
// A sink that throws must not turn a deny into an exception the agent
|
|
278
|
+
// handles by retrying, nor an allow into a failure. The decision stands.
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
attachOutcome(outcome) {
|
|
282
|
+
const entry = this.auditEntries.find((e) => e.decision.decisionId === outcome.decisionId);
|
|
283
|
+
if (entry)
|
|
284
|
+
entry.outcome = outcome;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
exports.AgentGuard = AgentGuard;
|
|
288
|
+
/**
|
|
289
|
+
* Distinguish "Decentrys answered, and found nothing" from "Decentrys did not
|
|
290
|
+
* answer".
|
|
291
|
+
*
|
|
292
|
+
* Protect never throws: an unreachable service produces an assessment whose
|
|
293
|
+
* `unknowns` say so. Both of those are `NO_CRITICAL_RISK_DETECTED` on the
|
|
294
|
+
* surface, and for an autonomous signer treating the second as the first is
|
|
295
|
+
* the whole failure — it is a clean bill of health issued by an outage.
|
|
296
|
+
*/
|
|
297
|
+
function unavailableReason(assessment) {
|
|
298
|
+
if (!assessment)
|
|
299
|
+
return 'no assessment was obtained';
|
|
300
|
+
const unknown = assessment.unknowns.find((u) => u.reason === 'PROVIDER_UNAVAILABLE');
|
|
301
|
+
return unknown ? unknown.statement : undefined;
|
|
302
|
+
}
|
|
303
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning a decision into something a person can be held to.
|
|
3
|
+
*
|
|
4
|
+
* The question this file exists to answer is not "why was that blocked" — a
|
|
5
|
+
* block explains itself, loudly, at the moment it happens. It is **"why was
|
|
6
|
+
* that allowed"**, asked weeks later by someone looking at a drained account.
|
|
7
|
+
* A record that lists only what went wrong cannot answer it, so
|
|
8
|
+
* `explainAgentAction` renders every rule that was applied, the bound it
|
|
9
|
+
* carried and the value it saw, alongside the axes on which the policy set no
|
|
10
|
+
* bound at all. An allow with nothing behind it reads, correctly, as an allow
|
|
11
|
+
* with nothing behind it.
|
|
12
|
+
*
|
|
13
|
+
* Nothing here recomputes anything. The explanation is a rendering of the
|
|
14
|
+
* decision record and cannot disagree with it — an explanation that is
|
|
15
|
+
* generated separately from the decision eventually explains a decision that
|
|
16
|
+
* was not made.
|
|
17
|
+
*/
|
|
18
|
+
import type { AgentDecision, AgentVerdict, RuleEvaluation } from './model';
|
|
19
|
+
export interface AgentActionExplanation {
|
|
20
|
+
decisionId: string;
|
|
21
|
+
agentId: string;
|
|
22
|
+
verdict: AgentVerdict;
|
|
23
|
+
/** One sentence. What happened and, at the top level, why. */
|
|
24
|
+
headline: string;
|
|
25
|
+
/** The failing or escalating rules, in the words they recorded. */
|
|
26
|
+
because: string[];
|
|
27
|
+
/**
|
|
28
|
+
* Every rule that ran, passes included. This is the part that makes an
|
|
29
|
+
* allow explainable rather than merely unobjected-to.
|
|
30
|
+
*/
|
|
31
|
+
rulesApplied: RuleEvaluation[];
|
|
32
|
+
/**
|
|
33
|
+
* Axes this policy left unbounded.
|
|
34
|
+
*
|
|
35
|
+
* An operator reading an allow deserves to know the difference between "the
|
|
36
|
+
* rate ceiling was checked and this was the third action this minute" and
|
|
37
|
+
* "there is no rate ceiling". Both produce a pass in the eyes of a summary;
|
|
38
|
+
* only one of them is a control.
|
|
39
|
+
*/
|
|
40
|
+
unbounded: string[];
|
|
41
|
+
/**
|
|
42
|
+
* What would have to change for this to be permitted.
|
|
43
|
+
*
|
|
44
|
+
* Addressed to the operator, who is the only party that can change a sealed
|
|
45
|
+
* policy. The agent can read this and still cannot act on it, which is the
|
|
46
|
+
* point of `sealPolicy`.
|
|
47
|
+
*/
|
|
48
|
+
toProceed: string[];
|
|
49
|
+
/** Enough to reproduce the decision against the same inputs. */
|
|
50
|
+
provenance: {
|
|
51
|
+
policyFingerprint: string;
|
|
52
|
+
policyVersion?: string;
|
|
53
|
+
actionDigest: string;
|
|
54
|
+
modelVersion: string;
|
|
55
|
+
riskModelVersion?: string;
|
|
56
|
+
riskLevel?: string;
|
|
57
|
+
decidedAt: string;
|
|
58
|
+
};
|
|
59
|
+
/** A plain-text rendering, for a log line, a ticket or a human review queue. */
|
|
60
|
+
text: string;
|
|
61
|
+
}
|
|
62
|
+
export declare function explainAgentAction(decision: AgentDecision): AgentActionExplanation;
|
|
63
|
+
//# sourceMappingURL=explain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explain.d.ts","sourceRoot":"","sources":["../src/explain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAIH,OAAO,KAAK,EAAE,aAAa,EAAe,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAExF,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,YAAY,CAAC;IACtB,8DAA8D;IAC9D,QAAQ,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB;;;OAGG;IACH,YAAY,EAAE,cAAc,EAAE,CAAC;IAC/B;;;;;;;OAOG;IACH,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB;;;;;;OAMG;IACH,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,gEAAgE;IAChE,UAAU,EAAE;QACV,iBAAiB,EAAE,MAAM,CAAC;QAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;QACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;QAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IACF,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAC;CACd;AA8BD,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,aAAa,GAAG,sBAAsB,CAyClF"}
|