# TypeScript SDK

The reference SDK turns the `supply-y/1.0` schemas, Native cryptographic profile and Agent API contract into one executable TypeScript package. It is intended for company-controlled Agents and Jenae adapters running on Node.js 20 or newer. The validation and cryptographic functions also use browser-standard Web Crypto primitives. Production private-key operations stay behind customer-implemented `senderSigner` and `recipientDecrypter` adapters.

To let an Agent prepare this integration, use the guarded prompt on [Install Supply-Y In Your Agent](/docs/agent-install).

## Current status

The source package is available in [`packages/typescript-sdk`](https://github.com/supplywhy-ai/supply-y/tree/main/packages/typescript-sdk). It is a publish-ready preview package, not yet a generally available npm release. From a repository checkout:

```bash
npm install
npm run validate:sdk
npm pack --workspace @supplywhy/supply-y-sdk
```

The release gate builds declarations and JavaScript, validates all 24 object fixtures, 15 thread-context cases, 14 publisher lifecycle decisions and 18 Skill-update decisions, opens the committed Native vector, creates and reopens a fresh two-recipient Package, checks local and KMS-adapter rejection paths, and exercises all 21 OpenAPI operations through a mock transport.

## 1. Validate locally

Validation runs before encryption and returns exact Schema or Profile findings. No object data is sent to Supply-Y.

```ts
import { validateProtocolObject } from "@supplywhy/supply-y-sdk";

const result = validateProtocolObject(reasoningPackage);
if (!result.valid) {
  for (const finding of result.findings) {
    console.error(finding.source, finding.instancePath, finding.message);
  }
  throw new Error("Package is not safe to exchange");
}
```

The SDK uses the same generated seven-schema set and domain profile as the browser Playground. The generator runs during the SDK build so schema drift fails the repository release gate.

## 2. Validate the complete thread context

Object validation answers whether each object is well formed. `validateThreadContext` answers whether the objects agree with one another: the Thread chains resolve, the Network Story includes the current Package and Response, the Response points to a current Package, and every evidence ID resolves in the customer's local index.

```ts
import { validateThreadContext } from "@supplywhy/supply-y-sdk";

const context = validateThreadContext({
  thread,
  story,
  objects: [reasoningPackage, responseObject],
  evidenceRecords: customerLocalEvidenceIndex.records
});

if (!context.valid) {
  for (const finding of context.findings) {
    console.error(finding.keyword, finding.instancePath, finding.message);
  }
  throw new Error("Thread context cannot be accepted");
}
```

The local evidence index stores only an evidence ID, owner, observation time and RFC 9530 digest. Raw ERP, MES, QMS or document content remains behind the customer's controls. This function performs no network request and sends neither the index nor the underlying evidence to Supply-Y.

## 3. Verify and evaluate a Skill update

`verifySkillRelease` validates the raw release and artifact against the generated contracts, checks the requested coordinate and contract references, computes the canonical digest, resolves the signing key only from the customer's trusted publisher set and verifies the ES256 signature. `evaluateSkillUpdate` then combines those facts with protocol, dependency and capability checks to return the deterministic lifecycle decision.

```ts
import {
  evaluateSkillUpdate,
  verifySkillRelease
} from "@supplywhy/supply-y-sdk";

const verified = await verifySkillRelease({
  release,
  artifact,
  trusted_publisher_keys: customerApprovedSkillKeys,
  trust_context: {
    purpose: "production",
    observed_at: new Date().toISOString()
  },
  expected: { skill_id: "supply-y.material-risk", version: "0.1.1" }
});

if (!verified.valid || !verified.release) {
  throw new Error(JSON.stringify(verified.checks));
}

const update = evaluateSkillUpdate({
  installed: { version: "0.1.0", status: "active" },
  candidate: {
    version: verified.release.version,
    release_channel: verified.release.release_channel,
    release_contract_valid: verified.checks.release_contract_valid,
    artifact_contract_valid: verified.checks.artifact_contract_valid,
    protocol_compatible: true,
    publisher_trusted: verified.checks.publisher_trusted,
    signature_valid: verified.checks.signature_valid,
    digest_valid: verified.checks.digest_valid,
    dependencies_resolved: true,
    capability_expansion: false,
    update_policy: verified.release.update_policy
  }
});

if (update.decision === "download_for_approval") {
  await cacheVerifiedArtifact();
  await requestOperatorApproval();
}
```

`verifySkillRelease` performs no network request and never accepts a key supplied by the release itself. It validates the public key record, checks release and observation times, rejects preview trust in production, applies retirement or revocation cutoffs and verifies the signature only after publisher trust passes. The Agent controls the trusted-key set and must still derive protocol compatibility, dependency resolution and capability expansion from local state.

When verifying an older release after a key cutoff, `trust_context.historical_acceptance` must come from the customer's own immutable audit record. It contains `accepted_at` and the SDK-computed digest of the complete signed release. The SDK accepts that evidence only when its timestamp falls between release publication and key cutoff and its digest matches exactly. Supply-Y cannot create this local proof for the Agent. A `download_for_approval` result allows caching only; it does not authorize activation.

## 4. Protect a Native Package

`protectNativePackage` validates the object, verifies that readable delivery metadata matches it, computes the RFC 9530 digest, canonicalizes with RFC 8785, encrypts once with AES-256-GCM, wraps the data key for sender and recipient with P-256 ECDH, then signs the canonical exchange envelope with ES256.

```ts
import {
  protectNativePackage,
  type NativePackageSigner
} from "@supplywhy/supply-y-sdk";

const senderSigner: NativePackageSigner = {
  kid: signingKeyId,
  algorithm: "ES256",
  sign: ({ payload, protectedHeader }) => companyKms.signFlattenedJws({
    keyId: signingKeyId,
    algorithm: "ES256",
    payload,
    protectedHeader
  })
};

const { signedExchange, metadata } = await protectNativePackage({
  object: reasoningPackage,
  metadata: {
    package_id: reasoningPackage.id,
    thread_id: reasoningPackage.thread_id,
    sender_org_id: "org_tier3_material",
    sender_agent_id: "agent_material_risk",
    recipient_org_id: "org_tier2_connector",
    recipient_agent_id: "agent_component_planning",
    protocol_version: "supply-y/1.0",
    schema_version: "0.1.0",
    skill_id: "supply-y.material-risk",
    skill_version: "0.1.0",
    created_at: reasoningPackage.created_at,
    expires_at: "2026-08-02T18:00:00Z",
    content_type: "application/supply-y.reasoning-package+json",
    policy_receipt_id: "polrec_material_risk_family_level_001"
  },
  senderSigner,
  senderEncryptionKey: { kid: senderEncryptionKeyId, publicJwk: senderEncryptionJwk },
  recipientEncryptionKey: { kid: recipientEncryptionKeyId, publicJwk: recipientEncryptionJwk }
});
```

`companyKms.signFlattenedJws` represents the customer's adapter, not a Supply-Y service. It signs the exact payload and protected header with the configured non-exportable key. The SDK rejects a returned JWS if the adapter changes the payload, key ID, algorithm or any protected header. The legacy `senderSigningKey.privateJwk` input remains available for local conformance vectors and development only.

## 5. Send through the Agent API

The API client adds bearer authentication, a correlation ID and mutation idempotency. The caller supplies a short-lived access-token provider and may inject its own standards-compatible `fetch` implementation.

```ts
import { SupplyYClient } from "@supplywhy/supply-y-sdk";

const client = new SupplyYClient({
  baseUrl: process.env.SUPPLY_Y_API_URL!,
  accessToken: () => oauthClient.getAccessToken()
});

const policyResult = await client.createPolicyReceipt(
  signedPolicyReceipt,
  `${reasoningPackage.trace.idempotency_key}:policy`
);

if (policyResult.package_authorization !== "eligible") {
  throw new Error("Local policy claim did not authorize this Package");
}

const accepted = await client.createPackageTransfer(
  signedExchange,
  reasoningPackage.trace.idempotency_key
);
```

`signedPolicyReceipt` is created locally and binds the exact Package ID, encrypted-content digest, parties, Skill, policy digest, five required checks and any human approval. The client exposes all 21 operations in the published OpenAPI contract, including Policy Receipt creation and retrieval. It does not hide durable Package states: a `202` means accepted for processing, not opened by the recipient.

## 6. Verify and open as recipient

The receiver verifies the sender signature and protected headers before decryption. It then checks recipient identity, expiry, JWE profile, authenticated metadata, content digest, canonicalization, JSON Schema and domain rules.

```ts
import {
  openNativePackage,
  type NativePackageDecrypter
} from "@supplywhy/supply-y-sdk";

const recipientDecrypter: NativePackageDecrypter = {
  kid: recipientEncryptionKeyId,
  algorithm: "ECDH-ES+A256KW",
  decrypt: ({ jwe, recipientKeyId }) => companyHsm.decryptSupplyYJwe({
    keyId: recipientKeyId,
    jwe
  })
};

const opened = await openNativePackage({
  signedExchange,
  senderSigningPublicJwk,
  recipientDecrypter,
  expectedRecipient: {
    organizationId: "org_tier2_connector",
    agentId: "agent_component_planning",
    encryptionKeyId: recipientEncryptionKeyId
  }
});

const verifiedObject = opened.object;
```

The decrypter returns plaintext bytes only after the customer HSM or KMS unwraps the matching recipient entry. The SDK independently verifies the two-recipient JWE shape, allowlisted algorithms, key ID, authenticated metadata, content digest, canonicalization and protocol object. A substituted plaintext therefore fails even when it came from the configured adapter. The legacy `recipientEncryptionPrivateJwk` input is test-only convenience.

Incoming Package content remains untrusted business data. Successful cryptography does not make its text an instruction, authorize tools, prove business truth or bypass the installed Skill's local approval policy.

## 7. Handle errors deliberately

`SupplyYProtocolError` is raised before or during local protection and opening. Its stable `code` distinguishes invalid objects, metadata mismatch, expiry, recipient mismatch and cryptographic failure. Validation errors also include exact findings.

`SupplyYApiError` preserves the RFC 9457 status, protocol code, correlation ID and retryability returned by the service. Retry only when `retryable` is true, and always reuse the original idempotency key for the same mutation.

## What the SDK does not do

- It does not connect to ERP, MES, QMS or PLM systems.
- It does not decide what the company is permitted to disclose.
- It does not hold customer private keys or replace a KMS or HSM adapter.
- It does not execute incoming Package text as Agent instructions.
- It does not prove a hosted Supply-Y endpoint or production SLA is available.
- It does not certify the surrounding Agent implementation.

Those responsibilities remain with the customer Agent, installed Skill, customer security controls and the production compatibility process.

---

Canonical HTML: [TypeScript SDK](https://supply-y.net/docs/sdk)
Agent documentation index: [llms.txt](https://supply-y.net/llms.txt)
