npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@astrasyncai/verification-gateway

v2.4.11

Published

AstraSync KYA Platform SDK — counterparty verification gateway (verify incoming requests) + agent registration (register AI agents with the KYA backend).

Readme

@astrasyncai/verification-gateway

The AstraSync KYA Platform SDK. One package, two roles: register agents with the KYA backend, and verify agents at any counterparty type (API / MCP / website / agent-to-agent).

What's in this package?

As of v2.4.0 this is the only npm package you need for AstraSync integration. Pick the subpath that matches what you're doing:

| If you're… | Import from | Get | | ----------------------------------------------------------------------------------------- | ------------------------------------------------ | --------------------------------------------------------------------------------------------- | | Registering an agent with the KYA backend | @astrasyncai/verification-gateway/registration | AstraSync class — register(), verify(), health(). The astrasync CLI is bundled too. | | Building an agent that calls out to other services (per-request credential injection) | @astrasyncai/verification-gateway/agent | AgentClient, ChallengeHandler, ownership validation, runtime PDLSS shape. | | Running an Express API that AI agents call into | @astrasyncai/verification-gateway/express | createMiddleware() — protect routes, attach verification context. | | Running a Next.js app that AI agents call into | @astrasyncai/verification-gateway/nextjs | createMiddleware(), Commerce Shield wiring. | | Running an MCP server that AI agents call into | @astrasyncai/verification-gateway/mcp | MCP tool gates + inner-hop dedupe headers. | | Doing direct verification (no framework) | @astrasyncai/verification-gateway/sdk | VerificationGatewayClient with retry / backoff / timeout. | | Parsing protocol-level credentials (HTTP / A2A / MCP / x402 / AP2 / MPP) | @astrasyncai/verification-gateway/transport | Cross-protocol credential extraction + injection. | | Evaluating PDLSS offline (local / hybrid mode) | @astrasyncai/verification-gateway/gateway | AstraSyncGateway (online / local / hybrid). | | Receiving webhook callbacks | @astrasyncai/verification-gateway/webhooks | verifyAstraSyncWebhook(), signAstraSyncWebhook(). |

All subpaths talk to the same backend — POST /agents/verify-access for verification, POST /agents/register for registration. The contract is shared; the role is just which side of the handshake you're sitting on.

Before v2.4.0 the agent-registration half shipped as a separate package (@astrasyncai/agent-registration, briefly; @astrasyncai/sdk before that). It was merged in to stop the two-package version drift and to give partners one install instead of two. The legacy @astrasyncai/sdk package on npm is deprecated with a pointer to this package; @astrasyncai/agent-registration was never published.

Overview

The Verification Gateway provides a single, universal solution for verifying AI agents. One codebase, multiple deployment targets:

  • Express.js middleware - Protect API endpoints
  • Next.js middleware - Protect web applications with Commerce Shield
  • SDK functions - Direct verification for agent-to-agent or serverless

All verification flows through the same POST /agents/verify-access endpoint, ensuring consistent PDLSS (Permission, Duration, Limit, Scope, Self-instantiation) enforcement.

Installation

npm install @astrasyncai/verification-gateway

Quick Start

Express Middleware

import express from 'express';
import { createMiddleware } from '@astrasyncai/verification-gateway/express';

const app = express();

// v2.3.7+ — per-route policy lives in the AstraSync dashboard.
// The SDK fetches it on init via counterpartyId; do NOT pass routes here.
app.use(
  createMiddleware({
    apiBaseUrl: 'https://astrasync.ai/api',
    apiKey: process.env.ASTRASYNC_API_KEY,
    counterpartyId: 'ASTRAE-...', // your endpoint id from the dashboard
    setPassThroughHeader: true, // recommended for local-dev / staging
    // — surfaces pass-through mode when
    // no per-route policy is configured
  })
);

Next.js Middleware

// middleware.ts
import { createMiddleware } from '@astrasyncai/verification-gateway/nextjs';

