@adastracomputing/ink 0.1.0-alpha.0

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 (48) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/CODE_OF_CONDUCT.md +42 -0
  3. package/LICENSE-APACHE +201 -0
  4. package/LICENSE-MIT +21 -0
  5. package/README.md +133 -0
  6. package/SECURITY.md +57 -0
  7. package/docs/key-rotation-rule.md +108 -0
  8. package/docs/logo.svg +8 -0
  9. package/docs/maturity.md +81 -0
  10. package/docs/threat-model.md +150 -0
  11. package/package.json +72 -0
  12. package/specs/ink-agent-containment-and-governance-extension-spec.md +508 -0
  13. package/specs/ink-auditability.md +652 -0
  14. package/specs/ink-authorization-chain.md +242 -0
  15. package/specs/ink-compatibility-policy.md +263 -0
  16. package/specs/ink-compliance-checklist.md +309 -0
  17. package/specs/ink-containment-phase1-implementation-spec.md +593 -0
  18. package/specs/ink-introduction-receipts-extension.md +501 -0
  19. package/specs/ink-key-rotation-spec.md +535 -0
  20. package/src/crypto/ink.ts +902 -0
  21. package/src/crypto/keys.ts +211 -0
  22. package/src/crypto/multi-key-verify.ts +170 -0
  23. package/src/crypto/sign.ts +155 -0
  24. package/src/crypto/verify.ts +1 -0
  25. package/src/discovery/agent-card.ts +508 -0
  26. package/src/index.ts +59 -0
  27. package/src/ink/checkpoint.ts +75 -0
  28. package/src/ink/discovery-gating.ts +147 -0
  29. package/src/ink/handshake-budget.ts +413 -0
  30. package/src/ink/receipts.ts +114 -0
  31. package/src/ink/transport-auth.ts +96 -0
  32. package/src/middleware/ink-auth.ts +263 -0
  33. package/src/models/agent-card.ts +63 -0
  34. package/src/models/ink-audit.ts +205 -0
  35. package/src/models/ink-handshake.ts +123 -0
  36. package/src/models/intent.ts +201 -0
  37. package/src/models/key-entry.ts +52 -0
  38. package/src/models/profile.ts +31 -0
  39. package/test-vectors/README.md +129 -0
  40. package/test-vectors/encryption.json +90 -0
  41. package/test-vectors/handshake.json +482 -0
  42. package/test-vectors/jcs.json +30 -0
  43. package/test-vectors/key-rotation.json +101 -0
  44. package/test-vectors/keys.json +32 -0
  45. package/test-vectors/receipts-and-audit.json +142 -0
  46. package/test-vectors/replay.json +88 -0
  47. package/test-vectors/signing.json +61 -0
  48. package/test-vectors/witness.json +394 -0
