TypeScript SDK
Validate, protect, exchange and open Supply-Y Packages with one executable reference package.
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.
Current status
The source package is available in packages/typescript-sdk. It is a publish-ready preview package, not yet a generally available npm release. From a repository checkout:
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.
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.
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.
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.
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.
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.
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.