@adastracomputing/ink 0.1.0-alpha.5 → 0.1.1

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 CHANGED
@@ -8,6 +8,24 @@ here. Pre-1.0 releases follow `0.Y.Z` semantics, see
8
8
 
9
9
  No unreleased changes.
10
10
 
11
+ ## 0.1.1, discovery rename and normative SSRF floor
12
+
13
+ This release is **wire-compatible with v0.1.0**; the wire version stays `ink/0.1`. Every change below is additive and accepts the prior shape for the duration of the v0.1.x line — implementations that emit and consume v0.1.0 cards / service entries continue to interoperate. One observable library behavior changes: the runtime emit value of the redacted Agent Card `type` field flips from `"tulpa.agent.card"` to `"ink.agent.card"` (see below). Consumers that pinned to the literal must accept either string; the TypeScript union has been widened accordingly.
14
+
15
+ **Service entry rename.** The DID Document service entry for INK endpoints is now `type: "INKAgentEndpoint"`. The legacy `"TulpaAgentEndpoint"` is still accepted by consumers during v0.1.x; new publishers SHOULD emit `INKAgentEndpoint`. When both are present, `INKAgentEndpoint` takes precedence. Removed at the next wire-version bump.
16
+
17
+ **Service entry now points at the Agent Card URL.** `serviceEndpoint` is the URL of the Agent Card, not the inbound message endpoint. Inbound URL stays on the Card itself in the `endpoint` field. Per the spec update, `inboxEndpoint` is also accepted as a synonym for `endpoint`.
18
+
19
+ **Normative SSRF floor for discovery fetches.** The Discovery spec now mandates HTTPS-only, refusal of private/link-local/loopback/cloud-metadata hosts, bounded redirects with host re-checking on each hop, response size and time caps, and honoring `Cache-Control`. Detailed hardening stays implementer responsibility but the normative floor lives in the spec.
20
+
21
+ **Normative DID-binding of fetched cards.** Consumers MUST bind the card's `ownerDid` (when present) to the DID under resolution, and bind the card's `agentId` to the agent identifier being sent to. A mismatch on either field is a hard reject. This closes the substitution attack where a host that legitimately publishes one DID claims to publish another.
22
+
23
+ **Cache and refresh rules lifted into normative Discovery.** Refresh on signature/keyId miss, on observed `keySetVersion` increase, never fall back to bootstrap keys after a valid card has been observed.
24
+
25
+ **Redacted Agent Card type renamed.** The `type` field on a redacted Agent Card is now `"ink.agent.card"`. Consumers MUST also accept the legacy `"tulpa.agent.card"` during v0.1.x. The `network.tulpa.*` wire-message types are explicitly **frozen** for v0.x per the compatibility policy; rewriting them would break every deployed router.
26
+
27
+ **Wire version is `ink/0.1`.** All v0.1.x package releases emit `protocol: "ink/0.1"` on the wire. The next wire-version bump is `ink/0.2`.
28
+
11
29
  ## 0.1.0-alpha.5, ship compiled JS
12
30
 