// v2.3.7+ — per-route policy lives in the AstraSync dashboard.
// The SDK fetches it on init via counterpartyId; do NOT pass routes here.
export const middleware = createMiddleware({
  apiBaseUrl: 'https://api.astrasync.ai',
  apiKey: process.env.ASTRASYNC_API_KEY,
  counterpartyId: 'ASTRAE-...',
  showCommerceShield: true,
});

export const config = {
  matcher: ['/api/:path*', '/dashboard/:path*'],
};

SDK (Direct Usage)

import { createClient } from '@astrasyncai/verification-gateway/sdk';

const gateway = createClient({
  apiBaseUrl: 'https://api.astrasync.ai',
});

// Verify another agent before interacting
const result = await gateway.verify({
  astraId: 'ASTRA-abc123',
  purpose: 'data-exchange',
});

if (result.verified && result.accessLevel !== 'none') {
  // Safe to interact with this agent
  console.log(`Trust score: ${result.agent?.trustScore}`);
}

Agent Registration

Use the SDK for all agent registration, whether you're a developer firing off a one-shot CLI call or an autonomous agent self-registering on first run. The SDK handles auth-mode routing for you: signature-authenticated callers get synchronous registration; API-key-only callers get an owner-approval handshake (server-side rule, not negotiable).

apiEndpoint — runtime-challenge URL

apiEndpoint is the URL where your agent's verification-gateway SDK is mounted to receive runtime challenges from counterparties. Optional but recommended — if you omit it, your agent declares no runtime-challenge support, which is included in the verification payload and may cause some counterparties to decline access requests.

Two registration response modes

The same register() call returns one of two shapes depending on auth context:

  • 201 active — synchronous: returned when the request is signed with a crypto keypair (privateKey configured) or when authenticated via email+password. Result: { status: 'active', agent }.
  • 202 pending_approval — API-key only: the SDK was authenticated with an API key but the request was not signed. The backend emails the account owner a "Sign In to Accept" link, fires a dashboard alert, and returns a tracking token. Result: { status: 'pending_approval', requestId, pollUrl, expiresAt }. The agent becomes active only after the owner approves.

This split enforces the platform rule that API-key registrations always require owner step-up — registering an agent autonomously with just an API key is not a sufficient authority signal on its own.

Pattern A — developer / long-running agent (block until approved)

Best for CLI tools, dev-machine first-run, and long-running services:

import {
  AstraSync,
  RegistrationDeniedError,
  RegistrationTimeoutError,
} from '@astrasyncai/verification-gateway/registration';

const sdk = new AstraSync({
  apiKey: process.env.ASTRASYNC_API_KEY,
  privateKey: process.env.ASTRASYNC_PRIVATE_KEY, // optional — when set, 201 sync path
});

try {
  const agent = await sdk.register({
    name: 'invoice-bot',
    description: 'Reconciles AP/AR across accounting systems.',
    agentType: 'autonomous',
    apiEndpoint: 'https://invoice-bot.example.com', // runtime-challenge URL
    model: { modelProvider: 'anthropic', modelName: 'claude-sonnet-4-5' },
    framework: { frameworkName: 'langchain', frameworkVersion: '0.3.0' },
    protocols: ['a2a', 'mcp'],
    pdlss: {
      purpose: {
        categories: ['accounting'],
        allowedActions: ['accounting.read', 'accounting.write'],
      },
      limits: { stepUpThreshold: 1_000, approvalThreshold: 10_000, currency: 'USD' },
      scope: { resources: ['xero.invoices', 'quickbooks.bills'] },
      selfInstantiation: { allowed: false },
    },
    // Blocking mode: poll until the request resolves.
    waitForApproval: true,
    timeoutMs: 10 * 60 * 1000, // 10 minutes default
    onPending: ({ ageMs }) =>
      console.log(`Awaiting owner approval (${(ageMs / 1000).toFixed(0)}s)…`),
  });
  console.log(`Agent registered: ${agent.astrasyncIdLevel1}`);
} catch (err) {
  if (err instanceof RegistrationDeniedError) {
    console.error('Owner denied:', err.reason);
  } else if (err instanceof RegistrationTimeoutError) {
    console.error(
      'Timed out — request is still active server-side; poll later via sdk.waitForApproval(requestId)'
    );
  } else {
    throw err;
  }
}

