MACP

Error Handling

Error Class Hierarchy

Error
└── MacpSdkError           Base class for all SDK errors
    ├── MacpTransportError  gRPC connectivity issues
    └── MacpAckError        Runtime rejected the message (nack)

MacpAckError

Thrown when the runtime returns an Ack with ok: false. Contains the full Ack object:

import { MacpAckError } from 'macp-sdk-typescript';

try {
  await session.vote({ proposalId: 'p1', vote: 'approve' });
} catch (err) {
  if (err instanceof MacpAckError) {
    console.log(err.ack.error?.code);    // e.g., 'SESSION_NOT_OPEN'
    console.log(err.ack.error?.message); // human-readable description
    console.log(err.ack.sessionId);      // session context
    console.log(err.ack.sessionState);   // current session state
  }
}

Suppressing Auto-Throw

By default, client.send() throws on nack. Disable this to handle rejections manually:

const ack = await client.send(envelope, { raiseOnNack: false });
if (!ack.ok) {
  console.log('rejected:', ack.error?.code);
}

MacpTransportError

Thrown on gRPC connectivity failures:

import { MacpTransportError } from 'macp-sdk-typescript';

try {
  await client.initialize();
} catch (err) {
  if (err instanceof MacpTransportError) {
    console.log('gRPC error:', err.message);
  }
}

Runtime Error Codes

The MACP runtime uses structured error codes in the Ack:

CodeMeaning
UNAUTHENTICATEDAuthentication failed
FORBIDDENSender not authorized for this session or message type
SESSION_NOT_FOUNDSession does not exist
SESSION_NOT_OPENSession already resolved or expired
DUPLICATE_MESSAGEmessage_id already accepted within session
INVALID_ENVELOPEEnvelope validation failed or payload structure invalid
UNSUPPORTED_PROTOCOL_VERSIONNo mutually supported protocol version
MODE_NOT_SUPPORTEDMode or mode version not supported
PAYLOAD_TOO_LARGEPayload exceeds allowed size (default 1MB)
RATE_LIMITEDToo many requests
INVALID_SESSION_IDSession ID format invalid
INTERNAL_ERRORUnrecoverable internal runtime error

Duplicate Handling

The runtime deduplicates messages by message_id. If a duplicate is detected, the Ack returns ok: true with duplicate: true — it is not an error:

const ack = await client.send(envelope);
if (ack.duplicate) {
  console.log('message was already accepted (idempotent)');
}

Patterns

Retry on Transient Errors

async function sendWithRetry(
  client: MacpClient,
  envelope: Envelope,
  maxRetries = 3,
): Promise<Ack> {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await client.send(envelope);
    } catch (err) {
      if (err instanceof MacpTransportError && attempt < maxRetries - 1) {
        await new Promise((r) => setTimeout(r, 1000 * (attempt + 1)));
        continue;
      }
      if (err instanceof MacpAckError && err.ack.error?.code === 'RATE_LIMITED') {
        await new Promise((r) => setTimeout(r, 2000 * (attempt + 1)));
        continue;
      }
      throw err;
    }
  }
  throw new Error('unreachable');
}

Graceful Session Handling

try {
  await session.commit({
    action: 'deployment.approved',
    authorityScope: 'ops',
    reason: 'approved',
  });
} catch (err) {
  if (err instanceof MacpAckError) {
    switch (err.ack.error?.code) {
      case 'SESSION_NOT_OPEN':
        console.log('session already resolved or expired');
        break;
      case 'FORBIDDEN':
        console.log('not authorized to commit');
        break;
      default:
        throw err;
    }
  }
}