13
31
  Fixes a publish-time regression in `0.1.0-alpha.3` (and the unreleased
package/README.md CHANGED
@@ -29,7 +29,7 @@ INK assumes [AT Protocol](https://atproto.com) for identity by default but isn't
29
29
  npm install @adastracomputing/ink
30
30
  ```
31
31
 
32
- The package ships TypeScript sources directly. Consumers need a TS-aware toolchain (tsx, ts-node, esbuild, Vite, etc.).
32
+ The package ships compiled ESM with bundled type definitions (`dist/index.js` + `dist/index.d.ts`). Any project with a standard JS toolchain can import it directly — no TypeScript build step on the consumer side. The build runs automatically via `prepack` before publish.
33
33
 
34
34
  ```ts
35
35
  import {
@@ -69,6 +69,12 @@ For consumers of bilateral audit-exchange responses (`network.tulpa.audit_respon
69
69
 
70
70
  For consumers of witness audit-query responses (`network.tulpa.audit_query_response`, Auditability §7.3, added in `0.1.0-alpha.3`), call `verifyAuditQueryResponse({response, witnessPublicKey, expectedRequester, expectedMessageId, verifyEventSignature, expectedServiceDid?, laterCheckpoint?})`. The `verifyEventSignature` callback is REQUIRED: it resolves the submitting agent's Ed25519 keys (typically via Agent Card §2) and validates each event's `agentSignature`. Without it, the verifier refuses to return valid, because Merkle inclusion alone does not prove a real agent produced the event (§7.5). The verifier enforces envelope shape, the `requester` binding (prevents cross-requester replay), events/proofs strict one-to-one alignment, the §7.4 per-event scope rule, walks every Merkle proof via `computeAuditMerkleLeafHash` up to the response's `rootHash`, runs `verifyEventSignature` on every event and supports an optional later-checkpoint cross-check. The lower-level `verifyAuditQueryResponseSignature` is signature-only and is not sufficient to accept a witness response on its own.
71
71
 
72
+ ## Agent-assisted implementation
73
+
74
+ If you are asking an AI coding agent to add INK support to an existing service, the canonical packet for that workflow is the [Agent-assisted implementation](https://ink.tulpa.network/guides/agent-assisted-implementation/) guide. It contains the curated implementer prompt, a mandatory traceability matrix, the conformance checklist, and a human-review checklist. The guide is updated as the protocol evolves; treat it as the live source rather than copying its contents into your repo.
75
+
76
+ Adopters who want the second open implementation to cross-check against can use the [`examples/foreign-sender-receiver/`](examples/foreign-sender-receiver/) TypeScript reference and the [`examples/interop-cli/`](examples/interop-cli/) Python from-scratch sender.
77
+
72
78
  ## Tests
73
79
 
74
80
  ```bash
@@ -78,7 +84,7 @@ npm run lint # eslint
78
84
  npm run check:surface # public-surface drift check
79
85
  ```
80
86
 
81
- For Nix users: `nix develop` gives a pinned Node 22 + git + gitleaks shell. `nix build` produces the publishable npm tarball under `result/`. `nix run github:Ad-Astra-Computing/ink -- verify-inclusion --file receipt.json --witness https://witness.example.com` runs the CLI without installing anything globally.
87
+ For Nix users: `nix develop` gives a pinned Node 24 + git + gitleaks shell. `nix build` produces the publishable npm tarball under `result/`. `nix run github:Ad-Astra-Computing/ink -- verify-inclusion --file receipt.json --witness https://witness.example.com` runs the CLI without installing anything globally.
82
88
 
83
89
  ## Layout
84
90
 
@@ -95,7 +101,7 @@ test-vectors/ JSON interop vectors
95
101
  test/ vitest unit + integration tests
96
102
  ```
97
103
 
98
- The library runs on any runtime providing standard Web Crypto and `fetch`: Node 22+, Deno, Bun, Cloudflare Workers, browsers. The timestamp freshness window is enforced inside `verifyInkAuth`; nonce single-use is enforced when a `NonceStore` is passed (otherwise `checkReplay` must be called separately). Nonce backing storage and its TTL policy are the integrator's choice.
104
+ The library runs on any runtime providing standard Web Crypto and `fetch`: Node 24+, Deno, Bun, Cloudflare Workers, browsers. The timestamp freshness window is enforced inside `verifyInkAuth`; nonce single-use is enforced when a `NonceStore` is passed (otherwise `checkReplay` must be called separately). Nonce backing storage and its TTL policy are the integrator's choice.
99
105
 
100
106
  ## What's stable in v0.1
101
107
 
@@ -121,6 +127,12 @@ You will see `network.tulpa.*` on the wire (e.g. `network.tulpa.intent`) and `in
121
127
 
122
128
  INK is developed by [Ad Astra Computing](https://adastracomputing.com) as the underlying protocol for [Tulpa](https://tulpa.network). The spec and the library in this repo are deliberately free of Tulpa product code so other agent platforms can adopt INK without inheriting Tulpa's surface area. Tulpa's product integration (message orchestration, marketplace, user-facing APIs) lives in a separate, closed-source codebase.
123
129
 
130
+ ## Interoperability
131
+
132
+ INK is a wire protocol. Any compatible service that publishes a DID and exposes an `/ink/v1/...` endpoint can accept signed envelopes from agents that live on other platforms — cross-platform interop is a primary design goal.
133
+
134
+ [`tulpa.network`](https://tulpa.network) is one current example of an accepting endpoint. Its receive side resolves inbound senders against published Agent Cards and applies operator-level and per-user acceptance policies; see [docs.tulpa.network/guide/foreign-agents](https://docs.tulpa.network/guide/foreign-agents/) for how a Tulpa user opts in. The protocol is intended to support other accepting endpoints.
135
+
124
136
  ## Security
125
137
 
126
138
  See [`SECURITY.md`](SECURITY.md) for the disclosure path. The threat model is in [`docs/threat-model.md`](docs/threat-model.md). **Do not open a public issue for security problems.**
package/dist/index.d.ts CHANGED
@@ -8,5 +8,6 @@ export { verifyInclusionReceipt, verifyAuditQueryResponse, type InclusionReceipt
8
8
  export { HandshakeBudgetTracker } from "./ink/handshake-budget.js";
9
9
  export type { InkSignInput } from "./crypto/ink.js";
10
10
  export type { CandidateKey } from "./models/key-entry.js";
11
+ export { resolveAgentInbox } from "./models/agent-card.js";
11
12
  export type { AgentCard } from "./models/agent-card.js";
12
13
  export type { BudgetCheckResult, HandshakeBudgetConfig, } from "./ink/handshake-budget.js";
package/dist/index.js CHANGED
@@ -13,3 +13,4 @@ export { verifyInkAuth } from "./middleware/ink-auth.js";
13
13
  export { verifyInclusionReceipt, verifyAuditQueryResponse, } from "./audit/inclusion-receipt.js";
14
14
  // Optional containment / governance primitives
15
15
  export { HandshakeBudgetTracker } from "./ink/handshake-budget.js";
16
+ export { resolveAgentInbox } from "./models/agent-card.js";
@@ -10,8 +10,17 @@ import { z } from "zod";
10
10
  import { type AgentCard } from "../models/agent-card.js";
11
11
  import type { AgentCardVisibility } from "../models/ink-handshake.js";
12
12
  export { AgentCardVisibilitySchema, type AgentCardVisibility } from "../models/ink-handshake.js";
13
+ /**
14
+ * The redacted Agent Card returned by visibility-aware GETs.
15
+ *
16
+ * The `type` field's canonical value is `"ink.agent.card"` from
17
+ * INK v0.1.1 onward. The legacy value `"tulpa.agent.card"` MUST
18
+ * also be accepted by consumers during the v0.1.x window so old
19
+ * publishers (and older snapshots of this library) keep parsing.
20
+ * The legacy synonym is removed at the next wire-version bump.
21
+ */
13
22
  export interface RedactedAgentCard {
14
- type: "tulpa.agent.card";
23
+ type: "ink.agent.card" | "tulpa.agent.card";
15
24
  version: "1.0";
16
25
  agentId: string;
17
26
  displayName?: string;
@@ -77,6 +86,7 @@ export declare const AgentCardResponseSchema: z.ZodObject<{
77
86
  handle: z.ZodString;
78
87
  displayName: z.ZodString;
79
88
  endpoint: z.ZodString;
89
+ inboxEndpoint: z.ZodOptional<z.ZodString>;
80
90
  publicKeyMultibase: z.ZodString;
81
91
  profileSnapshot: z.ZodOptional<z.ZodObject<{
82
92
  headline: z.ZodString;
@@ -25,7 +25,10 @@ export function buildRedactedCard(card) {
25
25
  // the field is anything other than the known enum members.
26
26
  const visibility = card.visibility === "network_only" ? "network_only" : "capability_gated";
27
27
  const out = {
28
- type: "tulpa.agent.card",
28
+ // INK v0.1.1+: emit the protocol-generic name. The interface
29
+ // union accepts both so consumers that hold pre-v0.1.1 cached
30
+ // values keep parsing.
31
+ type: "ink.agent.card",
29
32
  version: "1.0",
30
33
  agentId: card.agentId,
31
34
  displayName: card.displayName,
@@ -13,6 +13,7 @@ export declare const AgentCardSchema: z.ZodObject<{
13
13
  handle: z.ZodString;
14
14
  displayName: z.ZodString;
15
15
  endpoint: z.ZodString;
16
+ inboxEndpoint: z.ZodOptional<z.ZodString>;
16
17
  publicKeyMultibase: z.ZodString;
17
18
  profileSnapshot: z.ZodOptional<z.ZodObject<{
18
19
  headline: z.ZodString;
@@ -152,3 +153,18 @@ export declare const AgentCardSchema: z.ZodObject<{
152
153
  }, z.core.$strip>>;
153
154
  }, z.core.$strip>;
154
155
  export type AgentCard = z.infer<typeof AgentCardSchema>;
156
+ /**
157
+ * Return the inbound message URL for an Agent Card.
158
+ *
159
+ * Under v0.1.1, `endpoint` is still required on every parsed
160
+ * `AgentCard`, so this helper effectively returns `card.endpoint`
161
+ * today. The `?? card.inboxEndpoint` fallback is in place for the
162
+ * future v0.x revision where `inboxEndpoint` will become primary
163
+ * (with `endpoint` accepted as the legacy alias). Consumers SHOULD
164
+ * use this helper rather than reading `.endpoint` directly so the
165
+ * eventual swap is a one-import change.
166
+ *
167
+ * `inboxEndpoint` (when present alongside `endpoint`) MUST equal
168
+ * `endpoint`; that invariant is enforced by `AgentCardSchema`.
169
+ */
170
+ export declare function resolveAgentInbox(card: AgentCard): string;
@@ -17,8 +17,21 @@ export const AgentCardSchema = z.object({
17
17
  atprotoRecordUri: z.string().optional(),
18
18
  handle: z.string(),
19
19
  displayName: z.string().max(200),
20
+ /**
21
+ * Inbound message endpoint URL. Required.
22
+ *
23
+ * `inboxEndpoint` is accepted as an optional forward-compat hint
24
+ * from v0.1.1 onward; when present it MUST equal `endpoint`. The
25
+ * long-term name is settled at the next wire-version bump, so
26
+ * publishers SHOULD continue to emit `endpoint` for v0.1.x and
27
+ * MAY emit `inboxEndpoint` alongside it. The runtime helper
28
+ * `resolveAgentInbox(card)` returns whichever value is present.
29
+ */
20
30
  endpoint: z.string().url(),
31
+ inboxEndpoint: z.string().url().optional(),
21
32
  publicKeyMultibase: z.string().startsWith("z"),
33
+ // (other fields below; the `inboxEndpoint === endpoint` invariant
34
+ // is enforced by the .superRefine() at the bottom of this schema.)
22
35
  profileSnapshot: ProfileSnapshotSchema.optional(),
23
36
  capabilities: z.object({
24
37
  intentsAccepted: z.array(IntentTypeSchema),
@@ -56,4 +69,39 @@ export const AgentCardSchema = z.object({
56
69
  maxIntentsPerMinute: z.number().int().positive().optional(),
57
70
  }).optional(),
58
71
  }).optional(),
72
+ }).superRefine((card, ctx) => {
73
+ // v0.1.1: when both endpoint and inboxEndpoint are present they
74
+ // MUST refer to the same URL. The spec rationale is that the alias
75
+ // exists for forward compat, not as a way to publish two distinct
76
+ // inbound URLs under one card — a card with mismatched endpoints
77
+ // is ambiguous about which one to deliver to.
78
+ if (card.inboxEndpoint && card.endpoint && card.inboxEndpoint !== card.endpoint) {
79
+ ctx.addIssue({
80
+ code: z.ZodIssueCode.custom,
81
+ path: ["inboxEndpoint"],
82
+ message: "inboxEndpoint MUST equal endpoint when both are present (v0.1.1 spec).",
83
+ });
84
+ }
59
85
  });
86
+ /**
87
+ * Return the inbound message URL for an Agent Card.
88
+ *
89
+ * Under v0.1.1, `endpoint` is still required on every parsed
90
+ * `AgentCard`, so this helper effectively returns `card.endpoint`
91
+ * today. The `?? card.inboxEndpoint` fallback is in place for the
92
+ * future v0.x revision where `inboxEndpoint` will become primary
93
+ * (with `endpoint` accepted as the legacy alias). Consumers SHOULD
94
+ * use this helper rather than reading `.endpoint` directly so the
95
+ * eventual swap is a one-import change.
96
+ *
97
+ * `inboxEndpoint` (when present alongside `endpoint`) MUST equal
98
+ * `endpoint`; that invariant is enforced by `AgentCardSchema`.
99
+ */
100
+ export function resolveAgentInbox(card) {
101
+ // `card.endpoint` is required by the v0.1.x schema so the first
102
+ // branch always succeeds today. The `??` chain is in place for the
103
+ // future revision where `endpoint` becomes optional and
104
+ // `inboxEndpoint` becomes the primary field; consumers using this
105
+ // helper get the swap for free.
106
+ return card.endpoint ?? card.inboxEndpoint;
107
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adastracomputing/ink",
3
- "version": "0.1.0-alpha.5",
3
+ "version": "0.1.1",
4
4
  "description": "Library and specification for the INK (Inter-agent Networking Kernel) protocol",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "author": "Ad Astra Computing Inc.",
@@ -425,7 +425,7 @@ A third-party audit service is a **INK service role**, not a standard INK agent.
425
425
  | Concern | INK Agent | Audit Service |
426
426
  |---------|-----------|--------------|
427
427
  | Identity | DID bound to a human via `agentLink` | `did:web` or `did:key`, self-sovereign, no human owner |
428
- | Discovery | `TulpaAgentEndpoint` in DID document | Advertised in subscribing agents' Agent Card `capabilities.thirdPartyAudit.services` |
428
+ | Discovery | `INKAgentEndpoint` in DID document (legacy `TulpaAgentEndpoint` also accepted during v0.1.x) | Advertised in subscribing agents' Agent Card `capabilities.thirdPartyAudit.services` |
429
429
  | Auth (inbound) | INK auth §3.3, verifies sender's `agentLink` delegation | INK auth §3.3, verifies sender's `agentLink` delegation (same as any INK endpoint) |
430
430
  | Auth (outbound) | Signs with `agentLink.signingKeyMultibase` | Signs with its own Ed25519 key (published in subscribing agents' Agent Card) |
431
431
  | Delegation proof | Required, must trace authority back to a human DID | Not applicable, the service is independently trusted by each subscribing agent |
@@ -436,7 +436,7 @@ A third-party audit service is a **INK service role**, not a standard INK agent.
436
436
 
437
437
  2. **Inbound auth is standard INK.** When agents submit events TO the service, the service verifies the submitter's identity via standard INK auth (§3.3), resolve the sender's DID, find their `agentLink`, verify the signature. The service is a normal INK recipient in this direction.
438
438
 
439
- 3. **Service DID resolution.** The service's `did:web` (or `did:key`) is resolved normally for TLS binding and key discovery, but the service does NOT need a `TulpaAgentEndpoint` service entry in its DID document. Its endpoint is provided directly in the subscribing agent's Agent Card configuration.
439
+ 3. **Service DID resolution.** The service's `did:web` (or `did:key`) is resolved normally for TLS binding and key discovery, but the service does NOT need an `INKAgentEndpoint` service entry in its DID document. Its endpoint is provided directly in the subscribing agent's Agent Card configuration.
440
440
 
441
441
  4. **No inbox, no intents.** The audit service does not accept INK intents, challenges or resolutions. It exposes only the audit-specific endpoints (`/ink/v1/audit/submit`, `/ink/v1/audit/query`).
442
442
 
@@ -182,7 +182,7 @@ When visibility is `capability_gated`, unauthenticated requests to the Agent Car
182
182
 
183
183
  ```typescript
184
184
  interface RedactedAgentCard {
185
- type: "tulpa.agent.card";
185
+ type: "ink.agent.card"; // legacy "tulpa.agent.card" MUST also be accepted during v0.1.x
186
186
  version: "1.0";
187
187
  agentId: string;
188
188
  displayName?: string;