@adastracomputing/ink 0.1.0-alpha.2 → 0.1.0-alpha.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/CHANGELOG.md +56 -5
- package/CODE_OF_CONDUCT.md +1 -1
- package/README.md +7 -5
- package/SECURITY.md +1 -1
- package/bin/verify-inclusion-impl.mjs +4 -1
- package/dist/audit/inclusion-receipt.d.ts +142 -0
- package/dist/audit/inclusion-receipt.js +496 -0
- package/dist/crypto/ink.d.ts +178 -0
- package/dist/crypto/ink.js +915 -0
- package/dist/crypto/keys.d.ts +42 -0
- package/dist/crypto/keys.js +179 -0
- package/dist/crypto/multi-key-verify.d.ts +29 -0
- package/dist/crypto/multi-key-verify.js +153 -0
- package/dist/crypto/sign.d.ts +17 -0
- package/dist/crypto/sign.js +152 -0
- package/dist/crypto/verify.js +1 -0
- package/dist/discovery/agent-card.d.ts +83 -0
- package/dist/discovery/agent-card.js +545 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +15 -0
- package/dist/ink/checkpoint.d.ts +19 -0
- package/dist/ink/checkpoint.js +69 -0
- package/dist/ink/discovery-gating.d.ts +237 -0
- package/dist/ink/discovery-gating.js +91 -0
- package/dist/ink/handshake-budget.d.ts +90 -0
- package/dist/ink/handshake-budget.js +397 -0
- package/dist/ink/receipts.d.ts +31 -0
- package/dist/ink/receipts.js +89 -0
- package/dist/ink/transport-auth.d.ts +47 -0
- package/dist/ink/transport-auth.js +77 -0
- package/dist/middleware/ink-auth.d.ts +68 -0
- package/dist/middleware/ink-auth.js +214 -0
- package/dist/models/agent-card.d.ts +154 -0
- package/dist/models/agent-card.js +59 -0
- package/dist/models/ink-audit.d.ts +344 -0
- package/dist/models/ink-audit.js +167 -0
- package/dist/models/ink-handshake.d.ts +129 -0
- package/dist/models/ink-handshake.js +89 -0
- package/dist/models/intent.d.ts +437 -0
- package/dist/models/intent.js +172 -0
- package/dist/models/key-entry.d.ts +60 -0
- package/dist/models/key-entry.js +13 -0
- package/dist/models/profile.d.ts +61 -0
- package/dist/models/profile.js +24 -0
- package/docs/maturity.md +3 -3
- package/docs/threat-model.md +1 -1
- package/package.json +17 -13
- package/specs/ink-auditability.md +37 -12
- package/specs/ink-compliance-checklist.md +9 -1
- package/src/audit/inclusion-receipt.ts +0 -268
- package/src/crypto/ink.ts +0 -902
- package/src/crypto/keys.ts +0 -210
- package/src/crypto/multi-key-verify.ts +0 -170
- package/src/crypto/sign.ts +0 -155
- package/src/discovery/agent-card.ts +0 -508
- package/src/index.ts +0 -67
- package/src/ink/checkpoint.ts +0 -75
- package/src/ink/discovery-gating.ts +0 -147
- package/src/ink/handshake-budget.ts +0 -413
- package/src/ink/receipts.ts +0 -114
- package/src/ink/transport-auth.ts +0 -96
- package/src/middleware/ink-auth.ts +0 -263
- package/src/models/agent-card.ts +0 -63
- package/src/models/ink-audit.ts +0 -205
- package/src/models/ink-handshake.ts +0 -123
- package/src/models/intent.ts +0 -201
- package/src/models/key-entry.ts +0 -52
- package/src/models/profile.ts +0 -31
- /package/{src/crypto/verify.ts → dist/crypto/verify.d.ts} +0 -0
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability-gated Agent Card discovery.
|
|
3
|
+
*
|
|
4
|
+
* Implements §6 of the INK Containment spec:
|
|
5
|
+
* - Redacted cards for unauthenticated requests on non-public visibility
|
|
6
|
+
* - Authenticated card query endpoint schemas
|
|
7
|
+
* - Access denial response schemas
|
|
8
|
+
*/
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import { type AgentCard } from "../models/agent-card.js";
|
|
11
|
+
import type { AgentCardVisibility } from "../models/ink-handshake.js";
|
|
12
|
+
export { AgentCardVisibilitySchema, type AgentCardVisibility } from "../models/ink-handshake.js";
|
|
13
|
+
export interface RedactedAgentCard {
|
|
14
|
+
type: "tulpa.agent.card";
|
|
15
|
+
version: "1.0";
|
|
16
|
+
agentId: string;
|
|
17
|
+
displayName?: string;
|
|
18
|
+
visibility: "network_only" | "capability_gated";
|
|
19
|
+
supportsInk: true;
|
|
20
|
+
discoveryMode: "authenticate_for_details";
|
|
21
|
+
/**
|
|
22
|
+
* Bootstrap / legacy single public key. Preserved so peers can still verify
|
|
23
|
+
* Ed25519 signatures from cards without a full keys block.
|
|
24
|
+
*/
|
|
25
|
+
publicKeyMultibase: string;
|
|
26
|
+
/**
|
|
27
|
+
* Authoritative signing key set (rotation). Public material only — revealing
|
|
28
|
+
* these is safe and necessary so peers can discover key rotations even when
|
|
29
|
+
* the rest of the Card is gated. Without this, an agent that rotated to a
|
|
30
|
+
* new key under network_only / capability_gated visibility would be
|
|
31
|
+
* unverifiable to first-contact peers.
|
|
32
|
+
*/
|
|
33
|
+
keys?: {
|
|
34
|
+
signing: Array<{
|
|
35
|
+
keyId: string;
|
|
36
|
+
publicKeyMultibase: string;
|
|
37
|
+
status: "active" | "retired" | "revoked";
|
|
38
|
+
/** Validity-window fields are preserved in the redacted card so a
|
|
39
|
+
* first-contact peer that only ever sees the redacted form still
|
|
40
|
+
* enforces the same `[validFrom, validUntil]` bound the full card
|
|
41
|
+
* would. Stripping them creates a softer verification path. */
|
|
42
|
+
validFrom?: string;
|
|
43
|
+
validUntil?: string;
|
|
44
|
+
revokedAt?: string;
|
|
45
|
+
}>;
|
|
46
|
+
};
|
|
47
|
+
updatedAt: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Build a redacted Agent Card from a full card.
|
|
51
|
+
*
|
|
52
|
+
* Strips capabilities, endpoints, availability, profile, and other
|
|
53
|
+
* sensitive-on-a-closed-card fields. **Public signing keys are preserved**:
|
|
54
|
+
* Ed25519 public keys are not secrets — they are the identity. Hiding them
|
|
55
|
+
* would prevent peers from verifying signatures across key rotation when the
|
|
56
|
+
* full Card is gated.
|
|
57
|
+
*/
|
|
58
|
+
export declare function buildRedactedCard(card: AgentCard): RedactedAgentCard;
|
|
59
|
+
export declare const AgentCardQuerySchema: z.ZodObject<{
|
|
60
|
+
protocol: z.ZodLiteral<"ink/0.1">;
|
|
61
|
+
type: z.ZodLiteral<"network.tulpa.agent_card_query">;
|
|
62
|
+
from: z.ZodString;
|
|
63
|
+
nonce: z.ZodString;
|
|
64
|
+
timestamp: z.ZodString;
|
|
65
|
+
requestedFields: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
66
|
+
}, z.core.$strip>;
|
|
67
|
+
export type AgentCardQuery = z.infer<typeof AgentCardQuerySchema>;
|
|
68
|
+
export declare const AgentCardResponseSchema: z.ZodObject<{
|
|
69
|
+
protocol: z.ZodLiteral<"ink/0.1">;
|
|
70
|
+
type: z.ZodLiteral<"network.tulpa.agent_card_response">;
|
|
71
|
+
card: z.ZodObject<{
|
|
72
|
+
protocol: z.ZodLiteral<"ink/0.1">;
|
|
73
|
+
agentId: z.ZodString;
|
|
74
|
+
ownerDid: z.ZodOptional<z.ZodString>;
|
|
75
|
+
ownerHandle: z.ZodOptional<z.ZodString>;
|
|
76
|
+
atprotoRecordUri: z.ZodOptional<z.ZodString>;
|
|
77
|
+
handle: z.ZodString;
|
|
78
|
+
displayName: z.ZodString;
|
|
79
|
+
endpoint: z.ZodString;
|
|
80
|
+
publicKeyMultibase: z.ZodString;
|
|
81
|
+
profileSnapshot: z.ZodOptional<z.ZodObject<{
|
|
82
|
+
headline: z.ZodString;
|
|
83
|
+
skills: z.ZodArray<z.ZodString>;
|
|
84
|
+
interests: z.ZodArray<z.ZodString>;
|
|
85
|
+
availability: z.ZodOptional<z.ZodObject<{
|
|
86
|
+
timezone: z.ZodString;
|
|
87
|
+
meetingHours: z.ZodOptional<z.ZodString>;
|
|
88
|
+
responseSla: z.ZodOptional<z.ZodString>;
|
|
89
|
+
}, z.core.$strip>>;
|
|
90
|
+
openTo: z.ZodArray<z.ZodString>;
|
|
91
|
+
}, z.core.$strip>>;
|
|
92
|
+
capabilities: z.ZodObject<{
|
|
93
|
+
intentsAccepted: z.ZodArray<z.ZodEnum<{
|
|
94
|
+
schedule_meeting: "schedule_meeting";
|
|
95
|
+
schedule_meeting_response: "schedule_meeting_response";
|
|
96
|
+
intro_request: "intro_request";
|
|
97
|
+
intro_response: "intro_response";
|
|
98
|
+
opportunity: "opportunity";
|
|
99
|
+
opportunity_response: "opportunity_response";
|
|
100
|
+
follow_up: "follow_up";
|
|
101
|
+
ask: "ask";
|
|
102
|
+
ask_response: "ask_response";
|
|
103
|
+
connection_request: "connection_request";
|
|
104
|
+
connection_response: "connection_response";
|
|
105
|
+
context_share: "context_share";
|
|
106
|
+
ping: "ping";
|
|
107
|
+
retract: "retract";
|
|
108
|
+
multi_party_sync: "multi_party_sync";
|
|
109
|
+
}>>;
|
|
110
|
+
intentsSent: z.ZodArray<z.ZodEnum<{
|
|
111
|
+
schedule_meeting: "schedule_meeting";
|
|
112
|
+
schedule_meeting_response: "schedule_meeting_response";
|
|
113
|
+
intro_request: "intro_request";
|
|
114
|
+
intro_response: "intro_response";
|
|
115
|
+
opportunity: "opportunity";
|
|
116
|
+
opportunity_response: "opportunity_response";
|
|
117
|
+
follow_up: "follow_up";
|
|
118
|
+
ask: "ask";
|
|
119
|
+
ask_response: "ask_response";
|
|
120
|
+
connection_request: "connection_request";
|
|
121
|
+
connection_response: "connection_response";
|
|
122
|
+
context_share: "context_share";
|
|
123
|
+
ping: "ping";
|
|
124
|
+
retract: "retract";
|
|
125
|
+
multi_party_sync: "multi_party_sync";
|
|
126
|
+
}>>;
|
|
127
|
+
receipts: z.ZodOptional<z.ZodObject<{
|
|
128
|
+
send: z.ZodBoolean;
|
|
129
|
+
dispositions: z.ZodArray<z.ZodEnum<{
|
|
130
|
+
received: "received";
|
|
131
|
+
delivered: "delivered";
|
|
132
|
+
acted: "acted";
|
|
133
|
+
rejected: "rejected";
|
|
134
|
+
expired: "expired";
|
|
135
|
+
}>>;
|
|
136
|
+
}, z.core.$strip>>;
|
|
137
|
+
auditExchange: z.ZodOptional<z.ZodBoolean>;
|
|
138
|
+
thirdPartyAudit: z.ZodOptional<z.ZodObject<{
|
|
139
|
+
services: z.ZodArray<z.ZodObject<{
|
|
140
|
+
endpoint: z.ZodString;
|
|
141
|
+
did: z.ZodString;
|
|
142
|
+
publicKey: z.ZodString;
|
|
143
|
+
}, z.core.$strip>>;
|
|
144
|
+
submitPolicy: z.ZodEnum<{
|
|
145
|
+
none: "none";
|
|
146
|
+
all: "all";
|
|
147
|
+
high_value: "high_value";
|
|
148
|
+
}>;
|
|
149
|
+
}, z.core.$strip>>;
|
|
150
|
+
}, z.core.$strip>;
|
|
151
|
+
availability: z.ZodObject<{
|
|
152
|
+
timezone: z.ZodString;
|
|
153
|
+
meetingHours: z.ZodOptional<z.ZodString>;
|
|
154
|
+
responseSla: z.ZodOptional<z.ZodString>;
|
|
155
|
+
}, z.core.$strip>;
|
|
156
|
+
keys: z.ZodOptional<z.ZodObject<{
|
|
157
|
+
signing: z.ZodArray<z.ZodObject<{
|
|
158
|
+
keyId: z.ZodString;
|
|
159
|
+
algorithm: z.ZodEnum<{
|
|
160
|
+
Ed25519: "Ed25519";
|
|
161
|
+
X25519: "X25519";
|
|
162
|
+
}>;
|
|
163
|
+
publicKeyMultibase: z.ZodString;
|
|
164
|
+
status: z.ZodEnum<{
|
|
165
|
+
active: "active";
|
|
166
|
+
retired: "retired";
|
|
167
|
+
revoked: "revoked";
|
|
168
|
+
}>;
|
|
169
|
+
validFrom: z.ZodString;
|
|
170
|
+
validUntil: z.ZodOptional<z.ZodString>;
|
|
171
|
+
revokedAt: z.ZodOptional<z.ZodString>;
|
|
172
|
+
revokeReason: z.ZodOptional<z.ZodString>;
|
|
173
|
+
}, z.core.$strip>>;
|
|
174
|
+
encryption: z.ZodArray<z.ZodObject<{
|
|
175
|
+
keyId: z.ZodString;
|
|
176
|
+
algorithm: z.ZodEnum<{
|
|
177
|
+
Ed25519: "Ed25519";
|
|
178
|
+
X25519: "X25519";
|
|
179
|
+
}>;
|
|
180
|
+
publicKeyMultibase: z.ZodString;
|
|
181
|
+
status: z.ZodEnum<{
|
|
182
|
+
active: "active";
|
|
183
|
+
retired: "retired";
|
|
184
|
+
revoked: "revoked";
|
|
185
|
+
}>;
|
|
186
|
+
validFrom: z.ZodString;
|
|
187
|
+
validUntil: z.ZodOptional<z.ZodString>;
|
|
188
|
+
revokedAt: z.ZodOptional<z.ZodString>;
|
|
189
|
+
revokeReason: z.ZodOptional<z.ZodString>;
|
|
190
|
+
}, z.core.$strip>>;
|
|
191
|
+
}, z.core.$strip>>;
|
|
192
|
+
currentSigningKeyId: z.ZodOptional<z.ZodString>;
|
|
193
|
+
currentEncryptionKeyId: z.ZodOptional<z.ZodString>;
|
|
194
|
+
keySetVersion: z.ZodOptional<z.ZodNumber>;
|
|
195
|
+
visibility: z.ZodOptional<z.ZodEnum<{
|
|
196
|
+
public: "public";
|
|
197
|
+
network_only: "network_only";
|
|
198
|
+
capability_gated: "capability_gated";
|
|
199
|
+
private: "private";
|
|
200
|
+
}>>;
|
|
201
|
+
governance: z.ZodOptional<z.ZodObject<{
|
|
202
|
+
maxAcceptedDelegationDepth: z.ZodOptional<z.ZodNumber>;
|
|
203
|
+
supportedTransports: z.ZodOptional<z.ZodArray<z.ZodEnum<{
|
|
204
|
+
ink_http: "ink_http";
|
|
205
|
+
ink_ws: "ink_ws";
|
|
206
|
+
extension_api: "extension_api";
|
|
207
|
+
voice: "voice";
|
|
208
|
+
line_phone: "line_phone";
|
|
209
|
+
human_review_queue: "human_review_queue";
|
|
210
|
+
}>>>;
|
|
211
|
+
supportsCapabilityGatedDiscovery: z.ZodOptional<z.ZodBoolean>;
|
|
212
|
+
handshakeBudget: z.ZodOptional<z.ZodObject<{
|
|
213
|
+
maxChallengesPerCorrelation: z.ZodOptional<z.ZodNumber>;
|
|
214
|
+
maxIntentsPerMinute: z.ZodOptional<z.ZodNumber>;
|
|
215
|
+
}, z.core.$strip>>;
|
|
216
|
+
}, z.core.$strip>>;
|
|
217
|
+
}, z.core.$strip>;
|
|
218
|
+
grantedFields: z.ZodArray<z.ZodString>;
|
|
219
|
+
timestamp: z.ZodString;
|
|
220
|
+
}, z.core.$strip>;
|
|
221
|
+
export type AgentCardResponse = z.infer<typeof AgentCardResponseSchema>;
|
|
222
|
+
export declare const AgentCardDeniedSchema: z.ZodObject<{
|
|
223
|
+
protocol: z.ZodLiteral<"ink/0.1">;
|
|
224
|
+
type: z.ZodLiteral<"network.tulpa.agent_card_denied">;
|
|
225
|
+
reason: z.ZodEnum<{
|
|
226
|
+
unknown_requester: "unknown_requester";
|
|
227
|
+
insufficient_trust: "insufficient_trust";
|
|
228
|
+
not_connected: "not_connected";
|
|
229
|
+
}>;
|
|
230
|
+
timestamp: z.ZodString;
|
|
231
|
+
}, z.core.$strip>;
|
|
232
|
+
export type AgentCardDenied = z.infer<typeof AgentCardDeniedSchema>;
|
|
233
|
+
/**
|
|
234
|
+
* Determine whether an unauthenticated GET should return a full card
|
|
235
|
+
* or a redacted card based on visibility setting.
|
|
236
|
+
*/
|
|
237
|
+
export declare function shouldRedactOnGet(visibility: AgentCardVisibility): boolean;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability-gated Agent Card discovery.
|
|
3
|
+
*
|
|
4
|
+
* Implements §6 of the INK Containment spec:
|
|
5
|
+
* - Redacted cards for unauthenticated requests on non-public visibility
|
|
6
|
+
* - Authenticated card query endpoint schemas
|
|
7
|
+
* - Access denial response schemas
|
|
8
|
+
*/
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import { AgentCardSchema } from "../models/agent-card.js";
|
|
11
|
+
export { AgentCardVisibilitySchema } from "../models/ink-handshake.js";
|
|
12
|
+
/**
|
|
13
|
+
* Build a redacted Agent Card from a full card.
|
|
14
|
+
*
|
|
15
|
+
* Strips capabilities, endpoints, availability, profile, and other
|
|
16
|
+
* sensitive-on-a-closed-card fields. **Public signing keys are preserved**:
|
|
17
|
+
* Ed25519 public keys are not secrets — they are the identity. Hiding them
|
|
18
|
+
* would prevent peers from verifying signatures across key rotation when the
|
|
19
|
+
* full Card is gated.
|
|
20
|
+
*/
|
|
21
|
+
export function buildRedactedCard(card) {
|
|
22
|
+
// `visibility` is typed on AgentCard but a malformed runtime value can
|
|
23
|
+
// still slip through (e.g. a card deserialised without schema validation).
|
|
24
|
+
// Default to `capability_gated` — the least-privilege visibility — when
|
|
25
|
+
// the field is anything other than the known enum members.
|
|
26
|
+
const visibility = card.visibility === "network_only" ? "network_only" : "capability_gated";
|
|
27
|
+
const out = {
|
|
28
|
+
type: "tulpa.agent.card",
|
|
29
|
+
version: "1.0",
|
|
30
|
+
agentId: card.agentId,
|
|
31
|
+
displayName: card.displayName,
|
|
32
|
+
supportsInk: true,
|
|
33
|
+
discoveryMode: "authenticate_for_details",
|
|
34
|
+
visibility,
|
|
35
|
+
publicKeyMultibase: card.publicKeyMultibase,
|
|
36
|
+
updatedAt: new Date().toISOString(),
|
|
37
|
+
};
|
|
38
|
+
// Preserve `keys.signing` on PRESENCE, not on truthiness/length.
|
|
39
|
+
// An empty signing array is an authoritative "no usable signing
|
|
40
|
+
// keys" statement (e.g. all keys revoked) and the verifier's key
|
|
41
|
+
// rotation authority rule treats it as a reject-all signal. Dropping
|
|
42
|
+
// the field here would let peers fall back to publicKeyMultibase or
|
|
43
|
+
// bootstrap derivation, undoing the rotation. See
|
|
44
|
+
// multi-key-verify and middleware/ink-auth for the authority rule.
|
|
45
|
+
if (Array.isArray(card.keys?.signing)) {
|
|
46
|
+
out.keys = {
|
|
47
|
+
signing: card.keys.signing.map((k) => ({
|
|
48
|
+
keyId: k.keyId,
|
|
49
|
+
publicKeyMultibase: k.publicKeyMultibase,
|
|
50
|
+
status: k.status,
|
|
51
|
+
// Preserve validity / revocation metadata: these are public
|
|
52
|
+
// facts about the key, not secrets, and a redacted card must
|
|
53
|
+
// not be weaker than the full card for signature verification.
|
|
54
|
+
validFrom: k.validFrom,
|
|
55
|
+
validUntil: k.validUntil,
|
|
56
|
+
revokedAt: k.revokedAt,
|
|
57
|
+
})),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
// ── Query/Response schemas ──
|
|
63
|
+
export const AgentCardQuerySchema = z.object({
|
|
64
|
+
protocol: z.literal("ink/0.1"),
|
|
65
|
+
type: z.literal("network.tulpa.agent_card_query"),
|
|
66
|
+
from: z.string(),
|
|
67
|
+
nonce: z.string(),
|
|
68
|
+
timestamp: z.string().datetime(),
|
|
69
|
+
requestedFields: z.array(z.string()).optional(),
|
|
70
|
+
});
|
|
71
|
+
export const AgentCardResponseSchema = z.object({
|
|
72
|
+
protocol: z.literal("ink/0.1"),
|
|
73
|
+
type: z.literal("network.tulpa.agent_card_response"),
|
|
74
|
+
card: AgentCardSchema,
|
|
75
|
+
grantedFields: z.array(z.string()),
|
|
76
|
+
timestamp: z.string().datetime(),
|
|
77
|
+
});
|
|
78
|
+
export const AgentCardDeniedSchema = z.object({
|
|
79
|
+
protocol: z.literal("ink/0.1"),
|
|
80
|
+
type: z.literal("network.tulpa.agent_card_denied"),
|
|
81
|
+
reason: z.enum(["unknown_requester", "insufficient_trust", "not_connected"]),
|
|
82
|
+
timestamp: z.string().datetime(),
|
|
83
|
+
});
|
|
84
|
+
// ── Visibility check ──
|
|
85
|
+
/**
|
|
86
|
+
* Determine whether an unauthenticated GET should return a full card
|
|
87
|
+
* or a redacted card based on visibility setting.
|
|
88
|
+
*/
|
|
89
|
+
export function shouldRedactOnGet(visibility) {
|
|
90
|
+
return visibility !== "public";
|
|
91
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Handshake flood resistance — per-correlation and per-sender budget tracking.
|
|
3
|
+
*
|
|
4
|
+
* Implements §5 of the INK Containment spec:
|
|
5
|
+
* - Per-correlation budgets: max challenges, terminal states, total transitions, TTL
|
|
6
|
+
* - Per-sender rate limits: sliding window for intents and total handshake messages
|
|
7
|
+
* - First violation returns typed rejection with backoff hint
|
|
8
|
+
* - Subsequent violations are silent drops
|
|
9
|
+
*/
|
|
10
|
+
import type { InkBackoffHint } from "../models/ink-handshake.js";
|
|
11
|
+
export interface BudgetCheckResult {
|
|
12
|
+
allowed: boolean;
|
|
13
|
+
reason?: string;
|
|
14
|
+
backoffHint?: InkBackoffHint;
|
|
15
|
+
silentDrop?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface HandshakeBudgetConfig {
|
|
18
|
+
maxChallenges?: number;
|
|
19
|
+
maxTotalTransitions?: number;
|
|
20
|
+
maxIntentsPerMinute?: number;
|
|
21
|
+
maxHandshakeMsgsPerMinute?: number;
|
|
22
|
+
maxCorrelations?: number;
|
|
23
|
+
maxSenders?: number;
|
|
24
|
+
maxRejectionEntries?: number;
|
|
25
|
+
}
|
|
26
|
+
export declare class HandshakeBudgetTracker {
|
|
27
|
+
private correlations;
|
|
28
|
+
private senders;
|
|
29
|
+
private rejectionsSent;
|
|
30
|
+
private senderRejectionsSent;
|
|
31
|
+
private readonly maxChallenges;
|
|
32
|
+
private readonly maxTotalTransitions;
|
|
33
|
+
private readonly maxIntentsPerMinute;
|
|
34
|
+
private readonly maxHandshakeMsgsPerMinute;
|
|
35
|
+
private readonly maxCorrelations;
|
|
36
|
+
private readonly maxSenders;
|
|
37
|
+
private readonly maxRejectionEntries;
|
|
38
|
+
private checkCounter;
|
|
39
|
+
constructor(config?: HandshakeBudgetConfig);
|
|
40
|
+
/**
|
|
41
|
+
* Side-effect-free budget check. Returns the same BudgetCheckResult as
|
|
42
|
+
* checkAndRecord without mutating any internal state: no sender activity
|
|
43
|
+
* recorded, no correlation row created for an unknown intentRef, no
|
|
44
|
+
* transition counter incremented, no terminal flag set, and no rejection
|
|
45
|
+
* bookkeeping (rejectionsSent / senderRejectionsSent) advanced. Use this
|
|
46
|
+
* when you want to gate on budget BEFORE running expensive validation
|
|
47
|
+
* (signature verification, state-machine), then call recordAccepted()
|
|
48
|
+
* only on acceptance.
|
|
49
|
+
*
|
|
50
|
+
* Without the split, an invalid signed envelope for a known intentRef
|
|
51
|
+
* could pass the budget check, mark the correlation as terminal, then
|
|
52
|
+
* fail downstream validation — blocking later legitimate traffic for
|
|
53
|
+
* that correlation. Integrators that want the original eager-commit
|
|
54
|
+
* semantics can keep calling checkAndRecord.
|
|
55
|
+
*/
|
|
56
|
+
check(params: {
|
|
57
|
+
correlationId: string;
|
|
58
|
+
fromDid: string;
|
|
59
|
+
messageType: "intent" | "challenge" | "rejection" | "resolution";
|
|
60
|
+
intentExpiresAt?: string;
|
|
61
|
+
}): BudgetCheckResult;
|
|
62
|
+
/**
|
|
63
|
+
* Commit a previously-checked acceptance. The check() return value is
|
|
64
|
+
* not load-bearing here — this method runs the same checks again and
|
|
65
|
+
* applies the mutation. Callers are responsible for not re-running
|
|
66
|
+
* recordAccepted() on the same message; a nonce cache typically
|
|
67
|
+
* prevents that path.
|
|
68
|
+
*/
|
|
69
|
+
recordAccepted(params: {
|
|
70
|
+
correlationId: string;
|
|
71
|
+
fromDid: string;
|
|
72
|
+
messageType: "intent" | "challenge" | "rejection" | "resolution";
|
|
73
|
+
intentExpiresAt?: string;
|
|
74
|
+
}): BudgetCheckResult;
|
|
75
|
+
checkAndRecord(params: {
|
|
76
|
+
correlationId: string;
|
|
77
|
+
fromDid: string;
|
|
78
|
+
messageType: "intent" | "challenge" | "rejection" | "resolution";
|
|
79
|
+
intentExpiresAt?: string;
|
|
80
|
+
}): BudgetCheckResult;
|
|
81
|
+
private checkOrRecord;
|
|
82
|
+
pruneExpired(): void;
|
|
83
|
+
private checkSenderLimits;
|
|
84
|
+
private makeSenderRejection;
|
|
85
|
+
private recordSenderActivity;
|
|
86
|
+
private makeRejection;
|
|
87
|
+
private enforceRejectionBounds;
|
|
88
|
+
private enforceSenderBounds;
|
|
89
|
+
private enforceMemoryBounds;
|
|
90
|
+
}
|