@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.
- package/CHANGELOG.md +24 -0
- 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/package.json +15 -11
- package/src/audit/inclusion-receipt.ts +0 -604
- package/src/crypto/ink.ts +0 -1046
- 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 -73
- 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
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Transport-bound authorization for INK delegation chains.
|
|
3
|
-
*
|
|
4
|
-
* Ensures delegation tokens are only valid on the transports they were
|
|
5
|
-
* issued for. Implements §7 of the INK Containment spec with version-gated
|
|
6
|
-
* migration for legacy tokens.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { InkTransport } from "../models/ink-handshake.js";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Permissive transport set for legacy tokens during the 90-day migration window.
|
|
13
|
-
* Matches the set of transports that existed before transport scoping was introduced.
|
|
14
|
-
*/
|
|
15
|
-
export const LEGACY_MIGRATION_TRANSPORTS: InkTransport[] = [
|
|
16
|
-
"ink_http",
|
|
17
|
-
"extension_api",
|
|
18
|
-
"voice",
|
|
19
|
-
"line_phone",
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Hard deadline for the legacy transport migration window.
|
|
24
|
-
* After this date, tokens without tokenVersion get the strict default.
|
|
25
|
-
*/
|
|
26
|
-
export const LEGACY_MIGRATION_DEADLINE = new Date("2026-07-01T00:00:00Z");
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Resolve the effective allowed transports for a delegation token.
|
|
30
|
-
*
|
|
31
|
-
* Rules (per spec §1.2):
|
|
32
|
-
* - Explicit allowedTransports always wins
|
|
33
|
-
* - v0.3+ tokens without allowedTransports default to ["ink_http"]
|
|
34
|
-
* - Legacy tokens (no tokenVersion) default to permissive set before the
|
|
35
|
-
* migration deadline (2026-07-01), then ["ink_http"] after
|
|
36
|
-
*/
|
|
37
|
-
export function resolveEffectiveTransports(
|
|
38
|
-
allowedTransports: InkTransport[] | undefined,
|
|
39
|
-
tokenVersion: string | undefined,
|
|
40
|
-
now: Date = new Date(),
|
|
41
|
-
): InkTransport[] {
|
|
42
|
-
// Distinguish "field absent" from "field present and empty". An
|
|
43
|
-
// explicit empty array is a "this token allows no transports"
|
|
44
|
-
// statement — it MUST stay empty, never fall through to the legacy
|
|
45
|
-
// permissive set. Treating [] as "absent" would broaden a token that
|
|
46
|
-
// its issuer intended to deny.
|
|
47
|
-
if (Array.isArray(allowedTransports)) {
|
|
48
|
-
return [...allowedTransports];
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// v0.3+ token without explicit transports: strict default.
|
|
52
|
-
// Distinguish "field absent" (undefined) from "field present but
|
|
53
|
-
// malformed/empty". A token that supplies `tokenVersion: ""` is not
|
|
54
|
-
// a legacy token — it is a malformed new token. Treat anything
|
|
55
|
-
// present-and-not-undefined as a new token so a bad version string
|
|
56
|
-
// can't broaden transport scope during the migration window.
|
|
57
|
-
if (tokenVersion !== undefined) {
|
|
58
|
-
return ["ink_http"];
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Legacy token (tokenVersion truly absent): check against hard
|
|
62
|
-
// migration deadline.
|
|
63
|
-
if (now >= LEGACY_MIGRATION_DEADLINE) {
|
|
64
|
-
return ["ink_http"];
|
|
65
|
-
}
|
|
66
|
-
return [...LEGACY_MIGRATION_TRANSPORTS];
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Check if the current invocation transport is allowed by the delegation token.
|
|
71
|
-
*/
|
|
72
|
-
export function checkTransportAllowed(
|
|
73
|
-
currentTransport: InkTransport,
|
|
74
|
-
allowedTransports: InkTransport[],
|
|
75
|
-
): { allowed: true } | { allowed: false; reason: "transport_scope_violation" } {
|
|
76
|
-
if (allowedTransports.includes(currentTransport)) {
|
|
77
|
-
return { allowed: true };
|
|
78
|
-
}
|
|
79
|
-
return { allowed: false, reason: "transport_scope_violation" };
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Check that a child delegation hop's transports are a subset of the parent's.
|
|
84
|
-
* Each hop can only narrow, never widen the transport scope.
|
|
85
|
-
*/
|
|
86
|
-
export function checkTransportAttenuation(
|
|
87
|
-
parentTransports: InkTransport[],
|
|
88
|
-
childTransports: InkTransport[],
|
|
89
|
-
): { valid: true } | { valid: false; addedTransports: InkTransport[] } {
|
|
90
|
-
const parentSet = new Set(parentTransports);
|
|
91
|
-
const added = childTransports.filter((t) => !parentSet.has(t));
|
|
92
|
-
if (added.length > 0) {
|
|
93
|
-
return { valid: false, addedTransports: added };
|
|
94
|
-
}
|
|
95
|
-
return { valid: true };
|
|
96
|
-
}
|
|
@@ -1,263 +0,0 @@
|
|
|
1
|
-
import { verifyInkSignature, type InkSignInput, MAX_TIMESTAMP_AGE_MS, MAX_FUTURE_TIMESTAMP_MS } from "../crypto/ink.js";
|
|
2
|
-
import { extractPublicKeyFromAgentId } from "../crypto/keys.js";
|
|
3
|
-
import { verifyInkSignatureWithKeys } from "../crypto/multi-key-verify.js";
|
|
4
|
-
import type { CandidateKey, KeyStatus } from "../models/key-entry.js";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Pluggable nonce-record interface. The middleware uses this to enforce
|
|
8
|
-
* single-use semantics on body.nonce so a captured-and-replayed request
|
|
9
|
-
* is rejected even within the timestamp freshness window.
|
|
10
|
-
*/
|
|
11
|
-
export interface NonceStore {
|
|
12
|
-
has(nonce: string): boolean | Promise<boolean>;
|
|
13
|
-
add(nonce: string): void | Promise<void>;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Parse and verify an INK-Ed25519 Authorization header.
|
|
18
|
-
*
|
|
19
|
-
* The spec (§3.3) defines request signing as:
|
|
20
|
-
* Authorization: INK-Ed25519 <base64url(sig)>
|
|
21
|
-
*
|
|
22
|
-
* Signature base: ink/0.1\nMETHOD\nPATH\nrecipientDid\nJCS(body)\ntimestamp
|
|
23
|
-
*
|
|
24
|
-
* The body must contain `from` (sender DID/agentId — used to resolve the public key)
|
|
25
|
-
* and `timestamp` (used in the signature base).
|
|
26
|
-
*
|
|
27
|
-
* Also enforces timestamp freshness per §3.5:
|
|
28
|
-
* - Rejects timestamps older than 5 minutes
|
|
29
|
-
* - Rejects timestamps more than 30 seconds in the future
|
|
30
|
-
*
|
|
31
|
-
* Key resolution order:
|
|
32
|
-
* 1. resolveKeySet (multi-key, if provided and returns candidates)
|
|
33
|
-
* 2. resolvePublicKey (single-key from connection store)
|
|
34
|
-
* 3. extractPublicKeyFromAgentId (bootstrap fallback — only when no key set exists)
|
|
35
|
-
*/
|
|
36
|
-
export async function verifyInkAuth(opts: {
|
|
37
|
-
authHeader: string | undefined;
|
|
38
|
-
method: string;
|
|
39
|
-
path: string;
|
|
40
|
-
recipientAgentId: string;
|
|
41
|
-
body: Record<string, unknown>;
|
|
42
|
-
resolvePublicKey?: (agentId: string) => Uint8Array | null;
|
|
43
|
-
resolveKeySet?: (agentId: string) => CandidateKey[] | null;
|
|
44
|
-
/**
|
|
45
|
-
* When true, signatures that only verify against a retired key are
|
|
46
|
-
* rejected with `retired_key_for_live_auth`. Defaults to false so the
|
|
47
|
-
* middleware stays spec-conformant (active OR retired during rotation
|
|
48
|
-
* grace per the authority rule) but lets callers opt into the stricter
|
|
49
|
-
* policy for endpoints that should never accept a possibly-stolen
|
|
50
|
-
* retired key. Bootstrap and single-key (resolvePublicKey) verification
|
|
51
|
-
* paths are unaffected because they do not have status metadata.
|
|
52
|
-
*/
|
|
53
|
-
requireActiveKey?: boolean;
|
|
54
|
-
/**
|
|
55
|
-
* Single-use nonce enforcement. Required (fail-closed) because the
|
|
56
|
-
* 5-minute freshness window otherwise allows a captured signed request
|
|
57
|
-
* to replay. Pass a NonceStore to have the middleware check+record
|
|
58
|
-
* body.nonce, or pass the literal "deferred" to explicitly take
|
|
59
|
-
* responsibility for calling `checkReplay` (or equivalent) in the
|
|
60
|
-
* caller's own request pipeline. Omitting this option returns
|
|
61
|
-
* `nonce_handling_required` so misconfigured production deployments
|
|
62
|
-
* fail loudly.
|
|
63
|
-
*/
|
|
64
|
-
nonceStore: NonceStore | "deferred";
|
|
65
|
-
}): Promise<
|
|
66
|
-
| { valid: true; senderAgentId: string; keyId?: string; keyStatus?: KeyStatus }
|
|
67
|
-
| { valid: false; error: string }
|
|
68
|
-
> {
|
|
69
|
-
if (typeof opts.authHeader !== "string" || opts.authHeader.length === 0) {
|
|
70
|
-
return { valid: false, error: "missing_authorization" };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (opts.body === null || typeof opts.body !== "object" || Array.isArray(opts.body)) {
|
|
74
|
-
return { valid: false, error: "missing_sender" };
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
if (opts.authHeader.length > 512) {
|
|
78
|
-
return { valid: false, error: "invalid_auth_scheme" };
|
|
79
|
-
}
|
|
80
|
-
// Ed25519 signatures are exactly 86 base64url chars — tighten the regex to
|
|
81
|
-
// {86} so clearly-wrong lengths get rejected up front, rather than burning
|
|
82
|
-
// CPU on verifyInkSignature for a malformed value.
|
|
83
|
-
const match = opts.authHeader.match(/^INK-Ed25519\s+([A-Za-z0-9_-]{86})(?:\s+keyId=([A-Za-z0-9_:.-]{1,128}))?$/);
|
|
84
|
-
if (!match) {
|
|
85
|
-
return { valid: false, error: "invalid_auth_scheme" };
|
|
86
|
-
}
|
|
87
|
-
const signature = match[1]!;
|
|
88
|
-
const hintKeyId = match[2] ?? undefined;
|
|
89
|
-
|
|
90
|
-
const senderDid = opts.body.from;
|
|
91
|
-
if (senderDid !== undefined && typeof senderDid !== "string") {
|
|
92
|
-
return { valid: false, error: "invalid_from_field" };
|
|
93
|
-
}
|
|
94
|
-
if (!senderDid) {
|
|
95
|
-
return { valid: false, error: "missing_sender" };
|
|
96
|
-
}
|
|
97
|
-
// Cap sender DID length before passing to key resolvers and base58 decoding.
|
|
98
|
-
// Real agent IDs are ~50-100 chars; 256 leaves generous headroom while
|
|
99
|
-
// preventing CPU/memory waste on huge attacker-supplied values.
|
|
100
|
-
if (senderDid.length > 256) {
|
|
101
|
-
return { valid: false, error: "invalid_from_field" };
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const timestamp = opts.body.timestamp;
|
|
105
|
-
if (typeof timestamp !== "string" || timestamp.length === 0) {
|
|
106
|
-
return { valid: false, error: "missing_timestamp" };
|
|
107
|
-
}
|
|
108
|
-
// Cap length BEFORE handing to Date.parse. Real ISO 8601 timestamps
|
|
109
|
-
// are ≤ ~30 chars; we cap at 64 (matches buildSignatureBase). Without
|
|
110
|
-
// this, an unauthenticated request with a multi-megabyte timestamp
|
|
111
|
-
// string burns CPU inside the engine's Date parser before the
|
|
112
|
-
// signature ever runs.
|
|
113
|
-
if (timestamp.length > 64) {
|
|
114
|
-
return { valid: false, error: "invalid_timestamp" };
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Timestamp freshness check (§3.5)
|
|
118
|
-
const msgTime = new Date(timestamp).getTime();
|
|
119
|
-
if (isNaN(msgTime)) {
|
|
120
|
-
return { valid: false, error: "invalid_timestamp" };
|
|
121
|
-
}
|
|
122
|
-
const now = Date.now();
|
|
123
|
-
const drift = msgTime - now;
|
|
124
|
-
if (drift > MAX_FUTURE_TIMESTAMP_MS) {
|
|
125
|
-
return { valid: false, error: "timestamp_too_far_future" };
|
|
126
|
-
}
|
|
127
|
-
if (-drift > MAX_TIMESTAMP_AGE_MS) {
|
|
128
|
-
return { valid: false, error: "timestamp_expired" };
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Fail-closed nonce policy. Callers must either pass a NonceStore
|
|
132
|
-
// (middleware enforces single-use within the freshness window) or
|
|
133
|
-
// explicitly pass "deferred" (caller commits to calling checkReplay
|
|
134
|
-
// or equivalent in their request pipeline). An omitted/malformed
|
|
135
|
-
// nonceStore returns nonce_handling_required so a production
|
|
136
|
-
// deployment without nonce handling fails loudly rather than
|
|
137
|
-
// silently accepting replays.
|
|
138
|
-
const storeIsObject =
|
|
139
|
-
opts.nonceStore !== "deferred" &&
|
|
140
|
-
opts.nonceStore !== undefined &&
|
|
141
|
-
opts.nonceStore !== null &&
|
|
142
|
-
typeof (opts.nonceStore as NonceStore).has === "function" &&
|
|
143
|
-
typeof (opts.nonceStore as NonceStore).add === "function";
|
|
144
|
-
if (opts.nonceStore !== "deferred" && !storeIsObject) {
|
|
145
|
-
return { valid: false, error: "nonce_handling_required" };
|
|
146
|
-
}
|
|
147
|
-
const usingNonceStore = storeIsObject;
|
|
148
|
-
let bodyNonce: string | undefined;
|
|
149
|
-
if (usingNonceStore) {
|
|
150
|
-
const candidate = opts.body.nonce;
|
|
151
|
-
if (
|
|
152
|
-
typeof candidate !== "string" ||
|
|
153
|
-
candidate.length < 16 ||
|
|
154
|
-
candidate.length > 256 ||
|
|
155
|
-
!/^[A-Za-z0-9_-]+$/.test(candidate)
|
|
156
|
-
) {
|
|
157
|
-
return { valid: false, error: "missing_nonce" };
|
|
158
|
-
}
|
|
159
|
-
bodyNonce = candidate;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const input: InkSignInput = {
|
|
163
|
-
method: opts.method,
|
|
164
|
-
path: opts.path,
|
|
165
|
-
recipientDid: opts.recipientAgentId,
|
|
166
|
-
body: opts.body,
|
|
167
|
-
timestamp,
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
// Post-verify nonce check+record. Runs only when the caller provided
|
|
171
|
-
// a NonceStore object. Checking after signature verification means a
|
|
172
|
-
// forged request never pollutes the nonce store, but a replay of an
|
|
173
|
-
// authentic signed request is still rejected within the freshness
|
|
174
|
-
// window. Backend errors fail closed.
|
|
175
|
-
async function recordNonce(): Promise<{ ok: true } | { ok: false; error: string }> {
|
|
176
|
-
if (!usingNonceStore) return { ok: true };
|
|
177
|
-
const store = opts.nonceStore as NonceStore;
|
|
178
|
-
const nonce = bodyNonce!;
|
|
179
|
-
let alreadySeen: boolean;
|
|
180
|
-
try {
|
|
181
|
-
alreadySeen = await Promise.resolve(store.has(nonce));
|
|
182
|
-
} catch {
|
|
183
|
-
return { ok: false, error: "nonce_store_error" };
|
|
184
|
-
}
|
|
185
|
-
if (alreadySeen) return { ok: false, error: "nonce_replay" };
|
|
186
|
-
try {
|
|
187
|
-
await Promise.resolve(store.add(nonce));
|
|
188
|
-
} catch {
|
|
189
|
-
return { ok: false, error: "nonce_store_error" };
|
|
190
|
-
}
|
|
191
|
-
return { ok: true };
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Try multi-key verification first (Phase 1 key rotation support).
|
|
195
|
-
// If the agent has published a key set, it is authoritative: we must NOT
|
|
196
|
-
// fall through to resolvePublicKey or the bootstrap derivation, because
|
|
197
|
-
// either could surface a key (retired/revoked or stale conn-stored) that
|
|
198
|
-
// was already rejected — or deliberately excluded — from the key set.
|
|
199
|
-
if (opts.resolveKeySet) {
|
|
200
|
-
const candidates = opts.resolveKeySet(senderDid);
|
|
201
|
-
// null/undefined = no key set published for this agent → fall through to bootstrap.
|
|
202
|
-
// Empty array = key set exists but no usable signing keys (e.g. all revoked) →
|
|
203
|
-
// authoritative reject. Falling through here would let an attacker with the
|
|
204
|
-
// bootstrap-derived key authenticate even after the agent has revoked it.
|
|
205
|
-
if (candidates !== null && candidates !== undefined) {
|
|
206
|
-
if (candidates.length === 0) {
|
|
207
|
-
return { valid: false, error: "signature_verification_failed" };
|
|
208
|
-
}
|
|
209
|
-
try {
|
|
210
|
-
const result = await verifyInkSignatureWithKeys(input, signature, candidates, hintKeyId);
|
|
211
|
-
if (result.verified) {
|
|
212
|
-
// Local-policy gate: a retired key still verifies per the spec's
|
|
213
|
-
// authority rule, but a caller that runs sensitive endpoints
|
|
214
|
-
// (writes, capability grants, etc.) can require an active key.
|
|
215
|
-
// This closes the "stolen retired key signs a fresh message"
|
|
216
|
-
// window: even though the spec allows retired keys for grace,
|
|
217
|
-
// callers don't have to.
|
|
218
|
-
if (opts.requireActiveKey && result.keyStatus === "retired") {
|
|
219
|
-
return { valid: false, error: "retired_key_for_live_auth" };
|
|
220
|
-
}
|
|
221
|
-
const noncePass = await recordNonce();
|
|
222
|
-
if (!noncePass.ok) return { valid: false, error: noncePass.error };
|
|
223
|
-
return {
|
|
224
|
-
valid: true,
|
|
225
|
-
senderAgentId: senderDid,
|
|
226
|
-
keyId: result.keyId,
|
|
227
|
-
keyStatus: result.keyStatus,
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
} catch { /* treated as verification failure below */ }
|
|
231
|
-
// Authoritative key set rejected the signature — do not fall back.
|
|
232
|
-
return { valid: false, error: "signature_verification_failed" };
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// No key set published yet — first-contact / bootstrap path.
|
|
237
|
-
let publicKey: Uint8Array | null = null;
|
|
238
|
-
if (opts.resolvePublicKey) {
|
|
239
|
-
publicKey = opts.resolvePublicKey(senderDid);
|
|
240
|
-
}
|
|
241
|
-
if (!publicKey) {
|
|
242
|
-
try {
|
|
243
|
-
publicKey = extractPublicKeyFromAgentId(senderDid);
|
|
244
|
-
} catch {
|
|
245
|
-
return { valid: false, error: "unresolvable_sender_key" };
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
if (!publicKey) {
|
|
249
|
-
return { valid: false, error: "unresolvable_sender_key" };
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
try {
|
|
253
|
-
const valid = await verifyInkSignature(input, signature, publicKey);
|
|
254
|
-
if (!valid) {
|
|
255
|
-
return { valid: false, error: "invalid_signature" };
|
|
256
|
-
}
|
|
257
|
-
const noncePass = await recordNonce();
|
|
258
|
-
if (!noncePass.ok) return { valid: false, error: noncePass.error };
|
|
259
|
-
return { valid: true, senderAgentId: senderDid };
|
|
260
|
-
} catch {
|
|
261
|
-
return { valid: false, error: "signature_verification_failed" };
|
|
262
|
-
}
|
|
263
|
-
}
|
package/src/models/agent-card.ts
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import { IntentTypeSchema } from "./intent.js";
|
|
3
|
-
import { InkReceiptDispositionSchema } from "./ink-audit.js";
|
|
4
|
-
import { ProfileSnapshotSchema } from "./profile.js";
|
|
5
|
-
import { KeyEntrySchema } from "./key-entry.js";
|
|
6
|
-
import { InkTransportSchema, AgentCardVisibilitySchema } from "./ink-handshake.js";
|
|
7
|
-
|
|
8
|
-
export const ThirdPartyAuditServiceSchema = z.object({
|
|
9
|
-
endpoint: z.string().url(),
|
|
10
|
-
did: z.string(),
|
|
11
|
-
publicKey: z.string(),
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
export const AgentCardSchema = z.object({
|
|
15
|
-
protocol: z.literal("ink/0.1"),
|
|
16
|
-
agentId: z.string(),
|
|
17
|
-
ownerDid: z.string().optional(),
|
|
18
|
-
ownerHandle: z.string().optional(),
|
|
19
|
-
atprotoRecordUri: z.string().optional(),
|
|
20
|
-
handle: z.string(),
|
|
21
|
-
displayName: z.string().max(200),
|
|
22
|
-
endpoint: z.string().url(),
|
|
23
|
-
publicKeyMultibase: z.string().startsWith("z"),
|
|
24
|
-
profileSnapshot: ProfileSnapshotSchema.optional(),
|
|
25
|
-
capabilities: z.object({
|
|
26
|
-
intentsAccepted: z.array(IntentTypeSchema),
|
|
27
|
-
intentsSent: z.array(IntentTypeSchema),
|
|
28
|
-
receipts: z.object({
|
|
29
|
-
send: z.boolean(),
|
|
30
|
-
dispositions: z.array(InkReceiptDispositionSchema),
|
|
31
|
-
}).optional(),
|
|
32
|
-
auditExchange: z.boolean().optional(),
|
|
33
|
-
thirdPartyAudit: z.object({
|
|
34
|
-
services: z.array(ThirdPartyAuditServiceSchema),
|
|
35
|
-
submitPolicy: z.enum(["all", "high_value", "none"]),
|
|
36
|
-
}).optional(),
|
|
37
|
-
}),
|
|
38
|
-
availability: z.object({
|
|
39
|
-
timezone: z.string(),
|
|
40
|
-
meetingHours: z.string().optional(),
|
|
41
|
-
responseSla: z.string().optional(),
|
|
42
|
-
}),
|
|
43
|
-
keys: z.object({
|
|
44
|
-
signing: z.array(KeyEntrySchema),
|
|
45
|
-
encryption: z.array(KeyEntrySchema),
|
|
46
|
-
}).optional(),
|
|
47
|
-
currentSigningKeyId: z.string().optional(),
|
|
48
|
-
currentEncryptionKeyId: z.string().optional(),
|
|
49
|
-
keySetVersion: z.number().int().positive().optional(),
|
|
50
|
-
// Containment extension (Phase 1)
|
|
51
|
-
visibility: AgentCardVisibilitySchema.optional(),
|
|
52
|
-
governance: z.object({
|
|
53
|
-
maxAcceptedDelegationDepth: z.number().int().positive().optional(),
|
|
54
|
-
supportedTransports: z.array(InkTransportSchema).optional(),
|
|
55
|
-
supportsCapabilityGatedDiscovery: z.boolean().optional(),
|
|
56
|
-
handshakeBudget: z.object({
|
|
57
|
-
maxChallengesPerCorrelation: z.number().int().positive().optional(),
|
|
58
|
-
maxIntentsPerMinute: z.number().int().positive().optional(),
|
|
59
|
-
}).optional(),
|
|
60
|
-
}).optional(),
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
export type AgentCard = z.infer<typeof AgentCardSchema>;
|
package/src/models/ink-audit.ts
DELETED
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
|
|
3
|
-
// ── INK Audit Event Types (INK Auditability §2) ──
|
|
4
|
-
|
|
5
|
-
export const InkAuditEventTypeSchema = z.enum([
|
|
6
|
-
// Message lifecycle
|
|
7
|
-
"message.sent",
|
|
8
|
-
"message.received",
|
|
9
|
-
"message.queued",
|
|
10
|
-
"message.delivered",
|
|
11
|
-
"message.acted",
|
|
12
|
-
"message.rejected",
|
|
13
|
-
"message.expired",
|
|
14
|
-
"message.retracted",
|
|
15
|
-
// Receipt lifecycle
|
|
16
|
-
"receipt.sent",
|
|
17
|
-
"receipt.received",
|
|
18
|
-
// Delegation
|
|
19
|
-
"delegation.granted",
|
|
20
|
-
"delegation.used",
|
|
21
|
-
"delegation.revoked",
|
|
22
|
-
"delegation.expired",
|
|
23
|
-
// Connection
|
|
24
|
-
"connection.requested",
|
|
25
|
-
"connection.accepted",
|
|
26
|
-
"connection.declined",
|
|
27
|
-
// Verification
|
|
28
|
-
"signature.verified",
|
|
29
|
-
"signature.verified_retired",
|
|
30
|
-
"signature.failed",
|
|
31
|
-
"signature.revoked_rejected",
|
|
32
|
-
"replay.detected",
|
|
33
|
-
// Key lifecycle
|
|
34
|
-
"key.rotated",
|
|
35
|
-
"key.revoked",
|
|
36
|
-
// Introduction lifecycle
|
|
37
|
-
"introduction.requested",
|
|
38
|
-
"introduction.approved",
|
|
39
|
-
"introduction.declined",
|
|
40
|
-
"introduction.forwarded",
|
|
41
|
-
"introduction.completed",
|
|
42
|
-
"introduction.expired",
|
|
43
|
-
"introduction.receipt_sent",
|
|
44
|
-
"introduction.receipt_received",
|
|
45
|
-
// Enclave lifecycle
|
|
46
|
-
"enclave.requested",
|
|
47
|
-
"enclave.authorized",
|
|
48
|
-
"enclave.opened",
|
|
49
|
-
"enclave.operation_submitted",
|
|
50
|
-
"enclave.resolved",
|
|
51
|
-
"enclave.expired",
|
|
52
|
-
"enclave.aborted",
|
|
53
|
-
"enclave.receipt_sent",
|
|
54
|
-
"enclave.receipt_received",
|
|
55
|
-
// Containment (Phase 1)
|
|
56
|
-
"transport_scope_violation",
|
|
57
|
-
"handshake_rate_limited",
|
|
58
|
-
"handshake_budget_exhausted",
|
|
59
|
-
"discovery_query_received",
|
|
60
|
-
"discovery_query_granted",
|
|
61
|
-
"discovery_query_denied",
|
|
62
|
-
]);
|
|
63
|
-
|
|
64
|
-
export type InkAuditEventType = z.infer<typeof InkAuditEventTypeSchema>;
|
|
65
|
-
|
|
66
|
-
// ── INK Audit Event (hash-chained, signed) ──
|
|
67
|
-
|
|
68
|
-
export const InkAuditEventSchema = z.object({
|
|
69
|
-
id: z.string().min(1),
|
|
70
|
-
version: z.literal("ink-audit/1"),
|
|
71
|
-
agentId: z.string().min(1),
|
|
72
|
-
agentSignature: z.string().min(1),
|
|
73
|
-
sequence: z.number().int().positive(),
|
|
74
|
-
previousEventHash: z.string().regex(/^[0-9a-f]{64}$/).nullable(),
|
|
75
|
-
eventType: InkAuditEventTypeSchema,
|
|
76
|
-
timestamp: z.string().datetime(),
|
|
77
|
-
messageId: z.string().min(1).optional(),
|
|
78
|
-
correlationId: z.string().min(1).optional(),
|
|
79
|
-
counterpartyId: z.string().min(1).optional(),
|
|
80
|
-
signingKeyId: z.string().min(1).optional(),
|
|
81
|
-
data: z.record(z.unknown()).optional(),
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
export type InkAuditEvent = z.infer<typeof InkAuditEventSchema>;
|
|
85
|
-
|
|
86
|
-
// ── Receipt (INK Auditability §1) ──
|
|
87
|
-
|
|
88
|
-
export const InkReceiptDispositionSchema = z.enum([
|
|
89
|
-
"received",
|
|
90
|
-
"delivered",
|
|
91
|
-
"acted",
|
|
92
|
-
"rejected",
|
|
93
|
-
"expired",
|
|
94
|
-
]);
|
|
95
|
-
|
|
96
|
-
export type InkReceiptDisposition = z.infer<typeof InkReceiptDispositionSchema>;
|
|
97
|
-
|
|
98
|
-
export const InkReceiptSchema = z.object({
|
|
99
|
-
protocol: z.literal("ink/0.1"),
|
|
100
|
-
type: z.literal("network.tulpa.receipt"),
|
|
101
|
-
from: z.string(),
|
|
102
|
-
to: z.string(),
|
|
103
|
-
messageId: z.string(),
|
|
104
|
-
disposition: InkReceiptDispositionSchema,
|
|
105
|
-
dispositionAt: z.string().datetime(),
|
|
106
|
-
note: z.string().max(500).optional(),
|
|
107
|
-
messageHash: z.string(),
|
|
108
|
-
nonce: z.string(),
|
|
109
|
-
timestamp: z.string().datetime(),
|
|
110
|
-
signature: z.string(),
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
export type InkReceipt = z.infer<typeof InkReceiptSchema>;
|
|
114
|
-
|
|
115
|
-
// ── Audit Query (INK Auditability §3) ──
|
|
116
|
-
|
|
117
|
-
export const InkAuditQuerySchema = z.object({
|
|
118
|
-
protocol: z.literal("ink/0.1"),
|
|
119
|
-
type: z.literal("network.tulpa.audit_query"),
|
|
120
|
-
from: z.string(),
|
|
121
|
-
to: z.string(),
|
|
122
|
-
messageId: z.string(),
|
|
123
|
-
nonce: z.string(),
|
|
124
|
-
timestamp: z.string().datetime(),
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
export type InkAuditQuery = z.infer<typeof InkAuditQuerySchema>;
|
|
128
|
-
|
|
129
|
-
// ── Audit Response (INK Auditability §3) ──
|
|
130
|
-
|
|
131
|
-
export const InkAuditResponseSchema = z.object({
|
|
132
|
-
protocol: z.literal("ink/0.1"),
|
|
133
|
-
type: z.literal("network.tulpa.audit_response"),
|
|
134
|
-
messageId: z.string(),
|
|
135
|
-
events: z.array(InkAuditEventSchema),
|
|
136
|
-
responseSignature: z.string(),
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
export type InkAuditResponse = z.infer<typeof InkAuditResponseSchema>;
|
|
140
|
-
|
|
141
|
-
// ── Third-Party Audit Submit (INK Auditability §7.2) ──
|
|
142
|
-
|
|
143
|
-
export const InkAuditSubmitSchema = z.object({
|
|
144
|
-
protocol: z.literal("ink/0.1"),
|
|
145
|
-
type: z.literal("network.tulpa.audit_submit"),
|
|
146
|
-
from: z.string().max(256),
|
|
147
|
-
to: z.string().max(256),
|
|
148
|
-
event: InkAuditEventSchema,
|
|
149
|
-
nonce: z.string().min(16).max(256),
|
|
150
|
-
timestamp: z.string().datetime(),
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
export type InkAuditSubmit = z.infer<typeof InkAuditSubmitSchema>;
|
|
154
|
-
|
|
155
|
-
// ── Third-Party Audit Inclusion Receipt (INK Auditability §7.2) ──
|
|
156
|
-
|
|
157
|
-
export const InkAuditInclusionSchema = z.object({
|
|
158
|
-
protocol: z.literal("ink/0.1"),
|
|
159
|
-
type: z.literal("network.tulpa.audit_inclusion"),
|
|
160
|
-
eventId: z.string(),
|
|
161
|
-
treeSize: z.number().int().positive(),
|
|
162
|
-
leafIndex: z.number().int().min(0),
|
|
163
|
-
rootHash: z.string(),
|
|
164
|
-
timestamp: z.string().datetime(),
|
|
165
|
-
serviceSignature: z.string(),
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
export type InkAuditInclusion = z.infer<typeof InkAuditInclusionSchema>;
|
|
169
|
-
|
|
170
|
-
// ── Introduction Receipt (INK Introduction Receipts Extension §4) ──
|
|
171
|
-
|
|
172
|
-
export const InkIntroductionReceiptStatusSchema = z.enum([
|
|
173
|
-
"approved",
|
|
174
|
-
"declined",
|
|
175
|
-
"forwarded",
|
|
176
|
-
"completed",
|
|
177
|
-
"expired",
|
|
178
|
-
]);
|
|
179
|
-
|
|
180
|
-
export type InkIntroductionReceiptStatus = z.infer<typeof InkIntroductionReceiptStatusSchema>;
|
|
181
|
-
|
|
182
|
-
export const InkIntroductionReceiptSchema = z.object({
|
|
183
|
-
protocol: z.literal("ink/0.1"),
|
|
184
|
-
type: z.literal("network.tulpa.introduction_receipt"),
|
|
185
|
-
id: z.string(),
|
|
186
|
-
correlationId: z.string(),
|
|
187
|
-
from: z.string(),
|
|
188
|
-
to: z.string(),
|
|
189
|
-
requesterDid: z.string(),
|
|
190
|
-
introducerDid: z.string(),
|
|
191
|
-
beneficiaryDid: z.string(),
|
|
192
|
-
targetDid: z.string(),
|
|
193
|
-
status: InkIntroductionReceiptStatusSchema,
|
|
194
|
-
purpose: z.string().min(1).max(500),
|
|
195
|
-
nonce: z.string(),
|
|
196
|
-
timestamp: z.string().datetime(),
|
|
197
|
-
relatedIntentId: z.string().optional(),
|
|
198
|
-
relatedResolutionId: z.string().optional(),
|
|
199
|
-
note: z.string().max(500).optional(),
|
|
200
|
-
contextHash: z.string().optional(),
|
|
201
|
-
authorizationChainRef: z.string().optional(),
|
|
202
|
-
expiresAt: z.string().datetime().optional(),
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
export type InkIntroductionReceipt = z.infer<typeof InkIntroductionReceiptSchema>;
|