open-agentpay 0.1.0 → 0.2.5
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 +201 -0
- package/README.md +125 -4
- package/dist/client/client.d.ts +37 -0
- package/dist/client/client.d.ts.map +1 -0
- package/dist/client/client.js +90 -0
- package/dist/client/client.js.map +1 -0
- package/dist/client/errors.d.ts +23 -0
- package/dist/client/errors.d.ts.map +1 -0
- package/dist/client/errors.js +38 -0
- package/dist/client/errors.js.map +1 -0
- package/dist/client/index.d.ts +6 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +6 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/interfaces.d.ts +34 -0
- package/dist/client/interfaces.d.ts.map +1 -0
- package/dist/client/interfaces.js +5 -0
- package/dist/client/interfaces.js.map +1 -0
- package/dist/client/signer.d.ts +136 -0
- package/dist/client/signer.d.ts.map +1 -0
- package/dist/client/signer.js +300 -0
- package/dist/client/signer.js.map +1 -0
- package/dist/client/transport.d.ts +44 -0
- package/dist/client/transport.d.ts.map +1 -0
- package/dist/client/transport.js +389 -0
- package/dist/client/transport.js.map +1 -0
- package/dist/core/codecs.d.ts +35 -0
- package/dist/core/codecs.d.ts.map +1 -0
- package/dist/core/codecs.js +177 -0
- package/dist/core/codecs.js.map +1 -0
- package/dist/core/errors.d.ts +33 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +48 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/index.d.ts +6 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +6 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/interop.d.ts +10 -0
- package/dist/core/interop.d.ts.map +1 -0
- package/dist/core/interop.js +28 -0
- package/dist/core/interop.js.map +1 -0
- package/dist/core/models.d.ts +155 -0
- package/dist/core/models.d.ts.map +1 -0
- package/dist/core/models.js +463 -0
- package/dist/core/models.js.map +1 -0
- package/dist/core/state.d.ts +82 -0
- package/dist/core/state.d.ts.map +1 -0
- package/dist/core/state.js +87 -0
- package/dist/core/state.js.map +1 -0
- package/dist/evm/constants.d.ts +52 -0
- package/dist/evm/constants.d.ts.map +1 -0
- package/dist/evm/constants.js +72 -0
- package/dist/evm/constants.js.map +1 -0
- package/dist/evm/eip3009.d.ts +43 -0
- package/dist/evm/eip3009.d.ts.map +1 -0
- package/dist/evm/eip3009.js +145 -0
- package/dist/evm/eip3009.js.map +1 -0
- package/dist/evm/index.d.ts +3 -0
- package/dist/evm/index.d.ts.map +1 -0
- package/dist/evm/index.js +3 -0
- package/dist/evm/index.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/integrations/index.d.ts +4 -0
- package/dist/integrations/index.d.ts.map +1 -0
- package/dist/integrations/index.js +4 -0
- package/dist/integrations/index.js.map +1 -0
- package/dist/integrations/langchain.d.ts +40 -0
- package/dist/integrations/langchain.d.ts.map +1 -0
- package/dist/integrations/langchain.js +68 -0
- package/dist/integrations/langchain.js.map +1 -0
- package/dist/integrations/mcp.d.ts +29 -0
- package/dist/integrations/mcp.d.ts.map +1 -0
- package/dist/integrations/mcp.js +73 -0
- package/dist/integrations/mcp.js.map +1 -0
- package/dist/integrations/vercel-ai.d.ts +31 -0
- package/dist/integrations/vercel-ai.d.ts.map +1 -0
- package/dist/integrations/vercel-ai.js +50 -0
- package/dist/integrations/vercel-ai.js.map +1 -0
- package/dist/policy/db.d.ts +14 -0
- package/dist/policy/db.d.ts.map +1 -0
- package/dist/policy/db.js +86 -0
- package/dist/policy/db.js.map +1 -0
- package/dist/policy/errors.d.ts +31 -0
- package/dist/policy/errors.d.ts.map +1 -0
- package/dist/policy/errors.js +48 -0
- package/dist/policy/errors.js.map +1 -0
- package/dist/policy/evaluator.d.ts +22 -0
- package/dist/policy/evaluator.d.ts.map +1 -0
- package/dist/policy/evaluator.js +152 -0
- package/dist/policy/evaluator.js.map +1 -0
- package/dist/policy/index.d.ts +7 -0
- package/dist/policy/index.d.ts.map +1 -0
- package/dist/policy/index.js +7 -0
- package/dist/policy/index.js.map +1 -0
- package/dist/policy/interfaces.d.ts +59 -0
- package/dist/policy/interfaces.d.ts.map +1 -0
- package/dist/policy/interfaces.js +7 -0
- package/dist/policy/interfaces.js.map +1 -0
- package/dist/policy/models.d.ts +161 -0
- package/dist/policy/models.d.ts.map +1 -0
- package/dist/policy/models.js +449 -0
- package/dist/policy/models.js.map +1 -0
- package/dist/policy/sqlite-engine.d.ts +28 -0
- package/dist/policy/sqlite-engine.d.ts.map +1 -0
- package/dist/policy/sqlite-engine.js +501 -0
- package/dist/policy/sqlite-engine.js.map +1 -0
- package/dist/state/buyer-coordinator.d.ts +33 -0
- package/dist/state/buyer-coordinator.d.ts.map +1 -0
- package/dist/state/buyer-coordinator.js +363 -0
- package/dist/state/buyer-coordinator.js.map +1 -0
- package/dist/state/errors.d.ts +45 -0
- package/dist/state/errors.d.ts.map +1 -0
- package/dist/state/errors.js +88 -0
- package/dist/state/errors.js.map +1 -0
- package/dist/state/guards.d.ts +24 -0
- package/dist/state/guards.d.ts.map +1 -0
- package/dist/state/guards.js +211 -0
- package/dist/state/guards.js.map +1 -0
- package/dist/state/index.d.ts +5 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +5 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/reconciliation.d.ts +105 -0
- package/dist/state/reconciliation.d.ts.map +1 -0
- package/dist/state/reconciliation.js +297 -0
- package/dist/state/reconciliation.js.map +1 -0
- package/package.json +98 -12
- package/index.d.ts +0 -2
- package/index.js +0 -8
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State Transition Guards and Multi-Entity Validation Rules for AgentPay.
|
|
3
|
+
*
|
|
4
|
+
* Enforces:
|
|
5
|
+
* - Transition guard matrices for Intent, Settlement, Fulfillment, and Ledger
|
|
6
|
+
* - Strict Anti-Overspend invariant: SIGNATURE_SENT/RECOVERY_HOLD cannot release
|
|
7
|
+
* - Strict Anti-Rollback invariant: Upstream 5xx cannot void Settlement
|
|
8
|
+
* - Legal composite state whitelist
|
|
9
|
+
*/
|
|
10
|
+
import { BudgetLedgerState, FulfillmentState, PaymentIntentState, SettlementState, } from '../core/state.js';
|
|
11
|
+
import { IllegalStateTransitionError, InvalidCompositeStateError, OrphanedSettlementRollbackForbiddenError, SignatureSentReleaseForbiddenError, } from './errors.js';
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// 1. Individual Entity Transition Matrices
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
export const LEGAL_INTENT_TRANSITIONS = Object.freeze({
|
|
16
|
+
[PaymentIntentState.CREATED]: new Set([
|
|
17
|
+
PaymentIntentState.PROCESSING,
|
|
18
|
+
PaymentIntentState.AUTHORIZED,
|
|
19
|
+
PaymentIntentState.REJECTED,
|
|
20
|
+
PaymentIntentState.EXPIRED,
|
|
21
|
+
PaymentIntentState.CANCELLED,
|
|
22
|
+
]),
|
|
23
|
+
[PaymentIntentState.AUTHORIZED]: new Set([
|
|
24
|
+
PaymentIntentState.PROCESSING,
|
|
25
|
+
PaymentIntentState.SIGNATURE_SENT,
|
|
26
|
+
PaymentIntentState.CANCELLED,
|
|
27
|
+
]),
|
|
28
|
+
[PaymentIntentState.PROCESSING]: new Set([
|
|
29
|
+
PaymentIntentState.SIGNATURE_SENT,
|
|
30
|
+
PaymentIntentState.SUCCEEDED,
|
|
31
|
+
PaymentIntentState.COMPLETED,
|
|
32
|
+
PaymentIntentState.RECOVERY_HOLD,
|
|
33
|
+
PaymentIntentState.CANCELLED,
|
|
34
|
+
]),
|
|
35
|
+
[PaymentIntentState.SIGNATURE_SENT]: new Set([
|
|
36
|
+
PaymentIntentState.SUCCEEDED,
|
|
37
|
+
PaymentIntentState.COMPLETED,
|
|
38
|
+
PaymentIntentState.RECOVERY_HOLD,
|
|
39
|
+
]),
|
|
40
|
+
[PaymentIntentState.RECOVERY_HOLD]: new Set([
|
|
41
|
+
PaymentIntentState.SUCCEEDED,
|
|
42
|
+
PaymentIntentState.COMPLETED,
|
|
43
|
+
PaymentIntentState.REJECTED, // Permitted ONLY after reconciler proves nonce unspent
|
|
44
|
+
]),
|
|
45
|
+
// Terminal states
|
|
46
|
+
[PaymentIntentState.COMPLETED]: new Set(),
|
|
47
|
+
[PaymentIntentState.SUCCEEDED]: new Set(),
|
|
48
|
+
[PaymentIntentState.REJECTED]: new Set(),
|
|
49
|
+
[PaymentIntentState.EXPIRED]: new Set(),
|
|
50
|
+
[PaymentIntentState.CANCELLED]: new Set(),
|
|
51
|
+
});
|
|
52
|
+
export const LEGAL_SETTLEMENT_TRANSITIONS = Object.freeze({
|
|
53
|
+
[SettlementState.NOT_STARTED]: new Set([
|
|
54
|
+
SettlementState.SUBMITTED,
|
|
55
|
+
SettlementState.PENDING,
|
|
56
|
+
SettlementState.FAILED,
|
|
57
|
+
]),
|
|
58
|
+
[SettlementState.NOT_SUBMITTED]: new Set([
|
|
59
|
+
SettlementState.SUBMITTED,
|
|
60
|
+
SettlementState.PENDING,
|
|
61
|
+
SettlementState.FAILED,
|
|
62
|
+
]),
|
|
63
|
+
[SettlementState.SUBMITTED]: new Set([
|
|
64
|
+
SettlementState.PENDING,
|
|
65
|
+
SettlementState.CONFIRMED,
|
|
66
|
+
SettlementState.SUCCESS,
|
|
67
|
+
SettlementState.FAILED,
|
|
68
|
+
SettlementState.UNKNOWN,
|
|
69
|
+
]),
|
|
70
|
+
[SettlementState.PENDING]: new Set([
|
|
71
|
+
SettlementState.CONFIRMED,
|
|
72
|
+
SettlementState.SUCCESS,
|
|
73
|
+
SettlementState.FAILED,
|
|
74
|
+
SettlementState.REORGED,
|
|
75
|
+
SettlementState.UNKNOWN,
|
|
76
|
+
]),
|
|
77
|
+
[SettlementState.REORGED]: new Set([
|
|
78
|
+
SettlementState.SUBMITTED,
|
|
79
|
+
SettlementState.FAILED,
|
|
80
|
+
]),
|
|
81
|
+
[SettlementState.UNKNOWN]: new Set([
|
|
82
|
+
SettlementState.CONFIRMED,
|
|
83
|
+
SettlementState.SUCCESS,
|
|
84
|
+
SettlementState.PENDING,
|
|
85
|
+
SettlementState.FAILED,
|
|
86
|
+
]),
|
|
87
|
+
// Terminal states
|
|
88
|
+
[SettlementState.CONFIRMED]: new Set(),
|
|
89
|
+
[SettlementState.SUCCESS]: new Set(),
|
|
90
|
+
[SettlementState.FAILED]: new Set(),
|
|
91
|
+
});
|
|
92
|
+
export const LEGAL_FULFILLMENT_TRANSITIONS = Object.freeze({
|
|
93
|
+
[FulfillmentState.NOT_STARTED]: new Set([
|
|
94
|
+
FulfillmentState.PROCESSING,
|
|
95
|
+
FulfillmentState.DELIVERING,
|
|
96
|
+
FulfillmentState.FAILED,
|
|
97
|
+
]),
|
|
98
|
+
[FulfillmentState.NOT_DELIVERED]: new Set([
|
|
99
|
+
FulfillmentState.PROCESSING,
|
|
100
|
+
FulfillmentState.DELIVERING,
|
|
101
|
+
FulfillmentState.FAILED,
|
|
102
|
+
]),
|
|
103
|
+
[FulfillmentState.PROCESSING]: new Set([
|
|
104
|
+
FulfillmentState.DELIVERED,
|
|
105
|
+
FulfillmentState.FAILED,
|
|
106
|
+
FulfillmentState.RECOVERY_REQUIRED,
|
|
107
|
+
]),
|
|
108
|
+
[FulfillmentState.DELIVERING]: new Set([
|
|
109
|
+
FulfillmentState.DELIVERED,
|
|
110
|
+
FulfillmentState.FAILED,
|
|
111
|
+
FulfillmentState.RECOVERY_REQUIRED,
|
|
112
|
+
]),
|
|
113
|
+
[FulfillmentState.RECOVERY_REQUIRED]: new Set([
|
|
114
|
+
FulfillmentState.PROCESSING,
|
|
115
|
+
FulfillmentState.DELIVERING,
|
|
116
|
+
FulfillmentState.DELIVERED,
|
|
117
|
+
FulfillmentState.FAILED,
|
|
118
|
+
]),
|
|
119
|
+
// Terminal states
|
|
120
|
+
[FulfillmentState.DELIVERED]: new Set(),
|
|
121
|
+
[FulfillmentState.FAILED]: new Set(),
|
|
122
|
+
});
|
|
123
|
+
export const LEGAL_LEDGER_TRANSITIONS = Object.freeze({
|
|
124
|
+
[BudgetLedgerState.RESERVED]: new Set([
|
|
125
|
+
BudgetLedgerState.COMMITTED,
|
|
126
|
+
BudgetLedgerState.RELEASED,
|
|
127
|
+
]),
|
|
128
|
+
// Terminal states
|
|
129
|
+
[BudgetLedgerState.COMMITTED]: new Set(),
|
|
130
|
+
[BudgetLedgerState.RELEASED]: new Set(),
|
|
131
|
+
});
|
|
132
|
+
// ---------------------------------------------------------------------------
|
|
133
|
+
// 2. Guard Functions
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
export function validateIntentTransition(fromState, toState, options) {
|
|
136
|
+
if (fromState === toState) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// Guardrail: Once SIGNATURE_SENT or RECOVERY_HOLD, never transition to CANCELLED
|
|
140
|
+
if ((fromState === PaymentIntentState.SIGNATURE_SENT || fromState === PaymentIntentState.RECOVERY_HOLD) &&
|
|
141
|
+
toState === PaymentIntentState.CANCELLED) {
|
|
142
|
+
throw new SignatureSentReleaseForbiddenError(options?.intentId ?? '', fromState);
|
|
143
|
+
}
|
|
144
|
+
const allowed = LEGAL_INTENT_TRANSITIONS[fromState] || new Set();
|
|
145
|
+
if (!allowed.has(toState)) {
|
|
146
|
+
throw new IllegalStateTransitionError('PaymentIntent', fromState, toState, `Allowed transitions from '${fromState}': ${Array.from(allowed).join(', ')}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
export function validateSettlementTransition(fromState, toState, options) {
|
|
150
|
+
if (fromState === toState) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
// Guardrail: Active settlements cannot be reset to NOT_STARTED / NOT_SUBMITTED
|
|
154
|
+
if ((fromState === SettlementState.SUBMITTED ||
|
|
155
|
+
fromState === SettlementState.PENDING ||
|
|
156
|
+
fromState === SettlementState.CONFIRMED ||
|
|
157
|
+
fromState === SettlementState.SUCCESS) &&
|
|
158
|
+
(toState === SettlementState.NOT_STARTED || toState === SettlementState.NOT_SUBMITTED)) {
|
|
159
|
+
throw new OrphanedSettlementRollbackForbiddenError(options?.settlementId ?? '', fromState);
|
|
160
|
+
}
|
|
161
|
+
const allowed = LEGAL_SETTLEMENT_TRANSITIONS[fromState] || new Set();
|
|
162
|
+
if (!allowed.has(toState)) {
|
|
163
|
+
throw new IllegalStateTransitionError('Settlement', fromState, toState, `Allowed transitions from '${fromState}': ${Array.from(allowed).join(', ')}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
export function validateFulfillmentTransition(fromState, toState) {
|
|
167
|
+
if (fromState === toState) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const allowed = LEGAL_FULFILLMENT_TRANSITIONS[fromState] || new Set();
|
|
171
|
+
if (!allowed.has(toState)) {
|
|
172
|
+
throw new IllegalStateTransitionError('Fulfillment', fromState, toState, `Allowed transitions from '${fromState}': ${Array.from(allowed).join(', ')}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
export function validateLedgerTransition(fromState, toState) {
|
|
176
|
+
if (fromState === toState) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const allowed = LEGAL_LEDGER_TRANSITIONS[fromState] || new Set();
|
|
180
|
+
if (!allowed.has(toState)) {
|
|
181
|
+
throw new IllegalStateTransitionError('BudgetLedger', fromState, toState, `Ledger entry is already finalized in '${fromState}'.`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
// 3. Composite State Whitelist Validation
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
export function validateCompositeState(intentState, settlementState, fulfillmentState, ledgerState) {
|
|
188
|
+
// Invariant 1: Anti-Overspend Rule
|
|
189
|
+
if ((intentState === PaymentIntentState.SIGNATURE_SENT || intentState === PaymentIntentState.RECOVERY_HOLD) &&
|
|
190
|
+
ledgerState === BudgetLedgerState.RELEASED) {
|
|
191
|
+
throw new InvalidCompositeStateError(intentState, settlementState, fulfillmentState, ledgerState ?? null, 'Anti-Overspend Violation: Budget cannot be RELEASED while intent is in-flight or held.');
|
|
192
|
+
}
|
|
193
|
+
// Invariant 2: Success Alignment Rule
|
|
194
|
+
if (intentState === PaymentIntentState.SUCCEEDED || intentState === PaymentIntentState.COMPLETED) {
|
|
195
|
+
if (fulfillmentState !== FulfillmentState.DELIVERED) {
|
|
196
|
+
throw new InvalidCompositeStateError(intentState, settlementState, fulfillmentState, ledgerState ?? null, 'Intent cannot be SUCCEEDED if resource was not DELIVERED.');
|
|
197
|
+
}
|
|
198
|
+
if (ledgerState !== undefined && ledgerState !== null && ledgerState !== BudgetLedgerState.COMMITTED) {
|
|
199
|
+
throw new InvalidCompositeStateError(intentState, settlementState, fulfillmentState, ledgerState, 'Intent cannot be SUCCEEDED unless budget quota is COMMITTED.');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// Invariant 3: Delivered Resource Settlement Requirement
|
|
203
|
+
if (fulfillmentState === FulfillmentState.DELIVERED &&
|
|
204
|
+
settlementState !== SettlementState.SUBMITTED &&
|
|
205
|
+
settlementState !== SettlementState.PENDING &&
|
|
206
|
+
settlementState !== SettlementState.CONFIRMED &&
|
|
207
|
+
settlementState !== SettlementState.SUCCESS) {
|
|
208
|
+
throw new InvalidCompositeStateError(intentState, settlementState, fulfillmentState, ledgerState ?? null, 'Fulfillment cannot be DELIVERED if settlement was never submitted or succeeded.');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=guards.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guards.js","sourceRoot":"","sources":["../../src/state/guards.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/state/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/state/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,wBAAwB,CAAC;AACvC,cAAc,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reconciliation Engine for AgentPay State Coordination.
|
|
3
|
+
*
|
|
4
|
+
* Adheres strictly to:
|
|
5
|
+
* - CLAUDE.md §6: Nonce verification, fail-closed reconciliation, and validBefore window safety
|
|
6
|
+
* - P0-6 Invariant: Quota release permitted ONLY if nonce is proven unspent after (validBefore + safeBuffer)
|
|
7
|
+
* - Anti-Rollback Invariant: Upstream delivery failure never rolls back valid settlement
|
|
8
|
+
*/
|
|
9
|
+
import { BudgetLedgerState, PaymentIntentState, SettlementState } from '../core/state.js';
|
|
10
|
+
import type { Clock } from '../policy/interfaces.js';
|
|
11
|
+
import type { BuyerStateCoordinator } from './buyer-coordinator.js';
|
|
12
|
+
export declare enum NonceStatus {
|
|
13
|
+
UNUSED = "UNUSED",
|
|
14
|
+
USED = "USED",
|
|
15
|
+
CANCELED = "CANCELED",
|
|
16
|
+
UNKNOWN = "UNKNOWN"
|
|
17
|
+
}
|
|
18
|
+
export interface NonceProbeResult {
|
|
19
|
+
readonly status: NonceStatus;
|
|
20
|
+
readonly txHash?: string | null;
|
|
21
|
+
readonly detail?: string | null;
|
|
22
|
+
}
|
|
23
|
+
export interface SettlementProbeResult {
|
|
24
|
+
readonly status: SettlementState;
|
|
25
|
+
readonly txHash?: string | null;
|
|
26
|
+
readonly error?: string | null;
|
|
27
|
+
}
|
|
28
|
+
export interface ISettlementProbe {
|
|
29
|
+
probeSettlement(settlementId: string): Promise<SettlementProbeResult> | SettlementProbeResult;
|
|
30
|
+
probeNonce(networkId: string, assetAddress: string, payerAddress: string, nonce: string): Promise<NonceProbeResult> | NonceProbeResult;
|
|
31
|
+
}
|
|
32
|
+
export declare class MockSettlementProbe implements ISettlementProbe {
|
|
33
|
+
#private;
|
|
34
|
+
raiseOnProbe: boolean;
|
|
35
|
+
setSettlement(settlementId: string, status: SettlementState, options?: {
|
|
36
|
+
txHash?: string | null;
|
|
37
|
+
error?: string | null;
|
|
38
|
+
}): void;
|
|
39
|
+
setNonce(networkId: string, assetAddress: string, payerAddress: string, nonce: string, status: NonceStatus, options?: {
|
|
40
|
+
txHash?: string | null;
|
|
41
|
+
detail?: string | null;
|
|
42
|
+
}): void;
|
|
43
|
+
probeSettlement(settlementId: string): SettlementProbeResult;
|
|
44
|
+
probeNonce(networkId: string, assetAddress: string, payerAddress: string, nonce: string): NonceProbeResult;
|
|
45
|
+
}
|
|
46
|
+
export interface FacilitatorClientLike {
|
|
47
|
+
getSettlement?(settlementId: string): Promise<{
|
|
48
|
+
status: SettlementState | string;
|
|
49
|
+
txHash?: string | null;
|
|
50
|
+
error?: string | null;
|
|
51
|
+
}>;
|
|
52
|
+
isNonceUsed?(params: {
|
|
53
|
+
network: string;
|
|
54
|
+
asset: string;
|
|
55
|
+
payer: string;
|
|
56
|
+
nonce: string;
|
|
57
|
+
}): Promise<boolean>;
|
|
58
|
+
}
|
|
59
|
+
export interface FacilitatorSettlementProbeOptions {
|
|
60
|
+
client?: FacilitatorClientLike;
|
|
61
|
+
baseUrl?: string;
|
|
62
|
+
fetch?: typeof globalThis.fetch;
|
|
63
|
+
}
|
|
64
|
+
export declare class FacilitatorSettlementProbe implements ISettlementProbe {
|
|
65
|
+
#private;
|
|
66
|
+
constructor(options?: FacilitatorSettlementProbeOptions);
|
|
67
|
+
probeSettlement(settlementId: string): Promise<SettlementProbeResult>;
|
|
68
|
+
probeNonce(networkId: string, assetAddress: string, payerAddress: string, nonce: string): Promise<NonceProbeResult>;
|
|
69
|
+
}
|
|
70
|
+
export declare enum BuyerReconciliationOutcome {
|
|
71
|
+
COMMITTED = "COMMITTED",
|
|
72
|
+
RELEASED = "RELEASED",
|
|
73
|
+
HELD = "HELD",
|
|
74
|
+
SKIPPED_UNKNOWN = "SKIPPED_UNKNOWN"
|
|
75
|
+
}
|
|
76
|
+
export declare class BuyerReconciliationReport {
|
|
77
|
+
readonly intentId: string;
|
|
78
|
+
readonly previousIntentStatus: PaymentIntentState;
|
|
79
|
+
readonly newIntentStatus: PaymentIntentState;
|
|
80
|
+
readonly ledgerState: BudgetLedgerState;
|
|
81
|
+
readonly outcome: BuyerReconciliationOutcome;
|
|
82
|
+
readonly reason: string;
|
|
83
|
+
constructor(data: {
|
|
84
|
+
intentId: string;
|
|
85
|
+
previousIntentStatus: PaymentIntentState;
|
|
86
|
+
newIntentStatus: PaymentIntentState;
|
|
87
|
+
ledgerState: BudgetLedgerState;
|
|
88
|
+
outcome: BuyerReconciliationOutcome;
|
|
89
|
+
reason: string;
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
export declare class BuyerReconciler {
|
|
93
|
+
#private;
|
|
94
|
+
readonly safeBufferSeconds: number;
|
|
95
|
+
readonly inFlightTimeoutSeconds: number;
|
|
96
|
+
constructor(coordinator: BuyerStateCoordinator, probe: ISettlementProbe, options?: {
|
|
97
|
+
clock?: Clock;
|
|
98
|
+
safeBufferSeconds?: number;
|
|
99
|
+
inFlightTimeoutSeconds?: number;
|
|
100
|
+
});
|
|
101
|
+
getTime(): number;
|
|
102
|
+
reconcileIntent(intentId: string): Promise<BuyerReconciliationReport>;
|
|
103
|
+
reconcileAllHeld(): Promise<BuyerReconciliationReport[]>;
|
|
104
|
+
}
|
|
105
|
+
//# sourceMappingURL=reconciliation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconciliation.d.ts","sourceRoot":"","sources":["../../src/state/reconciliation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reconciliation Engine for AgentPay State Coordination.
|
|
3
|
+
*
|
|
4
|
+
* Adheres strictly to:
|
|
5
|
+
* - CLAUDE.md §6: Nonce verification, fail-closed reconciliation, and validBefore window safety
|
|
6
|
+
* - P0-6 Invariant: Quota release permitted ONLY if nonce is proven unspent after (validBefore + safeBuffer)
|
|
7
|
+
* - Anti-Rollback Invariant: Upstream delivery failure never rolls back valid settlement
|
|
8
|
+
*/
|
|
9
|
+
import { BudgetLedgerState, PaymentIntentState, SettlementState, } from '../core/state.js';
|
|
10
|
+
export var NonceStatus;
|
|
11
|
+
(function (NonceStatus) {
|
|
12
|
+
NonceStatus["UNUSED"] = "UNUSED";
|
|
13
|
+
NonceStatus["USED"] = "USED";
|
|
14
|
+
NonceStatus["CANCELED"] = "CANCELED";
|
|
15
|
+
NonceStatus["UNKNOWN"] = "UNKNOWN";
|
|
16
|
+
})(NonceStatus || (NonceStatus = {}));
|
|
17
|
+
export class MockSettlementProbe {
|
|
18
|
+
#settlements = new Map();
|
|
19
|
+
#nonces = new Map();
|
|
20
|
+
raiseOnProbe = false;
|
|
21
|
+
setSettlement(settlementId, status, options) {
|
|
22
|
+
this.#settlements.set(settlementId, {
|
|
23
|
+
status,
|
|
24
|
+
txHash: options?.txHash ?? null,
|
|
25
|
+
error: options?.error ?? null,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
setNonce(networkId, assetAddress, payerAddress, nonce, status, options) {
|
|
29
|
+
const key = `${networkId.toLowerCase()}:${assetAddress.toLowerCase()}:${payerAddress.toLowerCase()}:${nonce.toLowerCase()}`;
|
|
30
|
+
this.#nonces.set(key, {
|
|
31
|
+
status,
|
|
32
|
+
txHash: options?.txHash ?? null,
|
|
33
|
+
detail: options?.detail ?? null,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
probeSettlement(settlementId) {
|
|
37
|
+
if (this.raiseOnProbe) {
|
|
38
|
+
return {
|
|
39
|
+
status: SettlementState.UNKNOWN,
|
|
40
|
+
error: 'Simulated network failure in probe',
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return (this.#settlements.get(settlementId) ?? {
|
|
44
|
+
status: SettlementState.FAILED,
|
|
45
|
+
error: 'Settlement not found',
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
probeNonce(networkId, assetAddress, payerAddress, nonce) {
|
|
49
|
+
if (this.raiseOnProbe) {
|
|
50
|
+
return {
|
|
51
|
+
status: NonceStatus.UNKNOWN,
|
|
52
|
+
detail: 'Simulated network failure in probe',
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const key = `${networkId.toLowerCase()}:${assetAddress.toLowerCase()}:${payerAddress.toLowerCase()}:${nonce.toLowerCase()}`;
|
|
56
|
+
return this.#nonces.get(key) ?? { status: NonceStatus.UNUSED };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
export class FacilitatorSettlementProbe {
|
|
60
|
+
#client;
|
|
61
|
+
#baseUrl;
|
|
62
|
+
#fetch;
|
|
63
|
+
constructor(options) {
|
|
64
|
+
this.#client = options?.client;
|
|
65
|
+
this.#baseUrl = options?.baseUrl?.replace(/\/+$/, '');
|
|
66
|
+
this.#fetch = options?.fetch ?? globalThis.fetch;
|
|
67
|
+
}
|
|
68
|
+
async probeSettlement(settlementId) {
|
|
69
|
+
if (this.#client?.getSettlement) {
|
|
70
|
+
try {
|
|
71
|
+
const res = await this.#client.getSettlement(settlementId);
|
|
72
|
+
return {
|
|
73
|
+
status: res.status || SettlementState.UNKNOWN,
|
|
74
|
+
txHash: res.txHash ?? null,
|
|
75
|
+
error: res.error ?? null,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
if (err?.status === 404 || String(err?.message).includes('not found')) {
|
|
80
|
+
return { status: SettlementState.FAILED, error: 'Settlement not found' };
|
|
81
|
+
}
|
|
82
|
+
return { status: SettlementState.UNKNOWN, error: String(err?.message || err) };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (this.#baseUrl) {
|
|
86
|
+
try {
|
|
87
|
+
const resp = await this.#fetch(`${this.#baseUrl}/v1/settlements/${encodeURIComponent(settlementId)}`);
|
|
88
|
+
if (resp.status === 404) {
|
|
89
|
+
return { status: SettlementState.FAILED, error: 'Settlement not found' };
|
|
90
|
+
}
|
|
91
|
+
if (!resp.ok) {
|
|
92
|
+
return { status: SettlementState.UNKNOWN, error: `HTTP ${resp.status}` };
|
|
93
|
+
}
|
|
94
|
+
const data = (await resp.json());
|
|
95
|
+
return {
|
|
96
|
+
status: data.status || SettlementState.UNKNOWN,
|
|
97
|
+
txHash: data.txHash || data.tx_hash || null,
|
|
98
|
+
error: data.error || null,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
return { status: SettlementState.UNKNOWN, error: String(err?.message || err) };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return { status: SettlementState.UNKNOWN, error: 'No client or baseUrl configured for Facilitator probe' };
|
|
106
|
+
}
|
|
107
|
+
async probeNonce(networkId, assetAddress, payerAddress, nonce) {
|
|
108
|
+
if (this.#client?.isNonceUsed) {
|
|
109
|
+
try {
|
|
110
|
+
const isUsed = await this.#client.isNonceUsed({
|
|
111
|
+
network: networkId,
|
|
112
|
+
asset: assetAddress,
|
|
113
|
+
payer: payerAddress,
|
|
114
|
+
nonce,
|
|
115
|
+
});
|
|
116
|
+
return { status: isUsed ? NonceStatus.USED : NonceStatus.UNUSED };
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
return { status: NonceStatus.UNKNOWN, detail: String(err?.message || err) };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (this.#baseUrl) {
|
|
123
|
+
try {
|
|
124
|
+
const query = new URLSearchParams({
|
|
125
|
+
network: networkId,
|
|
126
|
+
asset: assetAddress,
|
|
127
|
+
payer: payerAddress,
|
|
128
|
+
nonce,
|
|
129
|
+
});
|
|
130
|
+
const resp = await this.#fetch(`${this.#baseUrl}/v1/nonces/status?${query.toString()}`);
|
|
131
|
+
if (!resp.ok) {
|
|
132
|
+
return { status: NonceStatus.UNKNOWN, detail: `HTTP ${resp.status}` };
|
|
133
|
+
}
|
|
134
|
+
const data = (await resp.json());
|
|
135
|
+
const status = data.used || data.status === 'USED' ? NonceStatus.USED : NonceStatus.UNUSED;
|
|
136
|
+
return { status, txHash: data.txHash || data.tx_hash || null };
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
return { status: NonceStatus.UNKNOWN, detail: String(err?.message || err) };
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return { status: NonceStatus.UNKNOWN, detail: 'No client or baseUrl configured for Facilitator probe' };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
export var BuyerReconciliationOutcome;
|
|
146
|
+
(function (BuyerReconciliationOutcome) {
|
|
147
|
+
BuyerReconciliationOutcome["COMMITTED"] = "COMMITTED";
|
|
148
|
+
BuyerReconciliationOutcome["RELEASED"] = "RELEASED";
|
|
149
|
+
BuyerReconciliationOutcome["HELD"] = "HELD";
|
|
150
|
+
BuyerReconciliationOutcome["SKIPPED_UNKNOWN"] = "SKIPPED_UNKNOWN";
|
|
151
|
+
})(BuyerReconciliationOutcome || (BuyerReconciliationOutcome = {}));
|
|
152
|
+
export class BuyerReconciliationReport {
|
|
153
|
+
intentId;
|
|
154
|
+
previousIntentStatus;
|
|
155
|
+
newIntentStatus;
|
|
156
|
+
ledgerState;
|
|
157
|
+
outcome;
|
|
158
|
+
reason;
|
|
159
|
+
constructor(data) {
|
|
160
|
+
this.intentId = data.intentId;
|
|
161
|
+
this.previousIntentStatus = data.previousIntentStatus;
|
|
162
|
+
this.newIntentStatus = data.newIntentStatus;
|
|
163
|
+
this.ledgerState = data.ledgerState;
|
|
164
|
+
this.outcome = data.outcome;
|
|
165
|
+
this.reason = data.reason;
|
|
166
|
+
Object.freeze(this);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
export class BuyerReconciler {
|
|
170
|
+
#coordinator;
|
|
171
|
+
#probe;
|
|
172
|
+
#clock;
|
|
173
|
+
safeBufferSeconds;
|
|
174
|
+
inFlightTimeoutSeconds;
|
|
175
|
+
constructor(coordinator, probe, options) {
|
|
176
|
+
this.#coordinator = coordinator;
|
|
177
|
+
this.#probe = probe;
|
|
178
|
+
this.#clock = options?.clock ?? (() => coordinator.getTime());
|
|
179
|
+
this.safeBufferSeconds = options?.safeBufferSeconds ?? 60;
|
|
180
|
+
this.inFlightTimeoutSeconds = options?.inFlightTimeoutSeconds ?? 30;
|
|
181
|
+
}
|
|
182
|
+
getTime() {
|
|
183
|
+
return this.#clock();
|
|
184
|
+
}
|
|
185
|
+
async reconcileIntent(intentId) {
|
|
186
|
+
const now = this.getTime();
|
|
187
|
+
let record = this.#coordinator.getIntent(intentId);
|
|
188
|
+
if (!record) {
|
|
189
|
+
throw new Error(`Buyer intent '${intentId}' not found.`);
|
|
190
|
+
}
|
|
191
|
+
let prevStatus = record.status;
|
|
192
|
+
const ledgerState = this.#coordinator.getLedgerState(intentId) || BudgetLedgerState.RELEASED;
|
|
193
|
+
// Pre-check: If intent is still in SIGNATURE_SENT but timed out, transition to RECOVERY_HOLD
|
|
194
|
+
if (prevStatus === PaymentIntentState.SIGNATURE_SENT) {
|
|
195
|
+
if (now - record.updatedAt >= this.inFlightTimeoutSeconds) {
|
|
196
|
+
this.#coordinator.holdIntent(intentId);
|
|
197
|
+
record = this.#coordinator.getIntent(intentId);
|
|
198
|
+
prevStatus = record.status;
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
return new BuyerReconciliationReport({
|
|
202
|
+
intentId,
|
|
203
|
+
previousIntentStatus: prevStatus,
|
|
204
|
+
newIntentStatus: prevStatus,
|
|
205
|
+
ledgerState,
|
|
206
|
+
outcome: BuyerReconciliationOutcome.HELD,
|
|
207
|
+
reason: 'In-flight dispatch still active; waiting for response',
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (record.status !== PaymentIntentState.RECOVERY_HOLD) {
|
|
212
|
+
return new BuyerReconciliationReport({
|
|
213
|
+
intentId,
|
|
214
|
+
previousIntentStatus: prevStatus,
|
|
215
|
+
newIntentStatus: record.status,
|
|
216
|
+
ledgerState,
|
|
217
|
+
outcome: BuyerReconciliationOutcome.HELD,
|
|
218
|
+
reason: `Intent status '${record.status}' is not held for recovery`,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
if (!record.nonce) {
|
|
222
|
+
return new BuyerReconciliationReport({
|
|
223
|
+
intentId,
|
|
224
|
+
previousIntentStatus: prevStatus,
|
|
225
|
+
newIntentStatus: record.status,
|
|
226
|
+
ledgerState,
|
|
227
|
+
outcome: BuyerReconciliationOutcome.SKIPPED_UNKNOWN,
|
|
228
|
+
reason: 'Intent has no recorded nonce',
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
const probeRes = await this.#probe.probeNonce(record.networkId, record.assetAddress, record.payerAddress, record.nonce);
|
|
232
|
+
// Case 1: Nonce was confirmed used on-chain / Facilitator
|
|
233
|
+
if (probeRes.status === NonceStatus.USED) {
|
|
234
|
+
this.#coordinator.completeIntent(intentId);
|
|
235
|
+
const updatedIntent = this.#coordinator.getIntent(intentId);
|
|
236
|
+
const updatedLedger = this.#coordinator.getLedgerState(intentId) || BudgetLedgerState.COMMITTED;
|
|
237
|
+
return new BuyerReconciliationReport({
|
|
238
|
+
intentId,
|
|
239
|
+
previousIntentStatus: prevStatus,
|
|
240
|
+
newIntentStatus: updatedIntent.status,
|
|
241
|
+
ledgerState: updatedLedger,
|
|
242
|
+
outcome: BuyerReconciliationOutcome.COMMITTED,
|
|
243
|
+
reason: 'Nonce was settled on-chain; quota permanently committed',
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
// Case 2: Nonce is UNUSED (or CANCELED)
|
|
247
|
+
if (probeRes.status === NonceStatus.UNUSED || probeRes.status === NonceStatus.CANCELED) {
|
|
248
|
+
const validBefore = record.validBefore || 0;
|
|
249
|
+
const safeExpiry = validBefore + this.safeBufferSeconds;
|
|
250
|
+
if (now > safeExpiry) {
|
|
251
|
+
// Authorization window safely expired and nonce unspent -> safe to release!
|
|
252
|
+
this.#coordinator.reconcileRelease(intentId, `Nonce confirmed unspent and validBefore expired (${now} > ${safeExpiry})`);
|
|
253
|
+
const updatedIntent = this.#coordinator.getIntent(intentId);
|
|
254
|
+
const updatedLedger = this.#coordinator.getLedgerState(intentId) || BudgetLedgerState.RELEASED;
|
|
255
|
+
return new BuyerReconciliationReport({
|
|
256
|
+
intentId,
|
|
257
|
+
previousIntentStatus: prevStatus,
|
|
258
|
+
newIntentStatus: updatedIntent.status,
|
|
259
|
+
ledgerState: updatedLedger,
|
|
260
|
+
outcome: BuyerReconciliationOutcome.RELEASED,
|
|
261
|
+
reason: `Authorization expired (${now} > ${safeExpiry}) with unspent nonce; quota released`,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
// Anti-Double-Spend Guard: validBefore has NOT expired yet!
|
|
265
|
+
return new BuyerReconciliationReport({
|
|
266
|
+
intentId,
|
|
267
|
+
previousIntentStatus: prevStatus,
|
|
268
|
+
newIntentStatus: record.status,
|
|
269
|
+
ledgerState,
|
|
270
|
+
outcome: BuyerReconciliationOutcome.HELD,
|
|
271
|
+
reason: `Authorization still within validBefore window (${now} <= ${safeExpiry}); preserving RESERVED`,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
// Case 3: Probe status is UNKNOWN (network error, timeout, RPC split) -> fail-closed
|
|
275
|
+
return new BuyerReconciliationReport({
|
|
276
|
+
intentId,
|
|
277
|
+
previousIntentStatus: prevStatus,
|
|
278
|
+
newIntentStatus: record.status,
|
|
279
|
+
ledgerState,
|
|
280
|
+
outcome: BuyerReconciliationOutcome.SKIPPED_UNKNOWN,
|
|
281
|
+
reason: `Probe returned UNKNOWN (${probeRes.detail || 'no detail'}); fail-closed preserve RESERVED`,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
async reconcileAllHeld() {
|
|
285
|
+
const intents = this.#coordinator.listIntents([
|
|
286
|
+
PaymentIntentState.RECOVERY_HOLD,
|
|
287
|
+
PaymentIntentState.SIGNATURE_SENT,
|
|
288
|
+
]);
|
|
289
|
+
const reports = [];
|
|
290
|
+
for (const it of intents) {
|
|
291
|
+
const rep = await this.reconcileIntent(it.intentId);
|
|
292
|
+
reports.push(rep);
|
|
293
|
+
}
|
|
294
|
+
return reports;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
//# sourceMappingURL=reconciliation.js.map
|