@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 +18 -0
- package/README.md +15 -3
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/ink/discovery-gating.d.ts +11 -1
- package/dist/ink/discovery-gating.js +4 -1
- package/dist/models/agent-card.d.ts +16 -0
- package/dist/models/agent-card.js +48 -0
- package/package.json +1 -1
- package/specs/ink-auditability.md +2 -2
- package/specs/ink-containment-phase1-implementation-spec.md +1 -1
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
@@ -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 | `
|
|
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
|
|
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;
|