@adastracomputing/ink 0.1.0-alpha.3 → 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.
Files changed (61) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/audit/inclusion-receipt.d.ts +142 -0
  3. package/dist/audit/inclusion-receipt.js +496 -0
  4. package/dist/crypto/ink.d.ts +178 -0
  5. package/dist/crypto/ink.js +915 -0
  6. package/dist/crypto/keys.d.ts +42 -0
  7. package/dist/crypto/keys.js +179 -0
  8. package/dist/crypto/multi-key-verify.d.ts +29 -0
  9. package/dist/crypto/multi-key-verify.js +153 -0
  10. package/dist/crypto/sign.d.ts +17 -0
  11. package/dist/crypto/sign.js +152 -0
  12. package/dist/crypto/verify.js +1 -0
  13. package/dist/discovery/agent-card.d.ts +83 -0
  14. package/dist/discovery/agent-card.js +545 -0
  15. package/dist/index.d.ts +12 -0
  16. package/dist/index.js +15 -0
  17. package/dist/ink/checkpoint.d.ts +19 -0
  18. package/dist/ink/checkpoint.js +69 -0
  19. package/dist/ink/discovery-gating.d.ts +237 -0
  20. package/dist/ink/discovery-gating.js +91 -0
  21. package/dist/ink/handshake-budget.d.ts +90 -0
  22. package/dist/ink/handshake-budget.js +397 -0
  23. package/dist/ink/receipts.d.ts +31 -0
  24. package/dist/ink/receipts.js +89 -0
  25. package/dist/ink/transport-auth.d.ts +47 -0
  26. package/dist/ink/transport-auth.js +77 -0
  27. package/dist/middleware/ink-auth.d.ts +68 -0
  28. package/dist/middleware/ink-auth.js +214 -0
  29. package/dist/models/agent-card.d.ts +154 -0
  30. package/dist/models/agent-card.js +59 -0
  31. package/dist/models/ink-audit.d.ts +344 -0
  32. package/dist/models/ink-audit.js +167 -0
  33. package/dist/models/ink-handshake.d.ts +129 -0
  34. package/dist/models/ink-handshake.js +89 -0
  35. package/dist/models/intent.d.ts +437 -0
  36. package/dist/models/intent.js +172 -0
  37. package/dist/models/key-entry.d.ts +60 -0
  38. package/dist/models/key-entry.js +13 -0
  39. package/dist/models/profile.d.ts +61 -0
  40. package/dist/models/profile.js +24 -0
  41. package/package.json +15 -11
  42. package/src/audit/inclusion-receipt.ts +0 -604
  43. package/src/crypto/ink.ts +0 -1046
  44. package/src/crypto/keys.ts +0 -210
  45. package/src/crypto/multi-key-verify.ts +0 -170
  46. package/src/crypto/sign.ts +0 -155
  47. package/src/discovery/agent-card.ts +0 -508
  48. package/src/index.ts +0 -73
  49. package/src/ink/checkpoint.ts +0 -75
  50. package/src/ink/discovery-gating.ts +0 -147
  51. package/src/ink/handshake-budget.ts +0 -413
  52. package/src/ink/receipts.ts +0 -114
  53. package/src/ink/transport-auth.ts +0 -96
  54. package/src/middleware/ink-auth.ts +0 -263
  55. package/src/models/agent-card.ts +0 -63
  56. package/src/models/ink-audit.ts +0 -205
  57. package/src/models/ink-handshake.ts +0 -123
  58. package/src/models/intent.ts +0 -201
  59. package/src/models/key-entry.ts +0 -52
  60. package/src/models/profile.ts +0 -31
  61. /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
+ }