macp-sdk-typescript 0.19.0 → 0.20.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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,13 @@ All notable changes to `macp-sdk-typescript` are documented here. The format
4
4
  follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the
5
5
  project uses [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [0.20.0](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/compare/v0.19.0...v0.20.0) (2026-10-26)
8
+
9
+
10
+ ### Features
11
+
12
+ * runtime-issued decision receipts — getDecisionReceipt, getReceiptKeys, trustedKeysFromJwks (RFC-MACP-0013 §13) ([#210](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/issues/210)) ([931d9e1](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/commit/931d9e13d7695a9f4659d0538e85dcb709eaeb20))
13
+
7
14
  ## [0.19.0](https://github.com/multiagentcoordinationprotocol/macp-sdk-typescript/compare/v0.18.0...v0.19.0) (2026-10-26)
8
15
 
9
16
 
package/README.md CHANGED
@@ -72,7 +72,7 @@ client.close();
72
72
 
73
73
  The SDK uses a **three-layer** design:
74
74
 
75
- - **Low-level transport** (`MacpClient`): typed wrappers around every `MACPRuntimeService` RPC — `initialize`, `send`, `openStream`, `getSession`, `cancelSession`, `suspendSession`, `resumeSession`, `listSessions`, `getManifest`, `listModes`, `listRoots`, `listExtModes`, `registerExtMode`, `unregisterExtMode`, `promoteMode`, the policy RPCs (`register`/`unregister`/`get`/`listPolicies`, and `whatIfReplay` where the runtime advertises it), plus streaming watchers (`ModeRegistryWatcher`, `RootsWatcher`, `SignalWatcher`, `PolicyWatcher`, `SessionLifecycleWatcher`).
75
+ - **Low-level transport** (`MacpClient`): typed wrappers around every `MACPRuntimeService` RPC — `initialize`, `send`, `openStream`, `getSession`, `cancelSession`, `suspendSession`, `resumeSession`, `listSessions`, `getManifest`, `listModes`, `listRoots`, `listExtModes`, `registerExtMode`, `unregisterExtMode`, `promoteMode`, the policy RPCs (`register`/`unregister`/`get`/`listPolicies`, and `whatIfReplay` where the runtime advertises it), `getDecisionReceipt`/`getReceiptKeys` where the runtime advertises receipts, plus streaming watchers (`ModeRegistryWatcher`, `RootsWatcher`, `SignalWatcher`, `PolicyWatcher`, `SessionLifecycleWatcher`).
76
76
  - **High-level session helpers**: one class per coordination mode (`DecisionSession`, `ProposalSession`, `TaskSession`, `HandoffSession`, `QuorumSession`) — wraps `MacpClient`, builds envelopes, encodes payloads, and maintains a local state projection.
77
77
  - **Agent framework** (`src/agent/`): event-driven `Participant` with strategy-based handlers, bootstrap runner (`fromBootstrap`), gRPC/HTTP transports, and the optional cancel-callback HTTP server.
78
78
 
@@ -463,6 +463,29 @@ const result = verifyReceipt(receipt, { 'prod-2026': 'TDVzkxaSf6_Oxu7mmn5TbCkF80
463
463
  | `attestations[i]` | `valid`, `invalid`, `unknown_key` or `unsupported` (any other `alg`, and every `vendor` attestation) |
464
464
  | `verdict` | `attested` (integrity holds and at least one attestation is valid), `integrity_only`, `unverified` (integrity fails, so attestations are not checked), or `refused` (unknown `receipt_format`) |
465
465
 
466
+ To trust a published key set (a JWK Set, such as a runtime's `GetReceiptKeys` response; RFC-MACP-0013 §13), convert it with `trustedKeysFromJwks(jwks)`:
467
+
468
+ ```typescript
469
+ import { trustedKeysFromJwks, verifyReceipt } from 'macp-sdk-typescript/receipt';
470
+
471
+ const trustedKeys = trustedKeysFromJwks(jwks); // { 'example-1': 'TDVzkxaSf6_…' }
472
+ verifyReceipt(receipt, trustedKeys).verdict; // 'attested'
473
+ ```
474
+
475
+ It keeps only Ed25519 public keys (`kty` `OKP`, `crv` `Ed25519`) with a `kid` and an `x`, whose `alg` (if any) is `EdDSA` and `use` (if any) is `sig`. It drops a key that carries a private member (`d`, `k`, …) and any `kid` that more than one entry of the set claims, even when the other entry is a key it can't use (an EC key, say). It never throws: anything that isn't a key set gives `{}`. Deciding to trust the set is still up to you.
476
+
477
+ ### Fetching a runtime-issued receipt
478
+
479
+ A runtime that advertises `capabilities.decisionReceipt.getDecisionReceipt` issues receipts itself (RFC-MACP-0013 §13). Fetch one for a resolved session, fetch the runtime's key set, and verify:
480
+
481
+ ```typescript
482
+ const { receipt, raw } = await client.getDecisionReceipt(sessionId); // raw: the exact bytes served
483
+ const jwks = await client.getReceiptKeys(); // { keys: [] } from an unsigned runtime
484
+ verifyReceipt(receipt, trustedKeysFromJwks(jwks)).verdict; // 'attested', or 'integrity_only' if unsigned
485
+ ```
486
+
487
+ Both methods throw `MacpSdkError` without sending anything when the runtime doesn't advertise the capability. A session the runtime can't issue a receipt for (not resolved yet, or resolved without a `CommitmentPayload`) gives `MacpTransportError` with `macpErrorCode` `RECEIPT_UNAVAILABLE`.
488
+
466
489
  If you hold the snake_case §3 projection (a receipt's `commitment`) instead of a `CommitmentPayload`, convert it with `commitmentFromProjection(projection)`.
467
490
 
468
491
  `verifyReceipt` never throws. `buildReceipt` throws `MacpSdkError` on a missing field, and when `commitmentWire` is not the wire encoding of the commitment. Signing is deterministic: the same inputs always produce the same receipt.
package/dist/client.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as grpc from '@grpc/grpc-js';
2
2
  import { type AuthConfig } from './auth';
3
3
  import { ProtoRegistry } from './proto-registry';
4
+ import type { DecisionReceiptResult, ReceiptKeySet } from './receipt';
4
5
  import type { Ack, AgentManifest, Envelope, InitializeResult, ModeDescriptor, PolicyDescriptor, SessionMetadata, Root, WhatIfReplayResult } from './types';
5
6
  interface MacpClientOptions {
6
7
  address: string;
@@ -99,12 +100,21 @@ export declare class MacpClient {
99
100
  private readonly defaultDeadlineMs?;
100
101
  private readonly clientName;
101
102
  private readonly clientVersion;
102
- /** The last {@link initialize} result; {@link whatIfReplay} reads its capabilities. */
103
+ /** The last {@link initialize} result; capability-gated RPCs read its capabilities. */
103
104
  private initializeResult?;
105
+ /** An `Initialize` that {@link requireCapability} started and is still in flight. */
106
+ private pendingInit?;
104
107
  /** The runtime's capabilities from the last {@link initialize}; `undefined` before one. */
105
108
  get serverCapabilities(): Record<string, unknown> | undefined;
106
109
  constructor(options: MacpClientOptions);
107
110
  private requireAuth;
111
+ /**
112
+ * Throws {@link MacpSdkError}, without sending anything, unless
113
+ * `capabilities.<group>.<flag>` from {@link initialize} is `true`. Calls
114
+ * `Initialize` once if this client hasn't; concurrent first callers share
115
+ * that one in-flight call (and the first caller's deadline).
116
+ */
117
+ private requireCapability;
108
118
  private metadata;
109
119
  private deadline;
110
120
  private unary;
@@ -249,6 +259,45 @@ export declare class MacpClient {
249
259
  auth?: AuthConfig;
250
260
  deadlineMs?: number;
251
261
  }): Promise<WhatIfReplayResult>;
262
+ /**
263
+ * Fetch the runtime-issued decision receipt for a resolved session
264
+ * (RFC-MACP-0013 §13). Returns the parsed `macp-receipt/1` object and `raw`,
265
+ * the exact JSON text the runtime served, which is stable across calls
266
+ * (§13 item 6), so it can be stored or forwarded byte for byte. Verify it
267
+ * with `verifyReceipt(receipt, trustedKeysFromJwks(await getReceiptKeys()))`;
268
+ * this method does not.
269
+ *
270
+ * The RPC is optional, so this checks
271
+ * `capabilities.decisionReceipt.getDecisionReceipt` from {@link initialize}
272
+ * first (calling it once if this client hasn't) and throws
273
+ * {@link MacpSdkError} without sending anything when the runtime doesn't
274
+ * advertise it. Authorized as {@link getSession}. Runtime refusals arrive as
275
+ * {@link MacpTransportError} with `macpErrorCode` set: `RECEIPT_UNAVAILABLE`
276
+ * (no receipt can be issued: not resolved yet, or resolved without a
277
+ * `CommitmentPayload`), `SESSION_NOT_FOUND`, `FORBIDDEN`,
278
+ * `INTERNAL_ERROR`. A `receipt_json` that isn't a JSON object throws
279
+ * {@link MacpSdkError}.
280
+ */
281
+ getDecisionReceipt(sessionId: string, options?: {
282
+ auth?: AuthConfig;
283
+ deadlineMs?: number;
284
+ }): Promise<DecisionReceiptResult>;
285
+ /**
286
+ * Fetch the JWK Set of public keys the runtime signs receipts with
287
+ * (RFC-MACP-0013 §13 item 8); an unsigned runtime returns `{keys: []}`. Pass
288
+ * it to `trustedKeysFromJwks` for the map `verifyReceipt` takes. Whether
289
+ * to trust these keys is the caller's decision.
290
+ *
291
+ * Gated on `capabilities.decisionReceipt.getDecisionReceipt` like
292
+ * {@link getDecisionReceipt}. Needs transport auth only, like
293
+ * {@link getManifest}: `auth` is optional and falls back to the client's.
294
+ * A `jwks_json` that isn't a JSON object with a `keys` array throws
295
+ * {@link MacpSdkError}.
296
+ */
297
+ getReceiptKeys(options?: {
298
+ auth?: AuthConfig;
299
+ deadlineMs?: number;
300
+ }): Promise<ReceiptKeySet>;
252
301
  /**
253
302
  * Fetch a single page of sessions (proto ≥ 0.1.6). `pageSize: 0` (or absent)
254
303
  * lets the server choose the page size; the server MAY cap it. Callers MUST
package/dist/client.js CHANGED
@@ -276,8 +276,10 @@ class MacpClient {
276
276
  defaultDeadlineMs;
277
277
  clientName;
278
278
  clientVersion;
279
- /** The last {@link initialize} result; {@link whatIfReplay} reads its capabilities. */
279
+ /** The last {@link initialize} result; capability-gated RPCs read its capabilities. */
280
280
  initializeResult;
281
+ /** An `Initialize` that {@link requireCapability} started and is still in flight. */
282
+ pendingInit;
281
283
  /** The runtime's capabilities from the last {@link initialize}; `undefined` before one. */
282
284
  get serverCapabilities() {
283
285
  return this.initializeResult?.capabilities;
@@ -320,6 +322,23 @@ class MacpClient {
320
322
  throw new errors_1.MacpSdkError('this operation requires auth; pass auth= or configure client.auth');
321
323
  return selected;
322
324
  }
325
+ /**
326
+ * Throws {@link MacpSdkError}, without sending anything, unless
327
+ * `capabilities.<group>.<flag>` from {@link initialize} is `true`. Calls
328
+ * `Initialize` once if this client hasn't; concurrent first callers share
329
+ * that one in-flight call (and the first caller's deadline).
330
+ */
331
+ async requireCapability(group, flag, rpc, rfc, deadlineMs) {
332
+ const capabilities = this.serverCapabilities ??
333
+ (await (this.pendingInit ??= this.initialize(deadlineMs).finally(() => {
334
+ this.pendingInit = undefined;
335
+ }))).capabilities;
336
+ const advertised = capabilities?.[group];
337
+ if (advertised?.[flag] !== true) {
338
+ throw new errors_1.MacpSdkError(`this runtime does not advertise capabilities.${group}.${flag}; ` +
339
+ `${rpc} is optional and a client MUST NOT call it otherwise (${rfc})`);
340
+ }
341
+ }
323
342
  metadata(auth) {
324
343
  const selected = auth ?? this.auth;
325
344
  if (!selected)
@@ -538,12 +557,7 @@ class MacpClient {
538
557
  */
539
558
  async whatIfReplay(sessionId, candidate, options) {
540
559
  const auth = this.requireAuth(options?.auth);
541
- const capabilities = this.serverCapabilities ?? (await this.initialize(options?.deadlineMs)).capabilities;
542
- const policyRegistry = capabilities?.policyRegistry;
543
- if (!policyRegistry?.whatIfReplay) {
544
- throw new errors_1.MacpSdkError('this runtime does not advertise capabilities.policyRegistry.whatIfReplay; ' +
545
- 'WhatIfReplay is optional and a client MUST NOT call it otherwise (RFC-MACP-0012 §8.1)');
546
- }
560
+ await this.requireCapability('policyRegistry', 'whatIfReplay', 'WhatIfReplay', 'RFC-MACP-0012 §8.1', options?.deadlineMs);
547
561
  const res = await this.unary('WhatIfReplay', { sessionId, candidate }, auth, options?.deadlineMs);
548
562
  // proto3 `optional` decodes with a synthetic oneof discriminator; an unset
549
563
  // first_divergence is absent, never "0" (index 0 is the SessionStart).
@@ -555,6 +569,54 @@ class MacpClient {
555
569
  delete result._firstDivergence;
556
570
  return result;
557
571
  }
572
+ /**
573
+ * Fetch the runtime-issued decision receipt for a resolved session
574
+ * (RFC-MACP-0013 §13). Returns the parsed `macp-receipt/1` object and `raw`,
575
+ * the exact JSON text the runtime served, which is stable across calls
576
+ * (§13 item 6), so it can be stored or forwarded byte for byte. Verify it
577
+ * with `verifyReceipt(receipt, trustedKeysFromJwks(await getReceiptKeys()))`;
578
+ * this method does not.
579
+ *
580
+ * The RPC is optional, so this checks
581
+ * `capabilities.decisionReceipt.getDecisionReceipt` from {@link initialize}
582
+ * first (calling it once if this client hasn't) and throws
583
+ * {@link MacpSdkError} without sending anything when the runtime doesn't
584
+ * advertise it. Authorized as {@link getSession}. Runtime refusals arrive as
585
+ * {@link MacpTransportError} with `macpErrorCode` set: `RECEIPT_UNAVAILABLE`
586
+ * (no receipt can be issued: not resolved yet, or resolved without a
587
+ * `CommitmentPayload`), `SESSION_NOT_FOUND`, `FORBIDDEN`,
588
+ * `INTERNAL_ERROR`. A `receipt_json` that isn't a JSON object throws
589
+ * {@link MacpSdkError}.
590
+ */
591
+ async getDecisionReceipt(sessionId, options) {
592
+ const auth = this.requireAuth(options?.auth);
593
+ await this.requireCapability('decisionReceipt', 'getDecisionReceipt', 'GetDecisionReceipt', 'RFC-MACP-0013 §13', options?.deadlineMs);
594
+ const res = await this.unary('GetDecisionReceipt', { sessionId }, auth, options?.deadlineMs);
595
+ const raw = res.receiptJson ?? '';
596
+ const receipt = parseJsonObject(raw, 'receipt_json');
597
+ return { receipt: receipt, raw };
598
+ }
599
+ /**
600
+ * Fetch the JWK Set of public keys the runtime signs receipts with
601
+ * (RFC-MACP-0013 §13 item 8); an unsigned runtime returns `{keys: []}`. Pass
602
+ * it to `trustedKeysFromJwks` for the map `verifyReceipt` takes. Whether
603
+ * to trust these keys is the caller's decision.
604
+ *
605
+ * Gated on `capabilities.decisionReceipt.getDecisionReceipt` like
606
+ * {@link getDecisionReceipt}. Needs transport auth only, like
607
+ * {@link getManifest}: `auth` is optional and falls back to the client's.
608
+ * A `jwks_json` that isn't a JSON object with a `keys` array throws
609
+ * {@link MacpSdkError}.
610
+ */
611
+ async getReceiptKeys(options) {
612
+ await this.requireCapability('decisionReceipt', 'getDecisionReceipt', 'GetReceiptKeys', 'RFC-MACP-0013 §13', options?.deadlineMs);
613
+ const res = await this.unary('GetReceiptKeys', {}, options?.auth, options?.deadlineMs);
614
+ const jwks = parseJsonObject(res.jwksJson ?? '', 'jwks_json');
615
+ if (!Array.isArray(jwks.keys)) {
616
+ throw new errors_1.MacpSdkError('runtime returned a jwks_json with no keys array');
617
+ }
618
+ return jwks;
619
+ }
558
620
  /**
559
621
  * Fetch a single page of sessions (proto ≥ 0.1.6). `pageSize: 0` (or absent)
560
622
  * lets the server choose the page size; the server MAY cap it. Callers MUST
@@ -683,3 +745,17 @@ class MacpClient {
683
745
  }
684
746
  }
685
747
  exports.MacpClient = MacpClient;
748
+ /** Parses runtime-served JSON text that must be an object, else throws {@link MacpSdkError}. */
749
+ function parseJsonObject(text, field) {
750
+ let value;
751
+ try {
752
+ value = JSON.parse(text);
753
+ }
754
+ catch (err) {
755
+ throw new errors_1.MacpSdkError(`runtime returned a ${field} that is not valid JSON: ${err.message}`);
756
+ }
757
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
758
+ throw new errors_1.MacpSdkError(`runtime returned a ${field} that is not a JSON object`);
759
+ }
760
+ return value;
761
+ }
@@ -42,6 +42,13 @@ export declare const AGENT_NOT_FOUND = "AGENT_NOT_FOUND";
42
42
  * holds (e.g. compacted when it ended) — FAILED_PRECONDITION. RFC-MACP-0012 §8.1.
43
43
  */
44
44
  export declare const HISTORY_UNAVAILABLE = "HISTORY_UNAVAILABLE";
45
+ /**
46
+ * GetDecisionReceipt named a session no receipt can be issued for: not
47
+ * RESOLVED, resolved without a `CommitmentPayload`, or issuance failed —
48
+ * FAILED_PRECONDITION. RFC-MACP-0013 §13. **Provisional** in the spec's error
49
+ * registry (not yet in the parity contract's permanent set).
50
+ */
51
+ export declare const RECEIPT_UNAVAILABLE = "RECEIPT_UNAVAILABLE";
45
52
  export declare const DENY_VETO = "veto";
46
53
  export declare const DENY_VETO_HOLD = "veto_hold";
47
54
  export declare const DENY_QUORUM_NOT_MET = "quorum_not_met";
package/dist/constants.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.KNOWN_DENY_CODES = exports.DENY_UNSPECIFIED = exports.DENY_INVALID_RULES = exports.DENY_UNSUPPORTED_SCHEMA_VERSION = exports.DENY_REASSIGNMENT_NOT_ALLOWED = exports.DENY_OUTPUT_REQUIRED = exports.DENY_CRITERION_NOT_MET = exports.DENY_DECLINE_NOT_BACKED = exports.DENY_DECLINE_OVER_APPROVAL = exports.DENY_NO_VOTES = exports.DENY_EVALUATION_REQUIRED = exports.DENY_CONFIDENCE_BELOW_MINIMUM = exports.DENY_THRESHOLD_NOT_MET = exports.DENY_QUORUM_NOT_MET = exports.DENY_VETO_HOLD = exports.DENY_VETO = exports.HISTORY_UNAVAILABLE = exports.AGENT_NOT_FOUND = exports.STREAM_LAGGED = exports.INVALID_REQUEST = exports.REGISTRY_READ_ONLY = exports.INVALID_POLICY_DEFINITION = exports.UNKNOWN_POLICY_VERSION = exports.INVALID_SESSION_ID = exports.POLICY_DENIED = exports.INTERNAL_ERROR = exports.RATE_LIMITED = exports.PAYLOAD_TOO_LARGE = exports.DUPLICATE_MESSAGE = exports.UNAUTHENTICATED = exports.FORBIDDEN = exports.MODE_NOT_SUPPORTED = exports.SESSION_NOT_OPEN = exports.SESSION_NOT_FOUND = exports.SESSION_ALREADY_EXISTS = exports.INVALID_ENVELOPE = exports.UNSUPPORTED_PROTOCOL_VERSION = exports.STANDARD_MODES = exports.MODE_MULTI_ROUND = exports.MODE_QUORUM = exports.MODE_HANDOFF = exports.MODE_TASK = exports.MODE_PROPOSAL = exports.MODE_DECISION = exports.DEFAULT_POLICY_VERSION = exports.DEFAULT_CONFIGURATION_VERSION = exports.DEFAULT_MODE_VERSION = exports.MACP_VERSION = void 0;
3
+ exports.KNOWN_DENY_CODES = exports.DENY_UNSPECIFIED = exports.DENY_INVALID_RULES = exports.DENY_UNSUPPORTED_SCHEMA_VERSION = exports.DENY_REASSIGNMENT_NOT_ALLOWED = exports.DENY_OUTPUT_REQUIRED = exports.DENY_CRITERION_NOT_MET = exports.DENY_DECLINE_NOT_BACKED = exports.DENY_DECLINE_OVER_APPROVAL = exports.DENY_NO_VOTES = exports.DENY_EVALUATION_REQUIRED = exports.DENY_CONFIDENCE_BELOW_MINIMUM = exports.DENY_THRESHOLD_NOT_MET = exports.DENY_QUORUM_NOT_MET = exports.DENY_VETO_HOLD = exports.DENY_VETO = exports.RECEIPT_UNAVAILABLE = exports.HISTORY_UNAVAILABLE = exports.AGENT_NOT_FOUND = exports.STREAM_LAGGED = exports.INVALID_REQUEST = exports.REGISTRY_READ_ONLY = exports.INVALID_POLICY_DEFINITION = exports.UNKNOWN_POLICY_VERSION = exports.INVALID_SESSION_ID = exports.POLICY_DENIED = exports.INTERNAL_ERROR = exports.RATE_LIMITED = exports.PAYLOAD_TOO_LARGE = exports.DUPLICATE_MESSAGE = exports.UNAUTHENTICATED = exports.FORBIDDEN = exports.MODE_NOT_SUPPORTED = exports.SESSION_NOT_OPEN = exports.SESSION_NOT_FOUND = exports.SESSION_ALREADY_EXISTS = exports.INVALID_ENVELOPE = exports.UNSUPPORTED_PROTOCOL_VERSION = exports.STANDARD_MODES = exports.MODE_MULTI_ROUND = exports.MODE_QUORUM = exports.MODE_HANDOFF = exports.MODE_TASK = exports.MODE_PROPOSAL = exports.MODE_DECISION = exports.DEFAULT_POLICY_VERSION = exports.DEFAULT_CONFIGURATION_VERSION = exports.DEFAULT_MODE_VERSION = exports.MACP_VERSION = void 0;
4
4
  exports.MACP_VERSION = '1.0';
5
5
  exports.DEFAULT_MODE_VERSION = '1.0.0';
6
6
  exports.DEFAULT_CONFIGURATION_VERSION = 'config.default';
@@ -49,6 +49,13 @@ exports.AGENT_NOT_FOUND = 'AGENT_NOT_FOUND';
49
49
  * holds (e.g. compacted when it ended) — FAILED_PRECONDITION. RFC-MACP-0012 §8.1.
50
50
  */
51
51
  exports.HISTORY_UNAVAILABLE = 'HISTORY_UNAVAILABLE';
52
+ /**
53
+ * GetDecisionReceipt named a session no receipt can be issued for: not
54
+ * RESOLVED, resolved without a `CommitmentPayload`, or issuance failed —
55
+ * FAILED_PRECONDITION. RFC-MACP-0013 §13. **Provisional** in the spec's error
56
+ * registry (not yet in the parity contract's permanent set).
57
+ */
58
+ exports.RECEIPT_UNAVAILABLE = 'RECEIPT_UNAVAILABLE';
52
59
  // Structured POLICY_DENIED denial codes (`PolicyDenial.code`, runtime ≥ 0.9.0;
53
60
  // RFC-MACP-0012 §6.2). Open set — names match python-sdk's exports.
54
61
  exports.DENY_VETO = 'veto';
package/dist/index.d.ts CHANGED
@@ -14,7 +14,7 @@ export * from './proposal';
14
14
  export * from './proto-registry';
15
15
  export * from './retry';
16
16
  export * from './quorum';
17
- export { RECEIPT_FORMAT, buildReceipt, commitmentFromProjection, verifyReceipt, type AttestationOutcome, type BuildReceiptOptions, type DecisionReceipt, type JwsAttestation, type ReceiptAttestation, type ReceiptCommitment, type ReceiptIntegrity, type ReceiptSigner, type ReceiptVerdict, type ReceiptVerification, type VendorAttestation, } from './receipt';
17
+ export { RECEIPT_FORMAT, buildReceipt, commitmentFromProjection, trustedKeysFromJwks, verifyReceipt, type AttestationOutcome, type BuildReceiptOptions, type DecisionReceipt, type DecisionReceiptResult, type JwsAttestation, type ReceiptAttestation, type ReceiptCommitment, type ReceiptIntegrity, type ReceiptJwk, type ReceiptKeySet, type ReceiptSigner, type ReceiptVerdict, type ReceiptVerification, type VendorAttestation, } from './receipt';
18
18
  export * from './task';
19
19
  export * from './types';
20
20
  export * from './validation';
package/dist/index.js CHANGED
@@ -36,7 +36,7 @@ var __importStar = (this && this.__importStar) || (function () {
36
36
  };
37
37
  })();
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.agent = exports.VERSION = exports.verifyReceipt = exports.commitmentFromProjection = exports.buildReceipt = exports.RECEIPT_FORMAT = exports.logger = exports.configureLogging = exports.verifyCommitmentHash = exports.isCanonicalCommitmentHash = exports.commitmentHash = exports.Auth = void 0;
39
+ exports.agent = exports.VERSION = exports.verifyReceipt = exports.trustedKeysFromJwks = exports.commitmentFromProjection = exports.buildReceipt = exports.RECEIPT_FORMAT = exports.logger = exports.configureLogging = exports.verifyCommitmentHash = exports.isCanonicalCommitmentHash = exports.commitmentHash = exports.Auth = void 0;
40
40
  // Auth: export only the public constructors and types. The helpers
41
41
  // `validateAuth`, `authSender`, `assertSenderMatchesIdentity`, and
42
42
  // `metadataFromAuth` are SDK-internal — importable from the `./auth`
@@ -84,6 +84,7 @@ var receipt_1 = require("./receipt");
84
84
  Object.defineProperty(exports, "RECEIPT_FORMAT", { enumerable: true, get: function () { return receipt_1.RECEIPT_FORMAT; } });
85
85
  Object.defineProperty(exports, "buildReceipt", { enumerable: true, get: function () { return receipt_1.buildReceipt; } });
86
86
  Object.defineProperty(exports, "commitmentFromProjection", { enumerable: true, get: function () { return receipt_1.commitmentFromProjection; } });
87
+ Object.defineProperty(exports, "trustedKeysFromJwks", { enumerable: true, get: function () { return receipt_1.trustedKeysFromJwks; } });
87
88
  Object.defineProperty(exports, "verifyReceipt", { enumerable: true, get: function () { return receipt_1.verifyReceipt; } });
88
89
  __exportStar(require("./task"), exports);
89
90
  __exportStar(require("./types"), exports);
package/dist/receipt.d.ts CHANGED
@@ -49,6 +49,32 @@ export interface DecisionReceipt {
49
49
  issued_at: string;
50
50
  attestations: ReceiptAttestation[];
51
51
  }
52
+ /**
53
+ * {@link MacpClient.getDecisionReceipt}'s result: the parsed receipt and the
54
+ * `receipt_json` text exactly as the runtime served it, so a caller can store
55
+ * the issued bytes unchanged (RFC-MACP-0013 §13 item 6 serves them
56
+ * byte-identical). `receipt` is parsed, not validated: {@link verifyReceipt}
57
+ * is the authority on whether it is a well-formed `macp-receipt/1`.
58
+ */
59
+ export interface DecisionReceiptResult {
60
+ receipt: DecisionReceipt;
61
+ raw: string;
62
+ }
63
+ /** One JWK of a receipt key set (`schemas/json/macp-receipt-keys.schema.json`). */
64
+ export interface ReceiptJwk {
65
+ kid: string;
66
+ kty: string;
67
+ crv?: string;
68
+ x?: string;
69
+ alg?: string;
70
+ use?: string;
71
+ [member: string]: unknown;
72
+ }
73
+ /** A JWK Set [RFC 7517] of receipt verification keys, as `GetReceiptKeys` returns it. */
74
+ export interface ReceiptKeySet {
75
+ keys: ReceiptJwk[];
76
+ [member: string]: unknown;
77
+ }
52
78
  /**
53
79
  * Signs a receipt with Ed25519: either a 32-byte private key seed, or a
54
80
  * callback that returns the 64-byte signature over `signingInput` (for a key
@@ -122,3 +148,19 @@ export declare function buildReceipt(commitment: CommitmentPayload, options: Bui
122
148
  * Never throws: malformed input yields an outcome, not an exception.
123
149
  */
124
150
  export declare function verifyReceipt(receipt: unknown, trustedKeys: Record<string, string>): ReceiptVerification;
151
+ /**
152
+ * Turns a receipt key set (the JWK Set `GetReceiptKeys` returns, RFC-MACP-0013
153
+ * §13 item 8) into the `kid → x` map {@link verifyReceipt} takes. Choosing to
154
+ * trust the set is the caller's decision; this only converts it.
155
+ *
156
+ * A key is kept only if it is an Ed25519 public key (`kty` `OKP`, `crv`
157
+ * `Ed25519`) with a non-empty `kid` and `x`, an `alg` (if any) of `EdDSA`, a
158
+ * `use` (if any) of `sig`, and no private member (`d`, `p`, `q`, `dp`, `dq`,
159
+ * `qi`, `oth`, `k`): a key set that publishes private key material is
160
+ * misconfigured and is not trusted silently. A `kid` that more than one entry
161
+ * of the set claims is dropped entirely, even when the other entry is a key
162
+ * this function can't use (an EC key, say): item 8 makes kids unique across
163
+ * the whole set, so a repeated one leaves the set ambiguous about which key it
164
+ * names. Never throws: input that is not a key set yields `{}`.
165
+ */
166
+ export declare function trustedKeysFromJwks(jwks: unknown): Record<string, string>;
package/dist/receipt.js CHANGED
@@ -4,6 +4,7 @@ exports.RECEIPT_FORMAT = void 0;
4
4
  exports.commitmentFromProjection = commitmentFromProjection;
5
5
  exports.buildReceipt = buildReceipt;
6
6
  exports.verifyReceipt = verifyReceipt;
7
+ exports.trustedKeysFromJwks = trustedKeysFromJwks;
7
8
  /**
8
9
  * RFC-MACP-0013 Appendix A decision receipts (`macp-receipt/1`).
9
10
  *
@@ -374,3 +375,54 @@ function verifyReceipt(receipt, trustedKeys) {
374
375
  verdict: outcomes.includes('valid') ? 'attested' : 'integrity_only',
375
376
  };
376
377
  }
378
+ /** JWK private-key members (RFC 7518 §6); RFC-MACP-0013 §13 item 8 forbids them in a key set. */
379
+ const PRIVATE_JWK_MEMBERS = ['d', 'p', 'q', 'dp', 'dq', 'qi', 'oth', 'k'];
380
+ const isNonEmptyString = (v) => typeof v === 'string' && v.length > 0;
381
+ /** An Ed25519 public verification key usable for an EdDSA `jws` attestation. */
382
+ function usableEd25519Key(key) {
383
+ if (!isRecord(key))
384
+ return false;
385
+ if (key.kty !== 'OKP' || key.crv !== 'Ed25519')
386
+ return false;
387
+ if (!isNonEmptyString(key.kid) || !isNonEmptyString(key.x))
388
+ return false;
389
+ if (key.alg !== undefined && key.alg !== 'EdDSA')
390
+ return false;
391
+ if (key.use !== undefined && key.use !== 'sig')
392
+ return false;
393
+ return !PRIVATE_JWK_MEMBERS.some((m) => Object.prototype.hasOwnProperty.call(key, m));
394
+ }
395
+ /**
396
+ * Turns a receipt key set (the JWK Set `GetReceiptKeys` returns, RFC-MACP-0013
397
+ * §13 item 8) into the `kid → x` map {@link verifyReceipt} takes. Choosing to
398
+ * trust the set is the caller's decision; this only converts it.
399
+ *
400
+ * A key is kept only if it is an Ed25519 public key (`kty` `OKP`, `crv`
401
+ * `Ed25519`) with a non-empty `kid` and `x`, an `alg` (if any) of `EdDSA`, a
402
+ * `use` (if any) of `sig`, and no private member (`d`, `p`, `q`, `dp`, `dq`,
403
+ * `qi`, `oth`, `k`): a key set that publishes private key material is
404
+ * misconfigured and is not trusted silently. A `kid` that more than one entry
405
+ * of the set claims is dropped entirely, even when the other entry is a key
406
+ * this function can't use (an EC key, say): item 8 makes kids unique across
407
+ * the whole set, so a repeated one leaves the set ambiguous about which key it
408
+ * names. Never throws: input that is not a key set yields `{}`.
409
+ */
410
+ function trustedKeysFromJwks(jwks) {
411
+ const result = {};
412
+ if (!isRecord(jwks) || !Array.isArray(jwks.keys))
413
+ return result;
414
+ // Count every entry's string kid, usable or not, before keeping any.
415
+ const claims = new Map();
416
+ for (const key of jwks.keys) {
417
+ if (isRecord(key) && typeof key.kid === 'string')
418
+ claims.set(key.kid, (claims.get(key.kid) ?? 0) + 1);
419
+ }
420
+ for (const key of jwks.keys) {
421
+ if (!usableEd25519Key(key) || claims.get(key.kid) !== 1)
422
+ continue;
423
+ // defineProperty, not assignment: a kid of "__proto__" must become an own
424
+ // key (which verifyReceipt's hasOwnProperty lookup finds), not a prototype.
425
+ Object.defineProperty(result, key.kid, { value: key.x, enumerable: true, writable: true, configurable: true });
426
+ }
427
+ return result;
428
+ }
package/dist/types.d.ts CHANGED
@@ -107,6 +107,23 @@ export interface PolicyRegistryCapability {
107
107
  */
108
108
  whatIfReplay?: boolean;
109
109
  }
110
+ /**
111
+ * `capabilities.decisionReceipt` (`macp.v1.DecisionReceiptCapability`, proto
112
+ * ≥ 0.1.13): optional runtime-issued decision receipts (RFC-MACP-0013 §13).
113
+ */
114
+ export interface DecisionReceiptCapability {
115
+ /**
116
+ * The runtime implements `GetDecisionReceipt` and `GetReceiptKeys`;
117
+ * {@link MacpClient.getDecisionReceipt} and {@link MacpClient.getReceiptKeys}
118
+ * refuse to call them otherwise.
119
+ */
120
+ getDecisionReceipt?: boolean;
121
+ /**
122
+ * Every receipt issued from now on carries at least one EdDSA `jws`
123
+ * attestation. Says nothing about receipts issued earlier.
124
+ */
125
+ signed?: boolean;
126
+ }
110
127
  /** How one replay handled one history entry (`macp.v1.WhatIfEntryOutcome`). */
111
128
  export interface WhatIfEntryOutcome {
112
129
  kind: 'KIND_UNSPECIFIED' | 'KIND_APPLIED' | 'KIND_NOT_OPEN' | 'KIND_REFUSED' | 'KIND_NO_SESSION';
package/dist/version.d.ts CHANGED
@@ -4,4 +4,4 @@
4
4
  * Kept in sync with package.json by release-please (see the
5
5
  * `x-release-please-version` annotation).
6
6
  */
7
- export declare const VERSION = "0.19.0";
7
+ export declare const VERSION = "0.20.0";
package/dist/version.js CHANGED
@@ -7,4 +7,4 @@ exports.VERSION = void 0;
7
7
  * Kept in sync with package.json by release-please (see the
8
8
  * `x-release-please-version` annotation).
9
9
  */
10
- exports.VERSION = '0.19.0'; // x-release-please-version
10
+ exports.VERSION = '0.20.0'; // x-release-please-version
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "macp-sdk-typescript",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "TypeScript SDK for the MACP Rust runtime",
5
5
  "license": "Apache-2.0",
6
6
  "type": "commonjs",
@@ -60,7 +60,7 @@
60
60
  "dependencies": {
61
61
  "@grpc/grpc-js": "^1.14.3",
62
62
  "@grpc/proto-loader": "^0.8.0",
63
- "@multiagentcoordinationprotocol/proto": "^0.1.12",
63
+ "@multiagentcoordinationprotocol/proto": "^0.1.13",
64
64
  "@noble/curves": "^1.9.7",
65
65
  "@noble/hashes": "^1.8.0",
66
66
  "protobufjs": "^8.6.5"