Pattern B — serverless / scheduled agent (non-blocking, exit and resume)

Best for Lambda / Cloud Functions / cron-driven self-registration where you can't hold the runtime open for minutes:

const sdk = new AstraSync({ apiKey: process.env.ASTRASYNC_API_KEY });

const result = await sdk.register({
  name: 'invoice-bot',
  apiEndpoint: 'https://invoice-bot.example.com',
  pdlss: { purpose: { categories: ['accounting'], allowedActions: ['accounting.read'] } },
});

if (result.status === 'pending_approval') {
  // Store the requestId in your durable storage (DynamoDB, KV, etc.)
  await store.set('astrasync.pendingRequestId', result.requestId);
  console.log(`Awaiting owner approval at ${result.pollUrl}; will resume on next scheduled run.`);
  return; // function exits — owner has time to approve
}

console.log(`Agent registered as ${result.agent.astrasyncIdLevel1}`);

On the next scheduled run, resume by polling:

const requestId = await store.get('astrasync.pendingRequestId');
const status = await sdk.pollRegistration(requestId);
if (status.state === 'approved') {
  await store.set('astrasync.agentId', status.agent.kyaAgentId);
  await store.delete('astrasync.pendingRequestId');
} else if (status.state === 'denied' || status.state === 'expired') {
  console.error(`Registration terminated: ${status.state}`);
}

CLI equivalent — same surface from a shell, also via npx:

npx astrasync register --name invoice-bot --agent-type autonomous \
  --model-provider anthropic --model-name claude-sonnet-4-5 \
  --framework-name langchain --framework-version 0.3.0 \
  --protocols a2a,mcp \
  --api-endpoint https://invoice-bot.example.com \
  --pdlss '{"purpose":{"categories":["accounting"],"allowedActions":["accounting.read"]}}'

The CLI uses the same response logic — it will print a "Sign in to approve" message and a poll URL when the response is 202 pending, and exit non-zero on deny/expire.

/registration is for one-shot onboarding — register an agent, look up its public profile, check API health. For per-request credential injection (attaching ASTRA-id, sessionId, PDLSS to outgoing HTTP / A2A / MCP calls from an already-registered agent), use /agent (AgentClient). The two roles are deliberately separate concerns: registration is identity, /agent is runtime.

A PDLSSConfig type exists in both /registration and /agent, with different shapes. /registration's PDLSSConfig is the boundary declaration submitted at register time; /agent's PDLSSConfig is the per-request runtime request shape. Disambiguate via the import path — the root export deliberately does not re-export either.

Inner-hop verification (MCP → REST dedupe with X-Astra-Verified-Hop)

When an MCP tool calls an inner REST endpoint that ALSO runs the verification gateway, two verify-access calls fire for the same agent in the same logical request — audit gets noisy and you pay a doubled round-trip. The X-Astra-Verified-Hop header marks the inner hop as already-verified by the upstream so the inner middleware skips a redundant verify-access call.

The MCP middleware emits the marker automatically on its outbound response. Tool handlers calling inner REST hops need to forward it on outgoing fetches; the inner middleware reads it via parseVerifiedHop + isVerifiedHopValidFor and skips a duplicate verify-access if the marker is fresh AND matches the agent claimed on the inner hop.

Worked example — upstream MCP → inner REST hop:

// === outer MCP server ===
import express from 'express';
import { createMcpMiddleware } from '@astrasyncai/verification-gateway/mcp';

