@haven_ai/signer 0.3.0-alpha.0 → 0.5.0-alpha.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/dist/index.d.ts CHANGED
@@ -1,20 +1,23 @@
1
- import { X402ExpectedAuth, X402PaymentRequired, X402PaymentOption, SweepAuthorization, SweepExpectedAuth } from '@haven_ai/sdk';
1
+ import { X402ExpectedAuth, X402PaymentRequired, X402PaymentOption, SweepAuthorization, SweepExpectedAuth, HavenClientUpdate } from '@haven_ai/sdk/edge';
2
+ export { deriveDelegateAccountAddress } from '@haven_ai/sdk/edge';
2
3
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
- import { z } from 'zod/v3';
4
+ import { z as z$1 } from 'zod/v3';
5
+ import { Stats } from 'node:fs';
6
+ import { z } from 'zod';
4
7
 
5
8
  /**
6
9
  * The edge signer core.
7
10
  *
8
- * Holds the delegate key in this process and exposes the two signing
9
- * operations a hosted-MCP flow needs. It performs no network I/O and never
11
+ * Holds the delegate key in this process and exposes the signing operations a
12
+ * hosted-MCP flow needs — five, none of them a raw-hash primitive (#3169): each
13
+ * takes a payload something can check — a Haven binding verified here, typed
14
+ * data the account validates on-chain, or a sweep authorization. It performs no network I/O and never
10
15
  * returns the key — only signatures and the standard x402 header. See
11
16
  * docs/architecture/07-edge-signer.md.
12
17
  */
13
18
  interface EdgeSigner {
14
19
  /** Address derived from the delegate key. */
15
20
  readonly delegateAddress: string;
16
- /** Sign an AllowanceModule funding/transfer hash (raw ECDSA, 65 bytes). */
17
- signPaymentHash(hash: string): string;
18
21
  /**
19
22
  * Sign a DIRECT delegation-rail payment's EIP-712 typed data (#1254) — the
20
23
  * non-x402 counterpart of `signX402FundingTypedData`. The Hybrid account
@@ -25,24 +28,27 @@ interface EdgeSigner {
25
28
  * visible to this process's audit log.
26
29
  */
27
30
  signDelegationTypedData(typedData: Record<string, unknown>): Promise<string>;
28
- /** Sign an x402 funding hash and remember the funded merchant-header context. */
29
- signX402FundingHash(hash: string, expected: X402ExpectedPayment): X402FundingSignatureResult;
30
31
  /**
31
32
  * Sign a delegation-rail x402 funding intent's EIP-712 typed data (#1138) and
32
- * remember the funded merchant-header context, exactly as the hash path does.
33
+ * remember the funded merchant-header context — every x402 funding intent
34
+ * now takes this path (#3272 criterion 8: the bare-hash v1 rail is
35
+ * retired).
33
36
  *
34
- * The account validates this typed data, NOT the bare ERC-4337 hash, so the
35
- * expected context must be v2 and commit to its digest — see
36
- * `assertExpectedBinding`.
37
+ * The account validates this typed data, NOT a bare hash, so the expected
38
+ * context must be v2/v3 and commit to its digest — see
39
+ * `assertExpectedBinding`. `typedData` is `undefined` when the caller had
40
+ * none to supply; that is itself refused (a v1 context that needed no typed
41
+ * data is refused earlier, by the version check).
37
42
  */
38
- signX402FundingTypedData(typedData: X402FundingTypedData, expected: X402ExpectedPayment): Promise<X402FundingSignatureResult>;
43
+ signX402FundingTypedData(typedData: X402FundingTypedData | undefined, expected: X402ExpectedPayment): Promise<X402FundingSignatureResult>;
39
44
  /** Build + sign the EIP-3009 X-PAYMENT header for the merchant leg of x402. */
40
45
  buildX402PaymentHeader(paymentRequired: X402PaymentRequired, x402Binding: string): Promise<X402HeaderResult>;
41
46
  /**
42
47
  * Sign a Haven-prepared EIP-3009 sweep authorization (gasless USDC recovery
43
- * delegate → Safe). Verifies the authorization came from Haven and pays out to
44
- * the delegate's own Safe before signing; the relayer broadcasts it and pays
45
- * gas. Never broadcasts — pure signing.
48
+ * delegate → the agent's account (Haven wallet)). Verifies the authorization
49
+ * came from Haven and pays out to the agent's account (Haven wallet) — the
50
+ * account belongs to the agent, not the delegate — before signing; the
51
+ * relayer broadcasts it and pays gas. Never broadcasts — pure signing.
46
52
  */
47
53
  signSweepAuthorization(input: SweepSignatureInput): Promise<SweepSignatureResult>;
48
54
  }
