@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-accessfor verification,POST /agents/registerfor 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-gatewayQuick 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 (
privateKeyconfigured) 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.
/registrationis 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,/agentis runtime.
A
PDLSSConfigtype exists in both/registrationand/agent, with different shapes./registration'sPDLSSConfigis the boundary declaration submitted at register time;/agent'sPDLSSConfigis 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:
Headers (recommended):
X-Astra-Id: Agent ASTRA-IDX-Api-Key: API keyAuthorization: Bearer <jwt>: JWT token
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.ai ↔ astrasync.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 — endpointUrl → counterpartyUrl 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 usescounterpartyUrl(wasendpointUrl).PUT /api/endpoints/{id}— same. The strict-mode validator on both verbs returns a clean 400unrecognized_keysnamingcounterpartyUrlas the expected key when partners send the old field name.- Response shape (
GET /api/endpoints/{id}) also renamed for full symmetry — partners receivecounterpartyUrlon read AND sendcounterpartyUrlon write. DB columnendpoint_urlstays 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'sruntimeChallengeblock regardless of F8 setting) - failure / timeout only escalates
recommendation(→deny/step_up_required) when the endpoint declaredrequiresRuntimeChallenge: true
Extracted into a pure helper
deriveRuntimeChallengeRecommendationso the 6-quadrant truth table is unit-testable without the full verify-access stack. See/docs/agent-access/runtime-challengefor the partner-facing contract page (new in this round).- challenge ALWAYS fires when
New
/docs/agent-access/runtime-challengepage 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 theChallengeHandlerdrop-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-accessrevisions: section 4b expanded to a dedicated "Headers an integrating agent must send" section coveringX-Astra-Id,X-Astra-Purpose,X-Astra-Action(the last is new partner-facing documentation for the round-13 R13-2 header). Section 5 invertedtokenGuidancetext 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/merchantsadditions: worked endpoint POST + PUT examples using the newcounterpartyUrlname; 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_immutable → agent_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
purposeandaction. Canonical resolution (documented ONCE, applies to both):X-Astra-<concept>HTTP headerparams._meta.astrasync.<concept>body fieldparams.arguments.<concept>body field- Transport-layer default:
purpose→'mcp_invoke'action→'<method>:<toolName>'(or'<method>'alone)
Round-12 F19 shipped purpose with
header → _meta → default; this round closes theparams.arguments.purposefallback 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 staysmcp:tool/<name>regardless.mcpToPdlss(parsed, headerPurpose, headerAction)signature.McpPdlssMapping.purposeSourcenow'header' | 'meta' | 'tool_argument' | 'default_mcp_invoke'(round-12 narrower'header' | 'tool_argument' | 'default_mcp_invoke'widened to splitmetafromtool_argument). New companionactionSource: 'header' | 'meta' | 'tool_argument' | 'transport_layer'.R13-5 — MCP
evaluateAlwaysIfCredentialedparity with F9. Flag moved fromExpressMiddlewareOptionstoGatewayConfigso both adapters inherit. MCP middleware now mirrors the express F9 pattern: route-none + flag-on + credentialed → run verify-access for the audit trail, populatereq.agentVerification, then proceed without gates (X-Astra-Gateway-Mode: enforced,Reason: evaluated-not-enforced). Closes the round-12 deferral.F14 closure —
sdkVersionbody 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 setsbody.sdkVersion = SDK_VERSION(sourced frompackages/verification-gateway/src/version.ts, bumped alongsidepackage.jsonon every release). Backend reads from the body field and runs the same forward-only auto-pop intokya_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,OwnerAstradIdbranded types in the backend atapps/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
- F9 —
ExpressMiddlewareOptions.evaluateAlwaysIfCredentialed: when true + credentials present + route-none, the middleware calls verify-access for the audit trail +req.agentVerificationpopulation, 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. - F12 —
defaultOnDenied/defaultMcpDeniedsynthesiseaccess_level.insufficientfailure 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 carriedINSUFFICIENT_ACCESSwith emptyfailures[]/denialReasons[]arrays. - F16 —
register()now passes the API response'swarnings[]through verbatim on both 201 (sync) and 202 (pending-approval) paths. Pre-fix the SDK silently dropped backend advisories likeno_callback_endpoint.RegisterResult+PendingRegistrationResponse+RegistrationResponsetypes extended. - F19 — MCP middleware purpose pass-through. The hardcoded
purpose: 'mcp_invoke'is now a fallback; resolution precedence isX-Astra-Purposeheader →params._meta.astrasync.purpose→'mcp_invoke'default. AddsinvocationProtocol: 'mcp'to the verify-access body so transport is marked separately from intent. Debug-levelpurpose_sourcelog line per call for adoption tracking + support triage. mcpToPdlsssignature extended to accept optionalheaderPurpose+toolArgumentPurposeargs; return type gainspurposeSource: 'header' | 'tool_argument' | 'default_mcp_invoke'.
v2.4.3 — Round-11 partner integration testing
PollRegistrationResult.astraId— the polling response now surfaces the canonicalASTRA-*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: theGET /api/agents/request-registration/{requestId}handler looks upkyaAgent.astrasyncIdLevel1on 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/reasonshape plus the new optionalastraIdfield. TypeScript surfaces the new field at everysdk.pollRegistration(...)call site. - Pairs with backend round-11 F2: the public verify-access surface now refuses UUID-form
agentIdwith a400 INVALID_AGENT_ID. After F3 partners can discover the canonicalastraIdpost-approval; F2 is what they pass to verify-access from there.
v2.4.2 — Round-10 partner re-test alignment
- baseUrl normalization (
AstraSyncConfig): trailing/apiis 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 carryverify_access.api_errorinfailures[]instead of the misleading "register your agent" template. X-Astra-Gateway-Moderename:pass-through→unenforced. The newenforcedvalue lands on denial paths. See the header semantics table above.silentconfig 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