const mcp = express();
mcp.use(express.json());
mcp.use(
  createMcpMiddleware({
    apiBaseUrl: 'https://astrasync.ai/api',
    apiKey: process.env.ASTRASYNC_API_KEY,
    counterpartyId: 'ASTRAE-mcp...',
    toolGates: { start_checkout: 'standard' },
  })
);

// MCP middleware sets X-Astra-Verified-Hop on the response automatically
// AND populates req.agentVerification. Tool handlers can read both.
mcp.post('/mcp', async (req, res) => {
  const v = req.agentVerification!;
  // Forward the verified-hop marker on outgoing inner-hop calls.
  // Build it from the same fields the middleware uses on the response.
  const { serializeVerifiedHop, MCP_VERIFIED_HOP_HEADER } =
    await import('@astrasyncai/verification-gateway/mcp');
  const hop = serializeVerifiedHop({
    astraId: v.agent!.astraId,
    sessionId: v.sessionId,
    checkedAt: Date.now(),
  });

  const inner = await fetch('http://internal/api/checkout/items', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Astra-Id': v.agent!.astraId, // identity marker
      [MCP_VERIFIED_HOP_HEADER]: hop, // verified-hop dedupe marker
    },
    body: JSON.stringify({ sku: 'sku_42' }),
  });
  res.status(inner.status).json(await inner.json());
});

// === inner REST hop (same fleet) ===
import { createMiddleware } from '@astrasyncai/verification-gateway/express';

const rest = express();
rest.use(
  createMiddleware({
    apiBaseUrl: 'https://astrasync.ai/api',
    apiKey: process.env.ASTRASYNC_API_KEY,
    counterpartyId: 'ASTRAE-rest...',
    trustVerifiedHop: true, // accept the dedupe marker
    verifiedHopMaxAgeMs: 60_000, // 60s default; tighten for high-stakes
  })
);
// Inner middleware reads X-Astra-Verified-Hop, validates via
// parseVerifiedHop + isVerifiedHopValidFor, and skips verify-access
// when the marker is fresh AND its astraId matches the X-Astra-Id on
// this request.

Important: the marker is NOT proof of identity by itself — pair it with X-Astra-Id so the inner middleware can verify the marker matches the claimed agent. The marker only gates the dedupe-skip decision.

Access Levels

| Level | Description | | ----------- | --------------------------------- | | none | No credentials provided | | guidance | Commerce Shield overlay shown | | read-only | Can browse, no mutations | | standard | Normal access per PDLSS | | full | Full access for high-trust agents | | internal | Organization member access |

Trust Levels

| Level | Score Range | | -------- | ----------- | | BRONZE | 0-39 | | SILVER | 40-59 | | GOLD | 60-79 | | PLATINUM | 80-100 |

UI Components

The package includes React components for displaying verification status:

import { CommerceShield, TrustLevelBadge, GuidanceCard } from '@astrasyncai/verification-gateway/ui';

// Commerce Shield overlay
<CommerceShield
  visible={!verified}
  result={verificationResult}
  onRegister={() => window.location.href = '/register'}
  allowGuestAccess={true}
/>

// Trust level badge
<TrustLevelBadge level="GOLD" score={75} />

// Guidance card
<GuidanceCard guidance={verificationResult.guidance} />

Credential Extraction

Agents can provide credentials via:

  1. Headers (recommended):

    • X-Astra-Id: Agent ASTRA-ID
    • X-Api-Key: API key
    • Authorization: Bearer <jwt>: JWT token
  2. Query Parameters (fallback):

    • ?astraId=ASTRA-xxx
    • ?apiKey=xxx

Verification Response

interface VerificationResult {
  verified: boolean;
  accessLevel: 'none' | 'guidance' | 'read-only' | 'standard' | 'full' | 'internal';

  agent?: {
    astraId: string;
    name: string;
    trustScore: number;
    trustLevel: 'BRONZE' | 'SILVER' | 'GOLD' | 'PLATINUM';
    blockchainVerified: boolean;
  };