@@ -51,7 +57,7 @@ interface SweepSignatureInput {
51
57
  authorization: SweepAuthorization;
52
58
  /** Haven's signature over the authorization context (binding). */
53
59
  expectedAuth: SweepExpectedAuth;
54
- /** Optional Safe address from the local credential, cross-checked against `to`. */
60
+ /** Optional account (Haven wallet) address from the local credential, cross-checked against `to` when present. */
55
61
  expectedSafe?: string;
56
62
  }
57
63
  interface SweepSignatureResult {
@@ -120,7 +126,7 @@ interface X402HeaderResult {
120
126
  accepted: X402PaymentOption;
121
127
  }
122
128
  interface X402FundingSignatureResult {
123
- /** Raw ECDSA signature over the Haven funding hash. */
129
+ /** EIP-712 signature over the Haven-committed funding typed data. */
124
130
  signature: string;
125
131
  /** Opaque process-local binding for the later merchant header signing step. */
126
132
  x402Binding: string;
@@ -148,11 +154,12 @@ declare function assertX402MatchesExpected(paymentRequired: X402PaymentRequired,
148
154
  * version arrives *here* instead of dying at the schema boundary with a raw
149
155
  * validation string.
150
156
  *
151
- * **Adding a version here is not sufficient to support it.** The mode rules in
157
+ * **Adding a version here is not sufficient to support it.** The rules in
152
158
  * `assertExpectedBinding` derive the expected version from the context's
153
- * *contents* (`typedDataHash` present ⇒ 2), not from `auth.version`, so a v3
154
- * that carries anything new needs that derivation extended in the same change.
155
- * Widening this array alone would admit a v3 context to the v1/v2 rule set:
159
+ * *contents* (`payerDelegate` present ⇒ 3, else 2 — `typedDataHash` is
160
+ * required unconditionally, #3272), not from `auth.version`, so a v4 that
161
+ * carries anything new needs that derivation extended in the same change.
162
+ * Widening this array alone would admit a v4 context to the v2/v3 rule set:
156
163
  * the array announces what this signer can evaluate, it does not define it.
157
164
  */
158
165
  declare const SUPPORTED_X402_EXPECTED_VERSIONS: readonly number[];
@@ -181,7 +188,7 @@ declare const SUPPORTED_SWEEP_BINDING_VERSIONS: readonly number[];
181
188
  *
182
189
  * #2347: that word was "authorised" until this change, and the reading was
183
190
  * always the correct one — Haven does sign this message. It is now "declared",
184
- * the word `settlement-child.ts` already uses for this exact binding ("proves
191
+ * the word the settlement-child verifier (`@haven_ai/sdk`'s `settlement-child.ts`) already uses for this exact binding ("proves
185
192
  * Haven *declared* a payload … it says nothing about what the payload MEANS"),
186
193
  * because on an agent-facing refusal inside a payment flow the broader word
187
194
  * invites the #2334 misreading that Haven is what authorises the spend. It is
@@ -206,9 +213,10 @@ declare function assertSupportedBindingVersion(received: number, supported: read
206
213
  *
207
214
  * **The check is necessarily agent-mediated.** The signer and the hosted Haven
208
215
  * MCP are two separate servers connected to the same client; neither can
209
- * introspect the other. The signer's single Haven call (#1263, the read-only
210
- * `GET /x402/:payment_id/sign-context` in `sign-context.ts`) does not help
211
- * here: it fetches one payment's signing bytes, not the hosted server's
216
+ * introspect the other. The signer's Haven reads (#1263's read-only
217
+ * `GET /x402/:payment_id/sign-context` and #3271's direct-payment
218
+ * `GET /payments/:payment_id/sign-context`, both in `sign-context.ts`) do not help
219
+ * here: each fetches one payment's signing bytes, not the hosted server's
212
220
  * handshake, and it happens at signing time — after the quote this module
213
221
  * exists to get ahead of. So only the agent sees both handshakes, and what
214
222
  * ships here is the *information* plus the prompt to compare it — never a
@@ -236,6 +244,13 @@ interface SignerCompatibility {
236
244
  x402_expected_context_versions: number[];
237
245
  /** Sweep-binding versions this signer will verify (`SUPPORTED_SWEEP_BINDING_VERSIONS`). */
238
246
  sweep_binding_versions: number[];
247
+ /**
248
+ * #3271: `direct_sign_context_version`s this signer will fetch and verify
249
+ * from `GET /payments/:id/sign-context` — derived from the SDK's
250
+ * `DIRECT_SIGN_CONTEXT_VERSION` via `SUPPORTED_DIRECT_SIGN_CONTEXT_VERSIONS`,
251
+ * never a second literal.
252
+ */
253
+ direct_sign_context_versions: number[];
239
254
  }
240
255
  /** The supported sets this signer enforces, as a plain serialisable object. */
241
256
  declare function signerCompatibility(): SignerCompatibility;
@@ -269,9 +284,11 @@ declare function signerInstructions(): string;
269
284
  *
270
285
  * That is a claim about this credential, not about the process, and the
271
286
  * difference matters: the sentence that used to stand here ("it does not call
272
- * the Haven API") was retired by #1263. The MCP server layer does make one
273
- * authenticated call — a read-only `GET /x402/:payment_id/sign-context`, see
274
- * `sign-context.ts` — and it reads the `api_url` / `api_key` for it from a
287
+ * the Haven API") was retired by #1263. The MCP server layer does make at
288
+ * most two authenticated reads per signing call — a read-only
289
+ * `GET /x402/:payment_id/sign-context` and, for a direct payment via
290
+ * `haven_sign` since #3271, then `GET /payments/:payment_id/sign-context`, see
291
+ * `sign-context.ts` — and it reads the `api_url` / `api_key` for them from a
275
292
  * SEPARATE `identity.json` in the same directory as the credential file
276
293
  * resolved here. That is why `sourcePath` below is load-bearing rather than
277
294
  * diagnostic, and why a key supplied through `HAVEN_DELEGATE_KEY` alone (no
@@ -352,6 +369,65 @@ declare function readAccountAddressEnv(env: NodeJS.ProcessEnv): string | undefin
352
369
  */
353
370
  declare function warnIfCredentialFilePermissive(path: string, log?: (message: string) => void, platform?: NodeJS.Platform): Promise<void>;
354
371
 
372
+ /** Owner-only: the mode every file this package writes beside a credential gets. */
373
+ declare const OWNER_ONLY_MODE = 384;
374
+ type PermissionLog = (message: string) => void;
375
+ interface PermissiveFile {
376
+ /** The offending mode, e.g. `0644`. */
377
+ octal: string;
378
+ /** False for a symlink, directory or device at that path — never chmod those. */
379
+ regular: boolean;
380
+ }
381
+ /**
382
+ * Is this path readable, writable or executable by anyone but its owner?
383
+ * Returns null when the path cannot be stat'ed or is owner-only, and on
384
+ * Windows where POSIX mode bits do not map cleanly (the same carve-out
385
+ * `@haven_ai/mcp` makes). Pass `stats` to reuse a stat the caller took
386
+ * IMMEDIATELY before — with `stats` the path is not touched, so a cached
387
+ * `Stats` would be trusted over the disk. `follow` decides what a symlink at the path means: a file the signer
388
+ * only READS (the credential) is judged by its target, which is what the
389
+ * content protection is about and what the pre-#3172 `stat` did; a file the
390
+ * signer is about to CHMOD (the audit sidecar) is judged as the link, so a
391
+ * planted symlink is never chmod-ed through (#3172 review).
392
+ */
393
+ declare function permissiveMode(path: string, platform?: NodeJS.Platform, stats?: Stats, follow?: 'follow' | 'nofollow'): Promise<PermissiveFile | null>;
394
+ /**
395
+ * Warn (best-effort, POSIX only) when a file this signer READS is readable
396
+ * beyond its owner. The signer does not own the credential, so it tells the
397
+ * operator what to run rather than changing a file it was handed. Follows a
398
+ * symlink, as the pre-#3172 `stat` did: a link to a 0600 credential is fine.
399
+ */
400
+ declare function warnIfFilePermissive(kind: string, path: string, log?: PermissionLog, platform?: NodeJS.Platform): Promise<void>;
401
+ type TightenOutcome = 'owner-only' | 'tightened' | 'warned' | 'skipped';
402
+ /**
403
+ * Tighten a file this signer WRITES to owner-only when it finds it
404
+ * permissive (#3172): the audit sidecar was created with the default mode
405
+ * (0644 under the usual umask 022) by every release before this one, so an
406
+ * existing sidecar is fixed in place rather than warned about. Judged with
407
+ * `lstat`. Falls back to the warning when the path is not a regular file (a
408
+ * symlink would make chmod hit whatever it points at) or when chmod is
409
+ * refused (a file owned by another user). Outcomes: `tightened` / `warned`
410
+ * as above; `owner-only` when there is nothing to tighten (owner-only file,
411
+ * or no file at that path); `skipped` on Windows only.
412
+ */
413
+ declare function tightenIfFilePermissive(kind: string, path: string, log?: PermissionLog, platform?: NodeJS.Platform, stats?: Stats): Promise<TightenOutcome>;
414
+
415
+ /**
416
+ * #3172: the sidecar is bounded. When it reaches this size the current file is
417
+ * renamed to `<path>.1` (replacing the previous `.1`) and a fresh file starts,
418
+ * so at most two generations — the live file and one predecessor — ever exist.
419
+ * 8 MiB is roughly 30 000 entries at the ~270-byte row size; nothing in the
420
+ * signer reads the file back, so the bound protects the disk and the reader's
421
+ * patience, never a signing decision.
422
+ */
423
+ declare const AUDIT_ROTATE_BYTES: number;
424
+ interface AppendAuditOptions {
425
+ /** Where permission notices go; defaults to stderr. */
426
+ log?: PermissionLog;
427
+ /** Rotation threshold in bytes; exported default `AUDIT_ROTATE_BYTES`. */
428
+ rotateAtBytes?: number;
429
+ platform?: NodeJS.Platform;
430
+ }
355
431
  interface SigningAuditEntry {
356
432
  version: 1;
357
433
  timestamp: string;
@@ -376,7 +452,7 @@ interface SigningAuditContext {
376
452
  auditPath?: string;
377
453
  }
378
454
  declare function defaultSigningAuditPath(credentialsPath?: string): string;
379
- declare function appendSigningAuditEntry(entry: SigningAuditEntry, path: string): Promise<void>;
455
+ declare function appendSigningAuditEntry(entry: SigningAuditEntry, path: string, options?: AppendAuditOptions): Promise<void>;
380
456
  declare function createSigningAuditEntry(tool: SignerToolName, payloadHash: string, context: SigningAuditContext, now?: Date): SigningAuditEntry;
381
457
  declare function hashPayloadForAudit(payload: unknown): string;
382
458
 
@@ -385,13 +461,227 @@ interface HavenIdentity {
385
461
  apiKey: string;
386
462
  }
387
463
 
464
+ /**
465
+ * #3103 (epic #3105, slice 4/5): the signer's typed next step.
466
+ *
467
+ * The signer decides a `next_action` on its refusals and, until this module,
468
+ * never said which tool to call. It cannot know the client's server names
469
+ * (#2550), so the handoff is expressed through the role fields the SDK
470
+ * builder renders — `next_tool_server_role: hosted`, `next_tool_name` — from
471
+ * a bare tool name. The signer must not import the hosted server (it is an
472
+ * npm package installed on user machines; the hosted server is a Docker
473
+ * deployment), so the hosted tools it hands off to are DECLARED here as the
474
+ * argument shape the signer actually emits, and `next-step-signer-parity`
475
+ * in the hosted server's own suite pins them to the hosted schemas at test
476
+ * time — the mirror of how the hosted server declares the signer's shapes.
477
+ */
478
+ declare const SIGNER_HOSTED_HANDOFF_SHAPES: {
479
+ readonly haven_get_payment_status: {
480
+ readonly payment_id: z.ZodString;
481
+ };
482
+ };
483
+
388
484
  /**
389
485
  * Local signer tool set. These run on the agent's machine, next to the key,
390
486
  * and pair with the hosted server's construct/relay tools (#183). They sign;
391
487
  * they never call the Haven API and never emit the key.
392
488
  */
393
489
  type SignerToolName = 'haven_sign' | 'haven_x402_sign_header' | 'haven_sign_x402' | 'haven_sign_sweep_delegate';
394
- declare const toolSchemas: Record<SignerToolName, z.ZodRawShape>;
490
+ declare const toolSchemas: {
491
+ readonly haven_sign_sweep_delegate: {
492
+ readonly authorization: z$1.ZodObject<{
493
+ from: z$1.ZodString;
494
+ to: z$1.ZodString;
495
+ value: z$1.ZodString;
496
+ validAfter: z$1.ZodString;
497
+ validBefore: z$1.ZodString;
498
+ nonce: z$1.ZodString;
499
+ token: z$1.ZodString;
500
+ chainId: z$1.ZodNumber;
501
+ }, "strip", z$1.ZodTypeAny, {
502
+ chainId: number;
503
+ to: string;
504
+ from: string;
505
+ nonce: string;
506
+ value: string;
507
+ token: string;
508
+ validAfter: string;
509
+ validBefore: string;
510
+ }, {
511
+ chainId: number;
512
+ to: string;
513
+ from: string;
514
+ nonce: string;
515
+ value: string;
516
+ token: string;
517
+ validAfter: string;
518
+ validBefore: string;
519
+ }>;
520
+ readonly expected_auth: z$1.ZodObject<{
521
+ version: z$1.ZodNumber;
522
+ message: z$1.ZodString;
523
+ signature: z$1.ZodString;
524
+ signer: z$1.ZodString;
525
+ }, "strip", z$1.ZodTypeAny, {
526
+ message: string;
527
+ version: number;
528
+ signature: string;
529
+ signer: string;
530
+ }, {
531
+ message: string;
532
+ version: number;
533
+ signature: string;
534
+ signer: string;
535
+ }>;
536
+ };
537
+ readonly haven_sign: {
538
+ readonly payment_id: z$1.ZodOptional<z$1.ZodString>;
539
+ readonly payload_hash: z$1.ZodOptional<z$1.ZodString>;
540
+ readonly x402_expected: z$1.ZodOptional<z$1.ZodObject<{
541
+ payment_id: z$1.ZodString;
542
+ payload_hash: z$1.ZodString;
543
+ resource_url: z$1.ZodString;
544
+ merchant_to: z$1.ZodString;
545
+ amount: z$1.ZodString;
546
+ asset: z$1.ZodString;
547
+ network: z$1.ZodString;
548
+ expires_at: z$1.ZodString;
549
+ typed_data_hash: z$1.ZodOptional<z$1.ZodString>;
550
+ payer_delegate: z$1.ZodOptional<z$1.ZodString>;
551
+ payer_agent_id: z$1.ZodOptional<z$1.ZodString>;
552
+ auth: z$1.ZodObject<{
553
+ version: z$1.ZodNumber;
554
+ message: z$1.ZodString;
555
+ signature: z$1.ZodString;
556
+ signer: z$1.ZodString;
557
+ }, "strip", z$1.ZodTypeAny, {
558
+ message: string;
559
+ version: number;
560
+ signature: string;
561
+ signer: string;
562
+ }, {
563
+ message: string;
564
+ version: number;
565
+ signature: string;
566
+ signer: string;
567
+ }>;
568
+ }, "strip", z$1.ZodTypeAny, {
569
+ payment_id: string;
570
+ expires_at: string;
571
+ network: string;
572
+ payload_hash: string;
573
+ resource_url: string;
574
+ merchant_to: string;
575
+ amount: string;
576
+ asset: string;
577
+ auth: {
578
+ message: string;
579
+ version: number;
580
+ signature: string;
581
+ signer: string;
582
+ };
583
+ typed_data_hash?: string | undefined;
584
+ payer_delegate?: string | undefined;
585
+ payer_agent_id?: string | undefined;
586
+ }, {
587
+ payment_id: string;
588
+ expires_at: string;
589
+ network: string;
590
+ payload_hash: string;
591
+ resource_url: string;
592
+ merchant_to: string;
593
+ amount: string;
594
+ asset: string;
595
+ auth: {
596
+ message: string;
597
+ version: number;
598
+ signature: string;
599
+ signer: string;
600
+ };
601
+ typed_data_hash?: string | undefined;
602
+ payer_delegate?: string | undefined;
603
+ payer_agent_id?: string | undefined;
604
+ }>>;
605
+ readonly typed_data: z$1.ZodOptional<z$1.ZodRecord<z$1.ZodString, z$1.ZodUnknown>>;
606
+ readonly typed_data_b64: z$1.ZodOptional<z$1.ZodString>;
607
+ };
608
+ readonly haven_x402_sign_header: {
609
+ readonly payment_required: z$1.ZodRecord<z$1.ZodString, z$1.ZodUnknown>;
610
+ readonly x402_binding: z$1.ZodString;
611
+ };
612
+ readonly haven_sign_x402: {
613
+ readonly payment_id: z$1.ZodOptional<z$1.ZodString>;
614
+ readonly payload_hash: z$1.ZodOptional<z$1.ZodString>;
615
+ readonly x402_expected: z$1.ZodOptional<z$1.ZodObject<{
616
+ payment_id: z$1.ZodString;
617
+ payload_hash: z$1.ZodString;
618
+ resource_url: z$1.ZodString;
619
+ merchant_to: z$1.ZodString;
620
+ amount: z$1.ZodString;
621
+ asset: z$1.ZodString;
622
+ network: z$1.ZodString;
623
+ expires_at: z$1.ZodString;
624
+ typed_data_hash: z$1.ZodOptional<z$1.ZodString>;
625
+ payer_delegate: z$1.ZodOptional<z$1.ZodString>;
626
+ payer_agent_id: z$1.ZodOptional<z$1.ZodString>;
627
+ auth: z$1.ZodObject<{
628
+ version: z$1.ZodNumber;
629
+ message: z$1.ZodString;
630
+ signature: z$1.ZodString;
631
+ signer: z$1.ZodString;
632
+ }, "strip", z$1.ZodTypeAny, {
633
+ message: string;
634
+ version: number;
635
+ signature: string;
636
+ signer: string;
637
+ }, {
638
+ message: string;
639
+ version: number;
640
+ signature: string;
641
+ signer: string;
642
+ }>;
643
+ }, "strip", z$1.ZodTypeAny, {
644
+ payment_id: string;
645
+ expires_at: string;
646
+ network: string;
647
+ payload_hash: string;
648
+ resource_url: string;
649
+ merchant_to: string;
650
+ amount: string;
651
+ asset: string;
652
+ auth: {
653
+ message: string;
654
+ version: number;
655
+ signature: string;
656
+ signer: string;
657
+ };
658
+ typed_data_hash?: string | undefined;
659
+ payer_delegate?: string | undefined;
660
+ payer_agent_id?: string | undefined;
661
+ }, {
662
+ payment_id: string;
663
+ expires_at: string;
664
+ network: string;
665
+ payload_hash: string;
666
+ resource_url: string;
667
+ merchant_to: string;
668
+ amount: string;
669
+ asset: string;
670
+ auth: {
671
+ message: string;
672
+ version: number;
673
+ signature: string;
674
+ signer: string;
675
+ };
676
+ typed_data_hash?: string | undefined;
677
+ payer_delegate?: string | undefined;
678
+ payer_agent_id?: string | undefined;
679
+ }>>;
680
+ readonly payment_required: z$1.ZodOptional<z$1.ZodRecord<z$1.ZodString, z$1.ZodUnknown>>;
681
+ readonly typed_data: z$1.ZodOptional<z$1.ZodRecord<z$1.ZodString, z$1.ZodUnknown>>;
682
+ readonly typed_data_b64: z$1.ZodOptional<z$1.ZodString>;
683
+ };
684
+ };
395
685
  declare const toolDescriptions: Record<SignerToolName, string>;
396
686
  interface ToolSuccess<T> {
397
687
  success: true;
@@ -406,6 +696,13 @@ interface ToolFailure {
406
696
  next_action?: string;
407
697
  retry_with_new_quote?: boolean;
408
698
  suggested_tool?: string;
699
+ /** #3101 (epic #3105, decision 7): the typed next-step family, additive; `next_tool` never null. */
700
+ next_tool?: string;
701
+ next_tool_server?: string;
702
+ next_tool_name?: string;
703
+ next_tool_server_role?: 'hosted' | 'signer';
704
+ next_arguments?: Record<string, unknown>;
705
+ next_tool_omitted_reason?: string;
409
706
  /**
410
707
  * #1309: present on `UNSUPPORTED_EXPECTED_CONTEXT_VERSION` /
411
708
  * `UNSUPPORTED_SWEEP_BINDING_VERSION` refusals — the exact version set this
@@ -424,11 +721,14 @@ interface ToolFailure {
424
721
  fallback?: string;
425
722
  /**
426
723
  * #3001: present on `SIGN_CONTEXT_REFUSED` — the HTTP status the backend
427
- * answered the `/x402/:id/sign-context` fetch with (404, 410, …).
724
+ * answered the sign-context fetch with (404, 410, …) — the x402 fetch, or
725
+ * the direct `/payments/:id/sign-context` fetch since #3271.
428
726
  */
429
727
  http_status?: number;
430
- /** #3001: the backend's own `error_code` on `SIGN_CONTEXT_REFUSED` (`expired`, `already_executed`, `not_signable`, `sign_context_unavailable`). */
728
+ /** #3001: the backend's own `error_code` on `SIGN_CONTEXT_REFUSED` (`expired`, `already_executed`, `not_signable`, `sign_context_unavailable`, and since #3303 `client_outdated`). */
431
729
  backend_error_code?: string;
730
+ /** #3303: on `client_outdated`, the backend's update hint — `upgrade_command` is what updates this signer. */
731
+ client_update?: HavenClientUpdate;
432
732
  }
433
733
  type ToolPayload<T = unknown> = ToolSuccess<T> | ToolFailure;
434
734
  interface ToolHandlerOptions {
@@ -443,6 +743,8 @@ interface ToolHandlerOptions {
443
743
  signContext?: {
444
744
  loadIdentity: () => Promise<HavenIdentity | null>;
445
745
  fetchImpl?: typeof fetch;
746
+ /** #3303: `@haven_ai/signer/<version>`, sent as `X-Haven-Client` on every sign-context read. */
747
+ clientIdentity?: string;
446
748
  };
447
749
  }
448
750
  declare function createToolHandlers(signer: EdgeSigner, options?: ToolHandlerOptions): Record<SignerToolName, (input: unknown) => Promise<ToolPayload>>;
@@ -474,7 +776,7 @@ declare function renderSignerConsentBlock(input: SignerConsentInput, hash: strin
474
776
  declare function ensureSignerConsent(input: SignerConsentInput, options?: SignerConsentOptions): Promise<SignerConsentDecision>;
475
777
 
476
778
  declare const SIGNER_NAME = "@haven_ai/signer";
477
- declare const SIGNER_VERSION = "0.3.0-alpha.0";
779
+ declare const SIGNER_VERSION = "0.5.0-alpha.1";
478
780
  interface SignerOptions {
479
781
  /** Path to a Haven credential JSON file (delegate_key is read from it). */
480
782
  credentialsPath?: string;
@@ -539,4 +841,4 @@ declare function assertSupportedNodeVersion(nodeVersion?: string): void;
539
841
  declare function runSignerStdioServer(options?: SignerOptions): Promise<void>;
540
842
  declare function runSignerConsentGate(signer: EdgeSigner, credentials: SignerCredentials | undefined, options: SignerOptions): Promise<SignerConsentDecision>;
541
843
 
542
- export { type EdgeSigner, type ResolvedSignerRuntime, SIGNER_ACK_ENV, SIGNER_CAPABILITY_KEY, SIGNER_NAME, SIGNER_VERSION, SUPPORTED_SWEEP_BINDING_VERSIONS, SUPPORTED_X402_EXPECTED_VERSIONS, type SignerCompatibility, type SignerConsentDecision, type SignerConsentInput, type SignerConsentOptions, type SignerCredentials, type SignerOptions, type SignerToolName, type SigningAuditContext, type SigningAuditEntry, type ToolFailure, type ToolPayload, type ToolSuccess, type X402ExpectedPayment, type X402FundingSignatureResult, type X402HeaderResult, appendSigningAuditEntry, assertSupportedBindingVersion, assertSupportedNodeVersion, assertX402MatchesExpected, buildSignerMcpServer, computeSignerConsentHash, createEdgeSigner, createSigningAuditEntry, createToolHandlers, defaultSigningAuditPath, ensureSignerConsent, hashPayloadForAudit, loadSignerCredentials, readAccountAddressEnv, readAccountAddressField, renderSignerConsentBlock, resolveEdgeSigner, resolveSignerRuntime, runSignerConsentGate, runSignerStdioServer, signerCapabilityAdvertisement, signerCompatibility, signerInstructions, toolDescriptions, toolSchemas, warnIfCredentialFilePermissive };
844
+ export { AUDIT_ROTATE_BYTES, type AppendAuditOptions, type EdgeSigner, OWNER_ONLY_MODE, type PermissionLog, type PermissiveFile, type ResolvedSignerRuntime, SIGNER_ACK_ENV, SIGNER_CAPABILITY_KEY, SIGNER_HOSTED_HANDOFF_SHAPES, SIGNER_NAME, SIGNER_VERSION, SUPPORTED_SWEEP_BINDING_VERSIONS, SUPPORTED_X402_EXPECTED_VERSIONS, type SignerCompatibility, type SignerConsentDecision, type SignerConsentInput, type SignerConsentOptions, type SignerCredentials, type SignerOptions, type SignerToolName, type SigningAuditContext, type SigningAuditEntry, type TightenOutcome, type ToolFailure, type ToolPayload, type ToolSuccess, type X402ExpectedPayment, type X402FundingSignatureResult, type X402HeaderResult, appendSigningAuditEntry, assertSupportedBindingVersion, assertSupportedNodeVersion, assertX402MatchesExpected, buildSignerMcpServer, computeSignerConsentHash, createEdgeSigner, createSigningAuditEntry, createToolHandlers, defaultSigningAuditPath, ensureSignerConsent, hashPayloadForAudit, loadSignerCredentials, permissiveMode, readAccountAddressEnv, readAccountAddressField, renderSignerConsentBlock, resolveEdgeSigner, resolveSignerRuntime, runSignerConsentGate, runSignerStdioServer, signerCapabilityAdvertisement, signerCompatibility, signerInstructions, tightenIfFilePermissive, toolDescriptions, toolSchemas, warnIfCredentialFilePermissive, warnIfFilePermissive };