@@ -0,0 +1,242 @@
1
+ # INK Authorization Chain (draft extension)
2
+
3
+ **Status:** Draft
4
+ **Authors:** Ad Astra Computing
5
+ **Date:** 2026-03-19
6
+
7
+ ## Problem
8
+
9
+ The current INK v0.1 model supports single-hop delegation: a Tulpa issues a delegation token to an extension, and the extension can act on the Tulpa's behalf. The message envelope carries a `provenance` field that claims origin (`human`, `agent_approved`, `agent_autonomous`), but:
10
+
11
+ 1. **No cryptographic proof of delegation**, the `provenance` field is a self-asserted claim. A malicious extension can forge `origin: "human"` on autonomous messages.
12
+ 2. **No multi-hop chains**, if Extension A delegates to Service B which calls Service C, the recipient sees only the final hop. There's no way to trace the full authorization path.
13
+ 3. **No recipient-verifiable authorization**, the recipient must trust the sender's provenance claim. The delegation token is between Tulpa↔Extension only; the recipient never sees it.
14
+
15
+ ## Design
16
+
17
+ ### 1. Delegation Proof (replaces self-asserted provenance)
18
+
19
+ Add a `delegationProof` field to the message envelope that cryptographically binds the delegation chain to the message.
20
+
21
+ ```typescript
22
+ DelegationProofSchema = z.object({
23
+ // The delegation token (existing format: payload.signature)
24
+ delegationToken: z.string(),
25
+
26
+ // The issuing Tulpa's public key (so recipient can verify the token signature)
27
+ issuerPublicKey: z.string(),
28
+
29
+ // Extension's signature over the message ID, binding this proof to this message
30
+ // signatureBase = messageId + "\n" + intent + "\n" + JCS(payload)
31
+ extensionSignature: z.string(),
32
+
33
+ // Extension's public key (from the installation record / manifest)
34
+ extensionPublicKey: z.string(),
35
+
36
+ // Origin assertion, now signed by the extension rather than self-asserted
37
+ origin: ProvenanceOriginSchema,
38
+ });
39
+ ```
40
+
41
+ **Verification by recipient:**
42
+
43
+ 1. Decode the delegation token, verify its Ed25519 signature against `issuerPublicKey`
44
+ 2. Check that `issuerPublicKey` matches the sender's known public key (from Agent Card or connection store)
45
+ 3. Verify `extensionSignature` against `extensionPublicKey` to confirm the extension signed this specific message
46
+ 4. Check that `extensionPublicKey` matches the `publicKeyMultibase` in the delegation token payload
47
+ 5. Verify the delegation token hasn't expired and permissions/autonomy tier are sufficient for the intent
48
+
49
+ **Result:** the recipient now has cryptographic proof that:
50
+ - The Tulpa owner authorized this extension (delegation token)
51
+ - The extension actually produced this message (extension signature)
52
+ - The origin claim is bound to the extension's key (not forgeable)
53
+
54
+ ### 2. Multi-Hop Delegation Chains
55
+
56
+ For cases where Extension A calls Service B which generates the message:
57
+
58
+ ```typescript
59
+ DelegationChainSchema = z.object({
60
+ // Ordered list of delegation hops, from Tulpa → final actor
61
+ hops: z.array(DelegationHopSchema).min(1).max(5),
62
+ });
63
+
64
+ DelegationHopSchema = z.object({
65
+ // Who delegated
66
+ delegator: z.string(), // did:key of delegator
67
+ delegatorPublicKey: z.string(),
68
+
69
+ // Who received delegation
70
+ delegate: z.string(), // did:key or extension ID
71
+ delegatePublicKey: z.string(),
72
+
73
+ // Scoped grant for this hop
74
+ permissions: z.array(PermissionSchema),
75
+ maxAutonomyTier: AutonomyTierSchema,
76
+ constraints: z.object({
77
+ intentTypes: z.array(IntentTypeSchema).optional(),
78
+ targetAgents: z.array(z.string()).optional(),
79
+ expiresAt: z.string().datetime(),
80
+ maxMessages: z.number().int().positive().optional(),
81
+ allowedTransports: z.array(z.enum([
82
+ "ink_http", "ink_ws", "extension_api",
83
+ "voice", "line_phone", "human_review_queue",
84
+ ])).optional(),
85
+ }),
86
+
87
+ // Delegator's signature over (delegate + permissions + constraints)
88
+ signature: z.string(),
89
+ });
90
+ ```
91
+
92
+ **Chain validation rules:**
93
+ - Each hop's permissions must be a **subset** of the previous hop's permissions (no privilege escalation)
94
+ - Each hop's `maxAutonomyTier` must be **≤** the previous hop's tier
95
+ - Each hop's `expiresAt` must be **≤** the previous hop's expiration
96
+ - Each hop's `allowedTransports` must be a **subset** of the previous hop's transports (transport attenuation, see INK Containment §7)
97
+ - Maximum chain depth: 5 hops (prevents unbounded delegation)
98
+ - The first hop must be signed by the Tulpa owner's key
99
+
100
+ **Transport scoping (INK Containment §7):**
101
+ - If `allowedTransports` is omitted on a v0.3+ token, it defaults to `["ink_http"]` (least privilege)
102
+ - Legacy tokens (no `tokenVersion` field) receive a permissive default of `["ink_http", "extension_api", "voice", "line_phone"]` during a 90-day migration window
103
+ - Messages arriving on a transport not in the token's `allowedTransports` are rejected with `transport_scope_violation`
104
+
105
+ ### 3. Envelope Changes
106
+
107
+ Update `MessageEnvelopeSchema` for v0.3:
108
+
109
+ ```typescript
110
+ MessageEnvelopeSchema = z.object({
111
+ protocol: z.literal("tulpa/0.2"), // bump when implemented
112
+ id: z.string(),
113
+ correlationId: z.string(),
114
+ createdAt: z.string(),
115
+ expiresAt: z.string().optional(),
116
+ from: z.string(),
117
+ to: z.string(),
118
+ intent: IntentTypeSchema,
119
+ payload: z.unknown(),
120
+ signature: z.string(),
121
+
122
+ // v0.3: replaces the old `provenance` field
123
+ delegationProof: DelegationProofSchema.optional(),
124
+ delegationChain: DelegationChainSchema.optional(),
125
+
126
+ // Deprecated, kept for backward compat during migration
127
+ provenance: MessageProvenanceSchema,
128
+ });
129
+ ```
130
+
131
+ **Backward compatibility:**
132
+ - Recipients that don't understand `delegationProof` fall back to `provenance` (existing behavior)
133
+ - Senders include both fields during the migration period
134
+ - After migration period, `provenance` becomes optional and `delegationProof` is required for extension-originated messages
135
+
136
+ ### 4. Agent Card Capability Advertisement
137
+
138
+ Extensions that support chain verification advertise it:
139
+
140
+ ```typescript
141
+ capabilities: {
142
+ intentsAccepted: [...],
143
+ intentsSent: [...],
144
+ delegationProof: true, // "I include delegation proofs on extension messages"
145
+ delegationChainDepth: 2, // "I support up to 2-hop chains"
146
+ }
147
+ ```
148
+
149
+ ### 5. Autonomy Tier Enforcement
150
+
151
+ The current `maxAutonomyTier` in the delegation token becomes **enforceable by the recipient**:
152
+
153
+ | Origin | Required Tier | Recipient Can Verify? |
154
+ |--------|--------------|----------------------|
155
+ | `human` | any | Yes, extension signature proves the extension saw user input |
156
+ | `agent_approved` | `social` or lower | Yes, delegation token tier checked |
157
+ | `agent_autonomous` | `transactional` only | Yes, delegation token tier checked |
158
+
159
+ If the delegation proof shows `origin: agent_autonomous` but the delegation token's `maxAutonomyTier` is `personal`, the recipient rejects the message.
160
+
161
+ ### 6. Revocation
162
+
163
+ Existing revocation (token hash stored in installation, checked on each request) works for the Tulpa↔Extension hop. For multi-hop chains:
164
+
165
+ - Each delegator maintains a revocation list (set of revoked delegate keys)
166
+ - The `delegationChain` includes a `revocationEndpoint` per hop
167
+ - Recipients can optionally check revocation endpoints (non-blocking, cached)
168
+ - Revocation is **eventually consistent**, a revoked chain may be accepted for up to the cache TTL (default: 5 min, matching the replay window)
169
+
170
+ ## Migration Path
171
+
172
+ 1. Ship `delegationProof` generation in the extension middleware
173
+ 2. Ship `delegationProof` verification in the integrator-side request pipeline (optional, don't reject messages without it yet)
174
+ 3. After adoption threshold (e.g. 90% of messages include proofs), make `delegationProof` required for extension-originated messages
175
+ 4. Deprecate `provenance` field
176
+
177
+ ## Prior Art and Research
178
+
179
+ This design was validated against established delegation protocols. Key influences:
180
+
181
+ ### UCAN (User Controlled Authorization Networks)
182
+ UCAN is the closest analog to INK's delegation model. Both use DID-based identities with Ed25519 signatures and chained capability tokens. Key lessons adopted:
183
+
184
+ - **Separate delegation from invocation.** UCAN 1.0 distinguishes "I was granted this capability" from "I am now exercising it." INK adopts this: the `delegationToken` is the grant; the `extensionSignature` over the specific message is the invocation. This prevents confused deputy attacks where a delegation token is accidentally replayed as an action.
185
+ - **Capability attenuation via partial ordering.** UCAN defines a partial order on capabilities where each hop can only narrow scope. INK uses the same approach: permission-subset checking and autonomy tier ≤ comparison. Given INK already has a flat permission enum (`PermissionSchema`), subset checking is straightforward.
186
+ - **CID-referenced vs. inlined proofs.** Early UCANs inlined parent tokens, causing exponential growth. INK avoids this by having each hop carry only its own signature and constraints, the chain is a flat array, not nested tokens.
187
+ - **No built-in revocation.** UCAN relies on short-lived tokens rather than revocation infrastructure. INK should adopt the same stance (see Revocation section update below).
188
+
189
+ ### ZCAP-LD (Authorization Capabilities for Linked Data)
190
+ W3C CCG's capability system uses additive caveats instead of UCAN's partial order. Each delegation can only ADD restrictions, never remove them. This is simpler to implement but less expressive. INK's permission-subset model is more aligned with UCAN's approach, which better fits the structured permission enum.
191
+
192
+ ### OAuth 2.0 Token Exchange (RFC 8693)
193
+ The `act` (actor) and `may_act` (pre-authorization) claims provide a clean representation of "who is acting on behalf of whom." INK adopts this pattern:
194
+ - The delegation proof's `origin` + `extensionPublicKey` serves as the `act` claim
195
+ - The delegation token itself serves as the `may_act` pre-authorization
196
+ - RFC 8693 supports nested `act` claims for multi-hop, which maps to INK's `hops` array
197
+
198
+ Key difference: RFC 8693 requires a centralized STS (Security Token Service). INK is decentralized, verification is self-contained using the chain signatures.
199
+
200
+ ### SPIFFE/SPIRE
201
+ SPIFFE separates identity from authorization entirely. Relevant pattern: **short-lived credentials with automatic rotation** avoid the revocation problem. SPIFFE SVIDs typically expire in hours, not days. INK should adopt this for delegation tokens (see updated recommendation below).
202
+
203
+ ### W3C DID + Verifiable Credentials
204
+ INK already uses `did:key` for agent identity, which is the right choice, self-certifying, no resolution infrastructure needed. The DID Document's `capabilityDelegation` verification relationship was designed to support exactly this kind of delegation. INK could optionally reference it for interop with the broader DID ecosystem.
205
+
206
+ ### ActivityPub / AT Protocol
207
+ Neither has meaningful delegation chains. ActivityPub bots are independent actors with no protocol-level delegation proof. AT Protocol has scoped app passwords and PDS-mediated service auth (single-hop). INK's delegation chain is a significant advancement over both.
208
+
209
+ ### Design Decisions Informed by Research
210
+
211
+ | Decision | Rationale | Prior Art |
212
+ |----------|-----------|-----------|
213
+ | Flat hop array (not nested tokens) | Avoids exponential size growth | UCAN 1.0 CID-referenced proofs |
214
+ | Permission subset checking | Fits INK's flat enum model | UCAN partial order |
215
+ | Separate delegation from invocation | Prevents confused deputy | UCAN 1.0 delegation/invocation split |
216
+ | `origin` bound to extension signature | Prevents origin forgery | RFC 8693 `act` claim binding |
217
+ | Short-lived tokens over revocation lists | Simpler, more reliable in decentralized systems | SPIFFE short-lived SVIDs |
218
+ | Max 5 hops | 2-3 is typical in practice; 5 allows headroom | No protocol sets a hard limit |
219
+
220
+ ## Updated Recommendations (Post-Research)
221
+
222
+ ### Shorter Token TTLs
223
+ Based on SPIFFE's approach and UCAN's lesson that revocation is unsolved in pure capability models:
224
+ - **Default TTL: 1-4 hours** (not 48 hours as currently allowed)
225
+ - Extensions should auto-renew tokens before expiry
226
+ - The `ttlHours` parameter in the token endpoint should cap at 24 hours for standard extensions
227
+ - 48-hour tokens should require elevated review status
228
+
229
+ ### Invocation Binding
230
+ Adopt UCAN 1.0's delegation/invocation separation explicitly:
231
+ - The `delegationToken` proves "I was granted this capability"
232
+ - The `extensionSignature` over `messageId + intent + JCS(payload)` proves "I am now exercising it on this specific message"
233
+ - Recipients MUST verify both, a valid delegation token alone is not sufficient
234
+
235
+ ## Security Considerations
236
+
237
+ - **Key rotation:** if a Tulpa rotates keys, outstanding delegation tokens become invalid. Extensions must re-request tokens after key rotation.
238
+ - **Stolen extension keys:** the delegation token is scoped (permissions, layers, expiry). A stolen key can only act within the granted scope until the token expires or is revoked. With the recommended 1-4 hour TTL, the exposure window is small.
239
+ - **Chain depth attacks:** capped at 5 hops. Each hop strictly narrows scope. No established protocol requires more than 3 in practice.
240
+ - **Clock skew:** delegation token expiry uses the same ±5 min window as INK replay protection.
241
+ - **Confused deputy:** the delegation/invocation separation (per UCAN 1.0) prevents a delegation token from being misused as a direct action. The extension must produce a fresh signature for each message.
242
+ - **Replay of delegation proofs:** the `extensionSignature` binds to a specific `messageId`, so replaying a delegation proof on a different message fails verification.
@@ -0,0 +1,263 @@
1
+ # INK Compatibility and Versioning Policy
2
+
3
+ ## Status
4
+ Draft, v1 stabilization
5
+
6
+ ## Purpose
7
+
8
+ This document defines how INK versions its wire protocol, when changes require version bumps, how optional capabilities are advertised, and how implementations handle unknown fields and message types.
9
+
10
+ This is the normative compatibility contract. Any change to the INK wire format MUST be evaluated against this policy before merging.
11
+
12
+ ---
13
+
14
+ ## 1. Protocol Version
15
+
16
+ INK uses a single protocol version string in every message envelope, receipt, audit event and handshake message.
17
+
18
+ Current version: `ink/0.1`
19
+
20
+ The version string appears in the `protocol` field of every top-level INK object and in the first line of every signature base.
21
+
22
+ ### 1.1 Version Format
23
+
24
+ `ink/<major>.<minor>`
25
+
26
+ - **Major**: incremented for incompatible wire changes
27
+ - **Minor**: incremented for backward-compatible additions
28
+
29
+ Implementations MUST reject messages with an unrecognized major version. Implementations SHOULD accept messages with a recognized major version and an unrecognized minor version by ignoring unknown optional fields.
30
+
31
+ ### 1.2 Audit Version
32
+
33
+ Audit events use a separate version string: `ink-audit/1`
34
+
35
+ This version follows the same policy but is independent of the transport protocol version. An audit version bump does not require a transport version bump or vice versa.
36
+
37
+ ### 1.3 Wire-namespace prefix (`network.tulpa.*`)
38
+
39
+ Message `type` fields throughout INK v0.1 carry the prefix
40
+ `network.tulpa.*` (e.g. `network.tulpa.encrypted`, `network.tulpa.challenge`).
41
+ This is a **historical artifact** of INK's origin at Tulpa and is *not*
42
+ intended to imply Tulpa ownership of the protocol, Ad Astra Computing
43
+ stewards INK; Tulpa is one product built on it.
44
+
45
+ The prefix is preserved in v0.x for wire compatibility with deployed
46
+ implementations. A future major version MAY introduce a vendor-neutral
47
+ prefix (e.g. `network.ink.*`) and define a transition policy. Until then,
48
+ conforming implementations MUST emit and accept `network.tulpa.*` types as
49
+ specified.
50
+
51
+ ---
52
+
53
+ ## 2. Compatibility Rules
54
+
55
+ ### 2.1 Breaking Changes (Major Version Bump Required)
56
+
57
+ The following changes MUST trigger a major version bump:
58
+
59
+ | Change | Reason |
60
+ |--------|--------|
61
+ | Signature base format change | Breaks all existing verification |
62
+ | Required field added to existing message type | Old implementations reject new messages |
63
+ | Required field removed from existing message type | New implementations reject old messages |
64
+ | Field type changed (e.g. string → number) | Deserialization breaks |
65
+ | Cryptographic algorithm change (signing, encryption, hashing) | Interop breaks silently |
66
+ | JCS canonicalization behavior change | Signature verification breaks |
67
+ | Replay protection parameter change (window size, nonce format) | Messages rejected incorrectly |
68
+ | Auth header scheme change | Transport auth breaks |
69
+ | Key status semantics change | Verification behavior changes |
70
+
71
+ ### 2.2 Backward-Compatible Changes (Minor Version Bump)
72
+
73
+ The following changes MAY be made under the same major version:
74
+
75
+ | Change | Constraint |
76
+ |--------|-----------|
77
+ | New optional field on existing message type | Receivers MUST ignore unknown fields |
78
+ | New intent type | Receivers respond with `unsupported_intent` rejection |
79
+ | New receipt disposition | Receivers MUST accept unknown dispositions gracefully |
80
+ | New audit event type | Processors MUST ignore unknown event types |
81
+ | New handshake challenge type | Receivers respond with appropriate rejection |
82
+ | New key algorithm added to Agent Card | Receivers skip keys with unknown algorithms |
83
+ | New optional capability in Agent Card | Receivers ignore unknown capability blocks |
84
+
85
+ ### 2.3 Non-Version Changes
86
+
87
+ The following changes do not require a version bump:
88
+
89
+ - Documentation clarifications that do not change wire behavior
90
+ - New test vectors for existing behavior
91
+ - Implementation bug fixes that bring behavior into spec compliance
92
+ - New optional Agent Card metadata fields outside `keys` and `capabilities`
93
+
94
+ ---
95
+
96
+ ## 3. Unknown Fields and Types
97
+
98
+ ### 3.1 Unknown Fields
99
+
100
+ Implementations MUST preserve unknown fields during canonicalization (JCS handles this correctly). Implementations MUST NOT reject messages containing unknown fields.
101
+
102
+ ### 3.2 Unknown Message Types
103
+
104
+ When a receiver encounters an unknown `type` value in a handshake or protocol message, it MUST respond with a `network.tulpa.rejection` with reason `unsupported_intent`.
105
+
106
+ When a receiver encounters an unknown `intent` in a message envelope, it MUST respond with a rejection and SHOULD send a `received` receipt if receipt support is advertised.
107
+
108
+ ### 3.3 Unknown Key Algorithms
109
+
110
+ When building a candidate key set for verification, implementations MUST skip keys with unrecognized `algorithm` values. Verification proceeds with the remaining candidates.
111
+
112
+ ### 3.4 Unknown Audit Event Types
113
+
114
+ Audit chain processors MUST NOT break on unknown event types. Unknown events MUST be included in hash chain computation to preserve chain integrity.
115
+
116
+ ---
117
+
118
+ ## 4. Capability Advertisement
119
+
120
+ ### 4.1 Mechanism
121
+
122
+ Agent Cards are the canonical mechanism for capability advertisement. The `capabilities` block declares what an agent supports.
123
+
124
+ Implementations MUST NOT assume capabilities that are not advertised.
125
+
126
+ ### 4.2 Required Capabilities
127
+
128
+ Every base INK v0.1 agent MUST support:
129
+ - Transport signing (`INK-Ed25519` auth header)
130
+ - Replay protection (timestamp + nonce)
131
+ - At least one intent type
132
+ - Agent Card discovery at `GET /ink/v1/{agentId}/agent.json`
133
+
134
+ ### 4.3 Optional Capabilities
135
+
136
+ The following are advertised per-agent and MUST NOT be assumed:
137
+
138
+ | Capability | Agent Card field | Default if absent |
139
+ |-----------|-----------------|-------------------|
140
+ | Receipt support | `capabilities.receipts` | No receipts |
141
+ | Bilateral audit exchange | `capabilities.auditExchange` | Not supported |
142
+ | Third-party witness | `capabilities.thirdPartyAudit` | Not supported |
143
+ | Encryption | Presence of encryption keys in `keys.encryption` | Encryption not supported |
144
+ | Key rotation | Presence of `keys.signing` array | Legacy single-key mode |
145
+ | Handshake stages | Presence of `/challenge`, `/rejection`, `/resolution` endpoints | Not supported |
146
+
147
+ ### 4.4 Capability Negotiation
148
+
149
+ INK does not use explicit capability negotiation handshakes. Capabilities are discovered by reading the Agent Card before first contact.
150
+
151
+ If a sender requires a capability the receiver does not advertise (e.g. encryption for `schedule_meeting`), the sender MUST NOT send the message. It MAY inform the user that the recipient does not support the required capability.
152
+
153
+ ---
154
+
155
+ ## 5. Wire Format Stability
156
+
157
+ ### 5.1 Signature Base
158
+
159
+ The signature base format is the most stability-critical element of INK:
160
+
161
+ ```
162
+ ink/<version>\n<METHOD>\n<PATH>\n<recipientDid>\n<JCS(body)>\n<timestamp>
163
+ ```
164
+
165
+ Any change to this format, field order, separator, domain prefix, canonicalization algorithm, is a breaking change.
166
+
167
+ ### 5.2 Auth Header
168
+
169
+ ```
170
+ INK-Ed25519 <base64url(signature)> [keyId=<keyId>]
171
+ ```
172
+
173
+ The `keyId` parameter is optional and was added in a backward-compatible way. The regex `/^INK-Ed25519\s+(\S+)(?:\s+keyId=(\S+))?$/` accepts both forms.
174
+
175
+ Future parameters MUST use the same `key=value` syntax after the signature, space-separated.
176
+
177
+ ### 5.3 Encoding Conventions
178
+
179
+ These conventions are fixed for the lifetime of INK v1:
180
+
181
+ | Data | Encoding |
182
+ |------|----------|
183
+ | Ed25519/X25519 signatures | base64url (no padding, RFC 4648 §5) |
184
+ | Public keys in Agent Card | multibase (base58btc with multicodec prefix) |
185
+ | Hashes (SHA-256) | lowercase hex |
186
+ | Timestamps | ISO 8601 (e.g. `2026-03-25T12:00:00Z`) |
187
+ | Nonces | UUID without hyphens (hex) or base64url |
188
+ | AES-GCM nonces | base64url |
189
+ | Body canonicalization | JCS (RFC 8785) |
190
+
191
+ ### 5.4 Multicodec Prefixes
192
+
193
+ | Algorithm | Prefix bytes |
194
+ |-----------|-------------|
195
+ | Ed25519 | `0xed 0x01` |
196
+ | X25519 | `0xec 0x01` |
197
+
198
+ ---
199
+
200
+ ## 6. Deprecation Policy
201
+
202
+ ### 6.1 Feature Deprecation
203
+
204
+ A feature or field MAY be deprecated by:
205
+ 1. Documenting it as deprecated in the spec
206
+ 2. Adding a note to the compliance checklist
207
+ 3. Continuing to support it for at least one major version
208
+
209
+ ### 6.2 Version Sunset
210
+
211
+ A major version MAY be sunset after:
212
+ 1. The successor version has been stable for at least 6 months
213
+ 2. All known active implementations have been notified
214
+ 3. A documented migration path exists
215
+
216
+ ---
217
+
218
+ ## 7. Extension Points
219
+
220
+ ### 7.1 Intent Types
221
+
222
+ New intent types can be added without a version bump. They follow the same envelope format and signing rules.
223
+
224
+ Custom intent types SHOULD use reverse-domain naming (e.g. `network.tulpa.custom_intent`) to avoid collisions.
225
+
226
+ ### 7.2 Audit Event Types
227
+
228
+ New audit event types can be added without a version bump. They use the same `InkAuditEvent` envelope and chain mechanics.
229
+
230
+ ### 7.3 Agent Card Extensions
231
+
232
+ Agent Cards MAY include additional top-level or nested fields. Unknown fields MUST be ignored by consumers.
233
+
234
+ ---
235
+
236
+ ## 8. Implementation Guidance
237
+
238
+ ### 8.1 Version Checking
239
+
240
+ Implementations MUST check `protocol` on every inbound message. The check SHOULD compare only the major version for forward compatibility:
241
+
242
+ ```
243
+ const [major] = protocol.split("/")[1].split(".");
244
+ if (major !== "0") reject("unsupported_protocol_version");
245
+ ```
246
+
247
+ ### 8.2 Forward Compatibility
248
+
249
+ Implementations SHOULD be written to tolerate:
250
+ - Unknown optional fields on any message type
251
+ - Unknown intent types (reject gracefully)
252
+ - Unknown audit event types (include in chain, skip processing)
253
+ - Unknown key algorithms (skip during verification)
254
+ - Unknown Agent Card fields (ignore)
255
+
256
+ ### 8.3 Strict Validation
257
+
258
+ Implementations MUST strictly validate:
259
+ - Signature base construction (exact format)
260
+ - Timestamp freshness windows
261
+ - Required fields on all message types
262
+ - Key status semantics (active/retired/revoked)
263
+ - Hash chain integrity for audit events