  developer?: {
    astradId: string;
    verified: boolean;
  };

  organization?: {
    name: string;
    verified: boolean;
    trustScore: number;
  };

  pdlss?: {
    purposeAllowed: boolean;
    withinDuration: boolean;
    withinLimits: boolean;
    scopeAllowed: boolean;
    selfInstantiationAllowed: boolean;
  };

  guidance?: {
    message: string;
    registrationUrl: string;
    documentationUrl: string;
    steps?: string[];
  };

  denialReasons?: string[];
}

Configuration

interface GatewayConfig {
  // Required. Always include the /api path prefix — for prod use
  // 'https://astrasync.ai/api', for staging 'https://staging.astrasync.ai/api'.
  apiBaseUrl: string;

  // Optional
  apiKey?: string; // For authenticated requests
  defaultAccessLevel?: string; // Default: 'guidance'
  cacheTtl?: number; // Cache duration in seconds (default: 300)
  debug?: boolean; // Enable debug logging

  // Counterparty attribution (v2.2.3+)
  counterpartyUrl?: string; // Sent with verify-access for analytics
  counterpartyType?: 'agent' | 'api' | 'mcp_server' | 'website' | 'other' | 'unknown';
  counterpartyId?: string; // Your ASTRAE-id (issued at endpoint registration);
  // forwarded on every verify-access call so the server attributes traffic
  // directly to this endpoint rather than resolving by URL.

  // Init self-test (v2.2.3+) — fires a HEAD probe to verify-access on first
  // call and warns if apiBaseUrl is pointing at HTML (catches the marketing-
  // 404 case). Set true for tests where the extra request is undesirable.
  disableInitChecks?: boolean;

  // @deprecated — removed as functional config in v2.3.0. Server is the
  // single source of truth for access-level decisions; the SDK reads
  // access.accessLevel from the response verbatim. Setting these has no
  // effect (a one-shot console.warn fires). To gate access to your
  // endpoint, configure trust_score_requirement server-side via the
  // /api/endpoints registration.
  minTrustScore?: number;
  minTrustScoreForFull?: number;
}

Commerce Shield

When an unverified agent visits a protected page, the Commerce Shield overlay displays:

  • Registration guidance
  • Steps to get verified
  • Link to documentation
  • Optional guest access

This creates a smooth experience for agents while maintaining security.

Two baseUrl conventions — same package, different clients

This package ships two clients. They use slightly different URL conventions; this is intentional but worth pinning down:

| Client | Field | Expected value | Example | | ---------------------------------------------------------------------------- | -------------------------- | ----------------------- | -------------------------- | | Verification gateway (createMiddleware, createMcpMiddleware, verify()) | GatewayConfig.apiBaseUrl | Origin + /api | https://astrasync.ai/api | | Registration SDK (new AstraSync(...)) | AstraSyncConfig.baseUrl | Bare origin (no /api) | https://astrasync.ai |

Why the split: the gateway derives multiple endpoints from apiBaseUrl (verify-access, fetchRoutes, recordDecision, etc.), all under /api/*. The registration SDK prepends /api/agents/... per call and would double the path if baseUrl already ended with /api.

As of v2.4.2, the registration SDK tolerates a trailing /api on baseUrl — it strips the suffix and emits a one-time console warning so you can fix the source. Pass silent: true in AstraSyncConfig to suppress the warning (e.g. in tests).

Staging vs production

Both environments share the same URL shape (origin + /api for the verify gateway; bare origin for the registration SDK). Promotion is a single hostname swap:

| Environment | Verify gateway (apiBaseUrl) | Registration SDK (baseUrl) | | ----------- | ---------------------------------- | ------------------------------ | | Staging | https://staging.astrasync.ai/api | https://staging.astrasync.ai | | Production | https://astrasync.ai/api | https://astrasync.ai |

Path shape, request bodies, response schemas, error envelopes — all identical. Swap staging.astrasync.aiastrasync.ai, nothing else.

Header semantics — X-Astra-Gateway-Mode

The SDK sets X-Astra-Gateway-Mode on every response that's been through the gateway middleware. The value describes the GATE state — not whether the request succeeded end-to-end.

| Value | Meaning | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | enforced | The gateway evaluated policy on this request. Set on denial responses (defaultOnDenied, defaultMcpDenied). | | unenforced | The gateway skipped policy evaluation for this request. The X-Astra-Gateway-Reason header explains why (mcp-tier-none, non-jsonrpc-body, no-policy, no-match, route-none). The downstream handler may still return any status. |

Pre-v2.4.2 used the value pass-through — renamed in v2.4.2 to disambiguate "gate skipped" from "request allowed".

Changelog

v2.4.6 — Round-14 partner integration testing

⚠️ BREAKING CHANGE — endpointUrlcounterpartyUrl on POST /api/endpoints AND PUT /api/endpoints/{id}

The body field renamed to align with every other surface (verify-access, dashboard policy, SDK config — all use counterparty*). Applies to BOTH create (POST) and update (PUT) verbs:

  • POST /api/endpoints — request body uses counterpartyUrl (was endpointUrl).
  • PUT /api/endpoints/{id} — same. The strict-mode validator on both verbs returns a clean 400 unrecognized_keys naming counterpartyUrl as the expected key when partners send the old field name.
  • Response shape (GET /api/endpoints/{id}) also renamed for full symmetry — partners receive counterpartyUrl on read AND send counterpartyUrl on write. DB column endpoint_url stays as the internal join key; the service layer maps the public name to the DB column.

Migration: if your code posts endpointUrl to POST /api/endpoints OR PUT /api/endpoints/{id}, rename to counterpartyUrl. If your code reads .endpointUrl from GET responses, rename to .counterpartyUrl. No legacy alias is shipped — clean break per the feedback_hard_break_no_legacy_shim discipline.

Other round-14 items:

  • Item 1 — F8 runtime-challenge gate moved from SEND to RECOMMENDATION (agents.routes.ts). Round-12 placed the gate at the SEND (if (data.enableRuntimeChallenge && counterparty.requiresRuntimeChallenge)) which suppressed the challenge entirely on F8=optional, losing the trust-scoring data the result feeds. Round-14 moves the gate to the recommendation outcome:

    • challenge ALWAYS fires when enableRuntimeChallenge: true (captures trust-scoring data for every call; the result lands on the response body's runtimeChallenge block regardless of F8 setting)
    • failure / timeout only escalates recommendation (→ deny / step_up_required) when the endpoint declared requiresRuntimeChallenge: true

    Extracted into a pure helper deriveRuntimeChallengeRecommendation so the 6-quadrant truth table is unit-testable without the full verify-access stack. See /docs/agent-access/runtime-challenge for the partner-facing contract page (new in this round).

  • New /docs/agent-access/runtime-challenge page documenting the agent-side challenge contract — request shape, response shape, HTTP status semantics, signing posture (unsigned v1; DPoP RFC 9449 on the roadmap as the cryptographic upgrade path), timing, replay semantics. Headlines the ChallengeHandler drop-in from @astrasyncai/verification-gateway/agent; curl is the wire-spec fallback.

  • New /docs/mcp-integration "Purpose + action precedence" section documenting the 4-tier chain (header → _meta → arguments → default) that applies to both purpose and action. Closes the round-13 SDK-README- only documentation gap.

  • /docs/agent-access revisions: section 4b expanded to a dedicated "Headers an integrating agent must send" section covering X-Astra-Id, X-Astra-Purpose, X-Astra-Action (the last is new partner-facing documentation for the round-13 R13-2 header). Section 5 inverted tokenGuidance text corrected. New section 5b "What verify-access tells you" annotates the full grant payload with cross-links to the SDK's exported TypeScript types (VerificationResult, TokenGuidance, EnhancedVerificationResult).

  • /docs/merchants additions: worked endpoint POST + PUT examples using the new counterpartyUrl name; per-protocol terminal-status table with explicit external-contract preamble (the literals come from each protocol's published spec, not from AstraSync's choice) + auth/capture two-step callout for agent-pay and TAP; A2A JSON-RPC worked example.

  • F14 Health Check tooltip reframed as descriptive-only metadata ("active probe on roadmap") — the storage + serializer + dashboard UI exist but the active probe pipeline isn't implemented yet (deferred to a focused future round). Sets partner expectations correctly while the pipeline lands.

v2.4.5 — Round-13 partner integration testing

⚠️ BREAKING CHANGE — pdlss_immutableagent_immutable

The 409 response from PUT /api/agents/:id (post-mint mutation attempt) now returns error: 'agent_immutable' instead of error: 'pdlss_immutable'. The scope also widened: round-12 rejected only the subset { pdlss, model, framework, agentType, apiEndpoint }; round-13 rejects ANY field except agentStatus (the only allowed lifecycle transition post-mint).

Migration: if your code catches pdlss_immutable, update to agent_immutable. No legacy alias is shipped — clean break prevents permanent shim cruft. The new shape:

{
  "success": false,
  "error": "agent_immutable",
  "message": "Agents are immutable post-approval. ... Attempted immutable fields: <list>. ...",
  "immutableFields": ["name", "description", ...]
}

Why: agents become immutable at approval + mint per the trust-chain invariant. Pre-mint owner edits flow through POST /agents/request-registration/:requestId/approve (dashboard-only, accepts a full edit body). Post-mint, the only allowed transition is agentStatus (e.g. dashboard retire button). For configuration changes, use the upgrade flow (coming soon) or retire-and-re-register.

Other round-13 items:

  • R13-1 + R13-2 — MCP middleware: symmetric precedence chain for purpose and action. Canonical resolution (documented ONCE, applies to both):

    1. X-Astra-<concept> HTTP header
    2. params._meta.astrasync.<concept> body field
    3. params.arguments.<concept> body field
    4. Transport-layer default:
      • purpose'mcp_invoke'
      • action'<method>:<toolName>' (or '<method>' alone)

    Round-12 F19 shipped purpose with header → _meta → default; this round closes the params.arguments.purpose fallback gap AND ships action with the same full chain in one round (not staggered) to pre-empt the parallel "I set action in arguments and it didn't take" support tickets. Resource string stays mcp:tool/<name> regardless.

    mcpToPdlss(parsed, headerPurpose, headerAction) signature. McpPdlssMapping.purposeSource now 'header' | 'meta' | 'tool_argument' | 'default_mcp_invoke' (round-12 narrower 'header' | 'tool_argument' | 'default_mcp_invoke' widened to split meta from tool_argument). New companion actionSource: 'header' | 'meta' | 'tool_argument' | 'transport_layer'.

  • R13-5 — MCP evaluateAlwaysIfCredentialed parity with F9. Flag moved from ExpressMiddlewareOptions to GatewayConfig so both adapters inherit. MCP middleware now mirrors the express F9 pattern: route-none + flag-on + credentialed → run verify-access for the audit trail, populate req.agentVerification, then proceed without gates (X-Astra-Gateway-Mode: enforced, Reason: evaluated-not-enforced). Closes the round-12 deferral.

  • F14 closure — sdkVersion body field on verify-access. Replaces round-12's User-Agent regex extraction which silently failed because Node's undici fetch doesn't ship a usable User-Agent header. The SDK now sets body.sdkVersion = SDK_VERSION (sourced from packages/verification-gateway/src/version.ts, bumped alongside package.json on every release). Backend reads from the body field and runs the same forward-only auto-pop into kya_counterparty.sdk_version. Works in Node, browser, and behind CDNs uniformly.

  • R13-4 — Branded TypeScript types (compile-time protection against the recurring UUID / public-id string-confusion bug class — round-7 #46, round-11 F1, round-12 F15). New CounterpartyUuid, AgentUuid, OwnerUuid, CounterpartyAstraeId, AgentAstraId, OwnerAstradId branded types in the backend at apps/backend/src/types/branded-ids.ts. Zero runtime cost; affects only compile-time assignment compatibility. Scope intentionally narrow — only the conversion-point function signatures.

v2.4.4 — Round-12 partner integration testing

  • F9ExpressMiddlewareOptions.evaluateAlwaysIfCredentialed: when true + credentials present + route-none, the middleware calls verify-access for the audit trail + req.agentVerification population, then proceeds without enforcement. Default false preserves existing behaviour. Use for tiered-response rendering on routes that grant public access but want caller identity visible to the handler.
  • F12defaultOnDenied / defaultMcpDenied synthesise access_level.insufficient failure entry on accessLevel-below-route + trust-score-below-route denials. Guidance text references the step-up verification flow ("coming soon — ships this month") only. Prior denials carried INSUFFICIENT_ACCESS with empty failures[] / denialReasons[] arrays.
  • F16register() now passes the API response's warnings[] through verbatim on both 201 (sync) and 202 (pending-approval) paths. Pre-fix the SDK silently dropped backend advisories like no_callback_endpoint. RegisterResult + PendingRegistrationResponse + RegistrationResponse types extended.
  • F19 — MCP middleware purpose pass-through. The hardcoded purpose: 'mcp_invoke' is now a fallback; resolution precedence is X-Astra-Purpose header → params._meta.astrasync.purpose'mcp_invoke' default. Adds invocationProtocol: 'mcp' to the verify-access body so transport is marked separately from intent. Debug-level purpose_source log line per call for adoption tracking + support triage.
  • mcpToPdlss signature extended to accept optional headerPurpose + toolArgumentPurpose args; return type gains purposeSource: 'header' | 'tool_argument' | 'default_mcp_invoke'.

v2.4.3 — Round-11 partner integration testing

  • PollRegistrationResult.astraId — the polling response now surfaces the canonical ASTRA-* id once the registration request is approved. Pre-fix partners only got the owner-private UUID from the register flow and had no programmatic path to the canonical id (had to go to the dashboard). Backend changes that drive this: the GET /api/agents/request-registration/{requestId} handler looks up kyaAgent.astrasyncIdLevel1 on approval and includes it in the response body.
  • No SDK code changes — pure type widening. Existing 2.4.2 callers see the same state / agent / reason shape plus the new optional astraId field. TypeScript surfaces the new field at every sdk.pollRegistration(...) call site.
  • Pairs with backend round-11 F2: the public verify-access surface now refuses UUID-form agentId with a 400 INVALID_AGENT_ID. After F3 partners can discover the canonical astraId post-approval; F2 is what they pass to verify-access from there.

v2.4.2 — Round-10 partner re-test alignment

  • baseUrl normalization (AstraSyncConfig): trailing /api is stripped + warned. Removes a class of NOT_FOUND bugs when partners pass the verify-gateway-style URL to the registration client.
  • Denial responses surface failures[] + correlationId (defaultOnDenied, defaultMcpDenied): partners can now render per-dimension UX and tie a denial back to a server log line. Previously only the first denialReason was surfaced.
  • API-error fallback (createGuidanceResponse): synthesised stubs for transient 5xx / network failures now carry verify_access.api_error in failures[] instead of the misleading "register your agent" template.
  • X-Astra-Gateway-Mode rename: pass-throughunenforced. The new enforced value lands on denial paths. See the header semantics table above.
  • silent config flag (AstraSyncConfig): suppresses one-time SDK warnings (baseUrl strip, deprecation notices).

v2.4.1 — Round-9 docs/SDK alignment

See git log for details.

License

MIT