{"_id":"@aurite-ai/kahuna-sdk","_rev":"5-944bf8fd512b9ec4ca0ed2272d52b09a","name":"@aurite-ai/kahuna-sdk","dist-tags":{"latest":"0.4.0"},"versions":{"0.1.0":{"name":"@aurite-ai/kahuna-sdk","version":"0.1.0","keywords":["kahuna","spiffe","spire","mtls","governance","agent","authorization"],"license":"MIT","_id":"@aurite-ai/kahuna-sdk@0.1.0","maintainers":[{"name":"wilcoxr","email":"wilcoxryan26@gmail.com"},{"name":"dreamwvr","email":"terry@weaversoftware.dev"},{"name":"jitenoswal","email":"jiten.p.oswal@gmail.com"}],"homepage":"https://github.com/Aurite-ai/kahuna-runtime#readme","bugs":{"url":"https://github.com/Aurite-ai/kahuna-runtime/issues"},"dist":{"shasum":"62ffddbebe4a0b0f731a871795d57d75f1bf5004","tarball":"https://registry.npmjs.org/@aurite-ai/kahuna-sdk/-/kahuna-sdk-0.1.0.tgz","fileCount":27,"integrity":"sha512-RQwFQGx8ZJTqcDso+tujTFlnxNbBCAJ9DkzVXef/3i06NSK8W4pOVS0Ny+xMh7d3hiAVFrXRFLJcHFF/L1ZLdw==","signatures":[{"sig":"MEUCIQC/l2Ois5etm0qYfDxu1dVFkceeksNWLh0lkwDwJaiJSwIgSu8DjC0kP+Zqu/5szNcOxQSF3aPa2dkay26yx0BvdIk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":94451},"main":"./dist/index.js","type":"module","_from":"file:/Users/jitenoswal/Downloads/aurite-ai-kahuna-sdk-0.1.0.tgz","types":"./dist/index.d.ts","engines":{"node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"scripts":{"test":"vitest run","build":"tsc && esbuild src/index.ts --bundle --platform=node --format=esm --target=node20 --outfile=dist/index.js --external:json-canon","clean":"rm -rf dist","test:unit":"vitest run --passWithNoTests","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"jitenoswal","email":"jiten.p.oswal@gmail.com"},"_resolved":"/Users/jitenoswal/Downloads/aurite-ai-kahuna-sdk-0.1.0.tgz","_integrity":"sha512-RQwFQGx8ZJTqcDso+tujTFlnxNbBCAJ9DkzVXef/3i06NSK8W4pOVS0Ny+xMh7d3hiAVFrXRFLJcHFF/L1ZLdw==","repository":{"url":"git+https://github.com/Aurite-ai/kahuna-runtime.git","type":"git","directory":"packages/agent-sdk"},"_npmVersion":"10.9.8","description":"Governance SDK for Kahuna — SPIFFE identity, mTLS, and fail-closed action authorization.","directories":{},"_nodeVersion":"22.22.3","dependencies":{"json-canon":"^1.0.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^1.0.0","esbuild":"^0.23.1","typescript":"^5.0.0","@types/node":"^20.0.0","@kahuna/shared-types":"0.0.0","@kahuna/shared-transport":"0.0.0"},"peerDependencies":{"@types/node":"^20.0.0"},"_npmOperationalInternal":{"tmp":"tmp/kahuna-sdk_0.1.0_1786871186214_0.42890769375621396","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@aurite-ai/kahuna-sdk","version":"0.1.1","keywords":["kahuna","spiffe","spire","mtls","governance","agent","authorization"],"license":"MIT","_id":"@aurite-ai/kahuna-sdk@0.1.1","maintainers":[{"name":"wilcoxr","email":"wilcoxryan26@gmail.com"},{"name":"dreamwvr","email":"terry@weaversoftware.dev"},{"name":"jitenoswal","email":"jiten.p.oswal@gmail.com"}],"homepage":"https://github.com/Aurite-ai/kahuna-runtime#readme","bugs":{"url":"https://github.com/Aurite-ai/kahuna-runtime/issues"},"dist":{"shasum":"40cbc13d723a77a2f92c38ad21ee48dc29dde127","tarball":"https://registry.npmjs.org/@aurite-ai/kahuna-sdk/-/kahuna-sdk-0.1.1.tgz","fileCount":27,"integrity":"sha512-y8Z1gCy0Kii65Rg3ztbbTtunNo5ucsH6yccCMCLzeM7nL+amSKjsmU8UGnQPMLQy5PGl2/sVqdLY2Sw2E9k9Ag==","signatures":[{"sig":"MEYCIQD3QuUJS3J+CoPXbrkmbsW4HLuD4tGI5tZVL4P02lFeLAIhAKKDLfiDVTB4vo4HikOwwtnbNGp6yEiTFxAUe5/XKdRF","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":96256},"main":"./dist/index.js","type":"module","_from":"file:/home/runner/work/_temp/aurite-ai-kahuna-sdk-0.1.1.tgz","types":"./dist/index.d.ts","engines":{"node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"scripts":{"test":"vitest run","build":"tsc && esbuild src/index.ts --bundle --platform=node --format=esm --target=node20 --outfile=dist/index.js --external:json-canon","clean":"rm -rf dist","test:unit":"vitest run --passWithNoTests","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:2ff5c690-0217-4105-80e8-935ffba6af36"}},"_resolved":"/home/runner/work/_temp/aurite-ai-kahuna-sdk-0.1.1.tgz","_integrity":"sha512-y8Z1gCy0Kii65Rg3ztbbTtunNo5ucsH6yccCMCLzeM7nL+amSKjsmU8UGnQPMLQy5PGl2/sVqdLY2Sw2E9k9Ag==","repository":{"url":"git+https://github.com/Aurite-ai/kahuna-runtime.git","type":"git","directory":"packages/agent-sdk"},"_npmVersion":"12.0.2","description":"Governance SDK for Kahuna — SPIFFE identity, mTLS, and fail-closed action authorization.","directories":{},"_nodeVersion":"22.23.2","dependencies":{"json-canon":"^1.0.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^1.0.0","esbuild":"^0.23.1","typescript":"^5.0.0","@types/node":"^20.0.0","@kahuna/shared-types":"0.0.0","@kahuna/shared-transport":"0.0.0"},"peerDependencies":{"@types/node":"^20.0.0"},"_npmOperationalInternal":{"tmp":"tmp/kahuna-sdk_0.1.1_1786874851904_0.9945547083311777","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@aurite-ai/kahuna-sdk","version":"0.2.0","keywords":["kahuna","spiffe","spire","mtls","governance","agent","authorization"],"license":"MIT","_id":"@aurite-ai/kahuna-sdk@0.2.0","maintainers":[{"name":"wilcoxr","email":"wilcoxryan26@gmail.com"},{"name":"dreamwvr","email":"terry@weaversoftware.dev"},{"name":"jitenoswal","email":"jiten.p.oswal@gmail.com"}],"homepage":"https://github.com/Aurite-ai/kahuna-runtime#readme","bugs":{"url":"https://github.com/Aurite-ai/kahuna-runtime/issues"},"dist":{"shasum":"9e836f13ba3d1973ab9b9aa617a37ff66672bc8d","tarball":"https://registry.npmjs.org/@aurite-ai/kahuna-sdk/-/kahuna-sdk-0.2.0.tgz","fileCount":31,"integrity":"sha512-+45/7WptwqlfLv8d+88I9DrOSpAhHPfHUQEcodA6pS+xpStWfKAo5TVIBJg/1UdVI8a2uXaSvc6pt+NlmzpNaA==","signatures":[{"sig":"MEYCIQDcpaAdohL+UV1JSjCHNUmLe5Rw/zM2JgVbr9FAUNJ9YQIhAJnwqy/4oOl8GOCerNFpycgYvYLzEVUMRyNO7ZLSSYDQ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":279652},"main":"./dist/index.js","type":"module","_from":"file:/home/runner/work/_temp/aurite-ai-kahuna-sdk-0.2.0.tgz","types":"./dist/index.d.ts","engines":{"node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"scripts":{"test":"vitest run","build":"tsc && esbuild src/index.ts --bundle --platform=node --format=esm --target=node20 --outfile=dist/index.js --external:json-canon","clean":"rm -rf dist","test:unit":"vitest run --passWithNoTests","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:2ff5c690-0217-4105-80e8-935ffba6af36"}},"_resolved":"/home/runner/work/_temp/aurite-ai-kahuna-sdk-0.2.0.tgz","_integrity":"sha512-+45/7WptwqlfLv8d+88I9DrOSpAhHPfHUQEcodA6pS+xpStWfKAo5TVIBJg/1UdVI8a2uXaSvc6pt+NlmzpNaA==","repository":{"url":"git+https://github.com/Aurite-ai/kahuna-runtime.git","type":"git","directory":"packages/agent-sdk"},"_npmVersion":"12.0.2","description":"Governance SDK for Kahuna — SPIFFE identity, mTLS, and fail-closed action authorization.","directories":{},"_nodeVersion":"22.23.2","dependencies":{"json-canon":"^1.0.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^1.0.0","esbuild":"^0.23.1","typescript":"^5.0.0","@types/node":"^20.0.0","@kahuna/shared-types":"0.0.0","@kahuna/shared-transport":"0.0.0"},"peerDependencies":{"@types/node":"^20.0.0"},"_npmOperationalInternal":{"tmp":"tmp/kahuna-sdk_0.2.0_1787009153956_0.5551425805795738","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@aurite-ai/kahuna-sdk","version":"0.3.0","keywords":["kahuna","spiffe","spire","mtls","governance","agent","authorization"],"license":"MIT","_id":"@aurite-ai/kahuna-sdk@0.3.0","maintainers":[{"name":"wilcoxr","email":"wilcoxryan26@gmail.com"},{"name":"dreamwvr","email":"terry@weaversoftware.dev"},{"name":"jitenoswal","email":"jiten.p.oswal@gmail.com"}],"homepage":"https://github.com/Aurite-ai/kahuna-runtime#readme","bugs":{"url":"https://github.com/Aurite-ai/kahuna-runtime/issues"},"dist":{"shasum":"05ec5f8eafdab2f96956c836a245ce5caf3c102c","tarball":"https://registry.npmjs.org/@aurite-ai/kahuna-sdk/-/kahuna-sdk-0.3.0.tgz","fileCount":31,"integrity":"sha512-I78wmSGQ5zi4rtwXeeTzgw/lHJQimYkxeqc5A3Cj9x242Ay7In0N+BZ3LdS03eHrwkt9bu+CUNOHv0PrFGKR5A==","signatures":[{"sig":"MEUCIDw2ndZPmzg3uwzDiiD13p4iFwhh7ym/hpLszqa5C7q7AiEAyp1tPC1BtKNXt0UMwmyckWqdv1E50DqFz71tF9IZA+A=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":285214},"main":"./dist/index.js","type":"module","_from":"file:/home/runner/work/_temp/aurite-ai-kahuna-sdk-0.3.0.tgz","types":"./dist/index.d.ts","engines":{"node":">=20.0.0"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"scripts":{"test":"vitest run","build":"tsc && esbuild src/index.ts --bundle --platform=node --format=esm --target=node20 --outfile=dist/index.js --external:json-canon","clean":"rm -rf dist","test:unit":"vitest run --passWithNoTests","typecheck":"tsc --noEmit","test:watch":"vitest"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:2ff5c690-0217-4105-80e8-935ffba6af36"}},"_resolved":"/home/runner/work/_temp/aurite-ai-kahuna-sdk-0.3.0.tgz","_integrity":"sha512-I78wmSGQ5zi4rtwXeeTzgw/lHJQimYkxeqc5A3Cj9x242Ay7In0N+BZ3LdS03eHrwkt9bu+CUNOHv0PrFGKR5A==","repository":{"url":"git+https://github.com/Aurite-ai/kahuna-runtime.git","type":"git","directory":"packages/agent-sdk"},"_npmVersion":"12.0.2","description":"Governance SDK for Kahuna — SPIFFE identity, mTLS, and fail-closed action authorization.","directories":{},"_nodeVersion":"22.23.2","dependencies":{"json-canon":"^1.0.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^1.0.0","esbuild":"^0.23.1","typescript":"^5.0.0","@types/node":"^20.0.0","@kahuna/shared-types":"0.0.0","@kahuna/shared-transport":"0.0.0"},"peerDependencies":{"@types/node":"^20.0.0"},"_npmOperationalInternal":{"tmp":"tmp/kahuna-sdk_0.3.0_1787012869885_0.06675374623673425","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@aurite-ai/kahuna-sdk","version":"0.4.0","description":"Governance SDK for Kahuna — SPIFFE identity, mTLS, and fail-closed action authorization.","type":"module","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"keywords":["kahuna","spiffe","spire","mtls","governance","agent","authorization"],"license":"MIT","peerDependencies":{"@types/node":"^20.0.0"},"devDependencies":{"@types/node":"^20.0.0","esbuild":"^0.23.1","typescript":"^5.0.0","vitest":"^1.0.0","@kahuna/shared-transport":"0.0.0","@kahuna/shared-types":"0.0.0"},"engines":{"node":">=20.0.0"},"dependencies":{"json-canon":"^1.0.1"},"publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/Aurite-ai/kahuna-runtime.git","directory":"packages/agent-sdk"},"scripts":{"build":"tsc && esbuild src/index.ts --bundle --platform=node --format=esm --target=node20 --outfile=dist/index.js --external:json-canon","test":"vitest run","test:unit":"vitest run --passWithNoTests","test:watch":"vitest","typecheck":"tsc --noEmit","clean":"rm -rf dist"},"_id":"@aurite-ai/kahuna-sdk@0.4.0","bugs":{"url":"https://github.com/Aurite-ai/kahuna-runtime/issues"},"homepage":"https://github.com/Aurite-ai/kahuna-runtime#readme","_integrity":"sha512-QksIa6m4yxPR3cPRygoxLk6XpTtp9d3N8A6Zx1lFJ2rjQrGjVAKcCkMn+1o3IBZW7xhGyMezwUe6bUK9KOWQ7A==","_resolved":"/home/runner/work/_temp/aurite-ai-kahuna-sdk-0.4.0.tgz","_from":"file:/home/runner/work/_temp/aurite-ai-kahuna-sdk-0.4.0.tgz","_nodeVersion":"22.23.2","_npmVersion":"12.0.2","dist":{"integrity":"sha512-QksIa6m4yxPR3cPRygoxLk6XpTtp9d3N8A6Zx1lFJ2rjQrGjVAKcCkMn+1o3IBZW7xhGyMezwUe6bUK9KOWQ7A==","shasum":"f5a57e36f771fdc782070491a410f1bd6713cf1f","tarball":"https://registry.npmjs.org/@aurite-ai/kahuna-sdk/-/kahuna-sdk-0.4.0.tgz","fileCount":31,"unpackedSize":294816,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIBlXGVGpThkStkqdhoCChGiAsS8iGIv2/5Ux+0bGcnOCAiB3tRdBEl9pHxGl6sXmYFLEgzzBOUjjPMCJCajWuMAuMA=="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:2ff5c690-0217-4105-80e8-935ffba6af36"}},"directories":{},"maintainers":[{"name":"wilcoxr","email":"wilcoxryan26@gmail.com"},{"name":"dreamwvr","email":"terry@weaversoftware.dev"},{"name":"jitenoswal","email":"jiten.p.oswal@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/kahuna-sdk_0.4.0_1787034670490_0.6364697573302733"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-16T09:06:26.037Z","modified":"2026-08-18T06:31:10.814Z","0.1.0":"2026-08-16T09:06:26.348Z","0.1.1":"2026-08-16T10:07:32.086Z","0.2.0":"2026-08-17T23:25:54.111Z","0.3.0":"2026-08-18T00:27:50.029Z","0.4.0":"2026-08-18T06:31:10.637Z"},"bugs":{"url":"https://github.com/Aurite-ai/kahuna-runtime/issues"},"license":"MIT","homepage":"https://github.com/Aurite-ai/kahuna-runtime#readme","keywords":["kahuna","spiffe","spire","mtls","governance","agent","authorization"],"repository":{"type":"git","url":"git+https://github.com/Aurite-ai/kahuna-runtime.git","directory":"packages/agent-sdk"},"description":"Governance SDK for Kahuna — SPIFFE identity, mTLS, and fail-closed action authorization.","maintainers":[{"name":"wilcoxr","email":"wilcoxryan26@gmail.com"},{"name":"dreamwvr","email":"terry@weaversoftware.dev"},{"name":"jitenoswal","email":"jiten.p.oswal@gmail.com"}],"readme":"# @aurite-ai/kahuna-sdk\n\nSDK for building Kahuna-governed agents with SPIFFE/SPIRE identity.\n\n## Overview\n\nThis package provides the infrastructure code needed to integrate with Kahuna's authorization service using mTLS authentication with SPIFFE SVIDs. It eliminates the need to copy-paste 315 lines of boilerplate code across agent implementations.\n\n**Key Benefits:**\n- ✅ **Reduces code duplication** - 315 lines of infrastructure code maintained centrally\n- ✅ **Improves correctness** - Single source of truth for mTLS, SVID loading, and authorization\n- ✅ **Accelerates development** - New agents integrate in hours instead of days\n- ✅ **Ensures consistency** - All agents use the same tested patterns\n- ✅ **Simplifies maintenance** - Bug fixes and improvements propagate automatically\n\n## Installation\n\n```bash\nnpm install @aurite-ai/kahuna-sdk\n```\n\n## Quick Start\n\n```typescript\nimport { KahunaClient, SvidFileLoader } from '@aurite-ai/kahuna-sdk';\n\n// 1. Load SVID files from spiffe-helper sidecar\nconst loader = new SvidFileLoader(\n  process.env.SVID_CERT ?? \"/tmp/agent-svid/svid.0.pem\",\n  process.env.SVID_KEY ?? \"/tmp/agent-svid/svid.0.key\",\n  process.env.SVID_CA ?? \"/tmp/agent-svid/bundle.0.pem\",\n);\n\nconst svid = loader.load();\n\n// 2. Create Kahuna client with mTLS authentication\nconst client = new KahunaClient(\n  process.env.KAHUNA_URL ?? \"https://kahuna-server.kahuna.svc.cluster.local:8443\",\n  svid,\n  process.env.KAHUNA_SERVER_SPIFFE_ID ?? \"spiffe://kahuna.local/server\",\n);\n\n// 3. Watch for SVID rotation (every 30 minutes)\nloader.watchForRotation((newSvid) => {\n  console.log('SVID rotated, updating client...');\n  client.updateSvid(newSvid);\n});\n\n// 4. Authorize actions before executing them\nconst response = await client.authorize({\n  action_id: crypto.randomUUID(),\n  parameters: {\n    action_type: 'write_data',\n    target_resource_type: 'file',\n    target_resource_id: '/tmp/example.txt',\n    write_size_bytes: 1024,\n    is_overwrite: false,\n  },\n  submitted_at: new Date().toISOString(),\n});\n\nif (response.outcome === 'authorized') {\n  // Proceed with the action\n  console.log('Action authorized!');\n} else {\n  // Handle denial\n  console.error('Action denied:', response.deny_reason_code);\n}\n```\n\n## Core Concepts\n\n### SVID Loading\n\nThe [`SvidFileLoader`](src/svid-loader.ts) class loads X.509 certificates from the spiffe-helper sidecar:\n\n- **Automatic retry logic** - Handles cert/key mismatch during rotation\n- **Certificate-key validation** - Uses OpenSSL to verify the pair matches\n- **File watching** - Detects rotation and invokes callbacks\n- **Directory-based watching** - Handles inode replacement during rotation\n\n```typescript\nconst loader = new SvidFileLoader(certPath, keyPath, caPath);\nconst svid = loader.load(); // Synchronous load with retry\n\nloader.watchForRotation((newSvid) => {\n  // Called when sidecar rotates credentials (every 30 minutes)\n  client.updateSvid(newSvid);\n});\n```\n\n### mTLS Authentication\n\nThe [`createMtlsAgent`](src/mtls-client.ts) function creates an HTTPS agent with SPIFFE ID verification:\n\n- **URI SAN validation** - Verifies SPIFFE IDs, not DNS names\n- **Impersonation prevention** - Ensures you're talking to the real Kahuna server\n- **Handshake abortion** - Rejects connections before sending request body\n\n```typescript\nimport { createMtlsAgent } from '@aurite-ai/kahuna-sdk';\n\nconst agent = createMtlsAgent(svid, 'spiffe://kahuna.local/server');\n\n// Use with any HTTPS request\nhttps.request(url, { agent }, (res) => {\n  // Server's SPIFFE ID was verified during handshake\n});\n```\n\n**Why this matters:** Every workload in the trust domain has a CA-signed certificate. Chain verification alone doesn't tell you that you reached Kahuna — the SPIFFE ID check is what prevents impersonation attacks.\n\n### Authorization Flow\n\nThe [`KahunaClient`](src/kahuna-client.ts) class wraps the `/v1/actions/authorize` endpoint:\n\n- **mTLS-authenticated requests** - Uses SPIFFE SVIDs for authentication\n- **SVID rotation support** - Updates agent without downtime\n- **Typed interfaces** - TypeScript types for requests and responses\n- **Error handling** - Distinguishes network errors from authorization denials\n\n```typescript\nconst client = new KahunaClient(baseUrl, svid, serverSpiffeId);\n\nconst response = await client.authorize({\n  action_id: crypto.randomUUID(),\n  parameters: {\n    action_type: 'send_email',\n    recipient: 'user@example.com',\n    subject: 'Test email',\n    body_size_bytes: 1024, // Size, NOT content\n  },\n  submitted_at: new Date().toISOString(),\n});\n\nif (response.outcome === 'authorized') {\n  // Execute the action\n} else {\n  // Handle denial: response.deny_reason_code\n}\n```\n\n### SVID Rotation\n\nSVIDs are rotated every 30 minutes by the spiffe-helper sidecar. The SDK handles this automatically:\n\n```typescript\n// Set up rotation handler once during initialization\nloader.watchForRotation((newSvid) => {\n  client.updateSvid(newSvid);\n});\n\n// The client will use new credentials for all subsequent requests\n// Old connections are gracefully closed\n```\n\n## API Reference\n\n### `SvidFileLoader`\n\nLoads SVID files from the spiffe-helper sidecar.\n\n```typescript\nclass SvidFileLoader {\n  constructor(certPath: string, keyPath: string, caPath: string);\n  \n  load(): SvidFiles;\n  watchForRotation(callback: (svid: SvidFiles) => void): FSWatcher;\n}\n```\n\n**Methods:**\n\n- **`load()`** - Load SVID files synchronously with retry logic. Throws `SvidLoadError` if files cannot be read or cert/key mismatch persists.\n- **`watchForRotation(callback)`** - Watch for SVID rotation and invoke callback with new credentials. Returns a watcher that can be closed with `.close()`.\n\n### `KahunaClient`\n\nClient for the `/v1/actions/authorize` endpoint.\n\n```typescript\nclass KahunaClient {\n  constructor(\n    baseUrl: string,\n    svid: SvidFiles,\n    serverSpiffeId: string,\n    options?: KahunaClientOptions,\n  );\n\n  updateSvid(svid: SvidFiles): void;\n  authorize(request: AuthorizeRequest): Promise<AuthorizeResponse>;\n}\n\ninterface KahunaClientOptions {\n  timeoutMs?: number;           // per attempt, default 5000\n  maxRetries?: number;          // retries reuse the same action_id, default 2\n  minimize?: boolean;           // default true — see below before turning it off\n  minimization?: MinimizationOptions;\n  definitions?: DefinitionCache; // custom actions — see \"Custom actions\"\n}\n```\n\n**Methods:**\n\n- **`updateSvid(svid)`** - Update the SVID after rotation. Call this when the sidecar rotates credentials.\n- **`authorize(request)`** - Authorize an action with Kahuna. Returns the authorization response. Throws `SpiffeIdMismatchError` if the server presents an unexpected SPIFFE ID, and a `KahunaTimeoutError` / `KahunaHttpError` / `KahunaTransportError` / `KahunaValidationError` for the corresponding failures.\n\n**On `minimize`.** It is on by default and should stay on. The client sends the\npolicy-relevant *projection* of your parameters plus a digest of the whole set;\nthe raw values never leave the workload. `{ minimize: false }` puts the action's\nfull parameters on the wire — including the ones you would not want in an\nauthorization log. An action type this build has no tiers for is not an error:\nit minimizes to nothing and is sent, so the server can refuse it and record it\nfor an operator to register. `isMinimizable(parameters)` answers \"does this\nbuild know how to project this?\" for diagnostics.\n\n### `createMtlsAgent`\n\nCreate an HTTPS agent with SPIFFE ID verification.\n\n```typescript\nfunction createMtlsAgent(\n  svid: SvidFiles,\n  expectedServerSpiffeId: string\n): https.Agent;\n```\n\n**Parameters:**\n\n- **`svid`** - The SVID files (cert, key, CA bundle)\n- **`expectedServerSpiffeId`** - The SPIFFE ID the server must present (e.g., `'spiffe://kahuna.local/server'`)\n\n**Returns:** An `https.Agent` configured for mTLS with SPIFFE ID verification.\n\n### Types\n\n```typescript\ninterface SvidFiles {\n  cert: string;  // PEM-encoded certificate\n  key: string;   // PEM-encoded private key\n  ca: string;    // PEM-encoded CA bundle\n}\n\ninterface AuthorizeRequest {\n  action_id: string;\n  parameters: Record<string, unknown>;\n  submitted_at: string;\n}\n\ninterface AuthorizeResponse {\n  outcome: \"authorized\" | \"denied\";\n  deny_reason_code?: string;\n  evaluated_policy_ids: string[];\n  matched_policy_id?: string;\n}\n```\n\n### Errors\n\n```typescript\nclass SpiffeIdMismatchError extends Error {   // server presented an unexpected SPIFFE ID\n  constructor(expected: string, actual: string);\n}\n\nclass SvidLoadError extends Error {           // SVID files unreadable or cert/key mismatched\n  constructor(message: string, cause?: Error);\n}\n\nclass AuthorizationError extends Error {      // the action was refused\n  constructor(message: string, denyReasonCode?: string);\n}\n\nclass KahunaTimeoutError extends Error {}     // no answer within timeoutMs\nclass KahunaHttpError extends Error {}        // a non-200 from the mediator\nclass KahunaTransportError extends Error {}   // connection refused, DNS, TLS\nclass KahunaValidationError extends Error {}  // a 200 whose body is not a decision\n\n// Narrow without instanceof, which is unreliable across duplicate installs of\n// the package (two versions in one tree, or ESM and CJS of the same version).\nfunction isKahunaError(err: unknown): boolean;\nfunction isKahunaErrorOfKind(err: unknown, kind: string): boolean;\n```\n\n**`deny_reason_code` is an open set.** The SDK does not validate it against a\nlist, so a newer mediator can add one without breaking clients — which means a\n`switch` over it needs a `default`. `schema_violation`, for instance, is what a\ncatalog-aware mediator answers when a custom action's parameters do not match its\nregistered schema.\n\n## Examples\n\n### Basic File Agent\n\n```typescript\nimport { KahunaClient, SvidFileLoader } from '@aurite-ai/kahuna-sdk';\nimport { writeFile } from 'node:fs/promises';\n\n// Initialize SDK\nconst loader = new SvidFileLoader(\n  '/tmp/agent-svid/svid.0.pem',\n  '/tmp/agent-svid/svid.0.key',\n  '/tmp/agent-svid/bundle.0.pem',\n);\n\nconst svid = loader.load();\nconst client = new KahunaClient(\n  'https://kahuna-server.kahuna.svc.cluster.local:8443',\n  svid,\n  'spiffe://kahuna.local/server',\n);\n\nloader.watchForRotation((newSvid) => {\n  client.updateSvid(newSvid);\n});\n\n// Implement governed file write\nconst writeFileWithGovernance = governed({\n  client,\n  name: 'write_file',\n\n  // Map the call to what policy decides on: the SHAPE of the action, never its\n  // contents. Minimization is on by default, so only these projected values —\n  // plus a digest of the full parameters — leave the workload.\n  params: (i: { path: string; content: string }) => ({\n    action_type: 'write_data',\n    target_resource_type: 'file',\n    target_resource_id: i.path,\n    write_size_bytes: Buffer.byteLength(i.content, 'utf8'),\n    is_overwrite: existsSync(i.path),\n  }),\n\n  // Reached only after Kahuna authorizes. Throws on a refusal, so a denied call\n  // never looks to a caller — or a model — like one that ran.\n  execute: async (i) => {\n    await writeFile(i.path, i.content);\n    return `File written: ${i.path}`;\n  },\n});\n```\n\n### Any agent framework\n\n`governed()` returns a plain `(input) => Promise<output>`, which is the tool\ninterface every framework accepts. There is no adapter to install:\n\n```typescript\nimport { DynamicStructuredTool } from '@langchain/core/tools';\nimport { z } from 'zod';\n\nconst tool = new DynamicStructuredTool({\n  name: 'write_file',\n  description: 'Write content to a file.',\n  schema: z.object({ path: z.string(), content: z.string() }),\n  func: writeFileWithGovernance,   // the governed() result from above\n});\n```\n\nThe same value drops into other frameworks with one line each:\n\n```typescript\ntool({ description, parameters: schema, execute: writeFileWithGovernance });          // Vercel AI SDK\nFunctionTool.from(writeFileWithGovernance, { name, description, parameters });        // LlamaIndex\ncreateTool({ id, description, inputSchema: schema,\n             execute: ({ context }) => writeFileWithGovernance(context) });          // Mastra\nserver.tool('write_file', schema, writeFileWithGovernance);                            // MCP\nawait writeFileWithGovernance({ path: '/tmp/report.txt', content: '...' });          // no framework\n```\n\nFull per-framework wiring: [framework-recipes.md](https://github.com/Aurite-ai/kahuna-runtime/blob/main/docs/integration/framework-recipes.md).\n\n> A LangChain base class (`KahunaGovernedTool`) also exists in this monorepo, but\n> it is **not published to npm** — it predates `governed()` and wraps exactly the\n> snippet above. Use the snippet.\n\n### Governing several actions at once\n\n`governAll` declares a whole toolset in one place, and makes an action you forgot\nto declare **fail** rather than run ungoverned:\n\n```typescript\nimport { governAll } from '@aurite-ai/kahuna-sdk';\n\nconst governed = governAll({\n  client,\n  actions: {\n    write_file: { params: (i) => ({ /* ... */ }), execute: async (i) => '...' },\n  },\n});\n\ngoverned.assertCovers(tools.map((t) => t.name));   // at startup\n// Error: 1 tool(s) registered with the agent but not governed: run_shell.\n```\n\nIt does not reduce the parameter mappings — those are what a security review\nreads, and they cannot be inferred. It removes the silent failure where a\nforgotten mapping gives the agent a capability nobody authorized.\n\n## Custom actions\n\nThe SDK has tiers compiled in for five action types. Everything your organization\nregisters in the Kahuna console — `refund_order`, `provision_tenant`, whatever the\nagent actually does — is described by a definition that lives on the server, and\nthe client reads it at runtime.\n\nThat is the point: **an admin registers an action in the console and your agents\nuse it — no SDK release, and no code change per action.**\n\nWithout a `DefinitionCache`, a custom action still authorizes, but it is sent with\nno attributes: no policy about its fields can match, so it is refused and recorded\nfor an operator to see. With one, it is sent with exactly the fields the admin\nclassified as policy-relevant.\n\n**Nothing to wire.** The client fetches the catalog over its own mTLS transport\nand refreshes when it goes stale:\n\n```typescript\nimport { KahunaClient } from '@aurite-ai/kahuna-sdk';\n\nconst kahuna = new KahunaClient(baseUrl, svid, serverSpiffeId);\n\n// An action an operator registered in the console. No setup, no refresh loop.\nawait kahuna.authorize({\n  action_id: crypto.randomUUID(),\n  parameters: { action_type: 'refund_order', amount_minor: 4999 },\n  submitted_at: new Date().toISOString(),\n});\n```\n\nA definitions call happens only for an action this build has no tiers for, so a\nworkload using only the built-in five never contacts the endpoint. If the\nendpoint is unreachable the action minimizes to nothing and is refused — the\nsame path an unregistered action takes — rather than failing the call.\n\nPass your own cache to set a freshness bound, cap disclosure, or see refusals:\n\n```typescript\nimport { DefinitionCache, KahunaClient } from '@aurite-ai/kahuna-sdk';\n\nconst definitions: DefinitionCache = new DefinitionCache({\n  fetchDefinitions: (generation) => kahuna.fetchActionDefinitions(generation),\n  maxAgeMs: 5 * 60_000,                 // after this, unrefreshed means minimize to nothing\n  disclosureFloor: { refund_order: { customer_note: 'payload' } },\n  onRefused: (actionType, reason) =>    // never silent\n    console.warn(`definition refused: ${actionType} — ${reason}`),\n});\n\nconst kahuna: KahunaClient = new KahunaClient(baseUrl, svid, serverSpiffeId, { definitions });\n```\n\nOr `{ definitions: false }` to switch it off: custom actions then minimize to\nnothing, which is the pre-catalog behaviour.\n\nconst client = new KahunaClient(baseUrl, svid, serverSpiffeId, { definitions });\n```\n\n**Three rules worth knowing before you rely on it**, because the tier table is a\ndisclosure decision that now arrives from the server rather than from code you\nreviewed:\n\n1. **A fetched definition can never redefine a built-in action.** The server\n   refuses such a row and the client refuses it again, independently.\n2. **A stale cache minimizes to nothing** — never to a previously fetched, wider\n   table. Definitions most often change because someone narrowed one.\n3. **An unknown projection refuses the whole definition** rather than applying a\n   closest match.\n\nIf you want a ceiling the catalog cannot raise, pass a `disclosureFloor`. It can\nonly narrow — there is deliberately no way to express widening a field:\n\n```typescript\nnew DefinitionCache({\n  fetchDefinitions,\n  // Whatever the catalog says, this workload never sends customer_email.\n  disclosureFloor: { refund_order: { customer_email: 'payload' } },\n});\n```\n\n## Migration Guide\n\n### Before SDK (480 lines of integration overhead)\n\n```\nexamples/kahuna-file-agent/\n├── src/\n│   ├── index.ts              (113 lines)\n│   ├── agent.ts              (32 lines)\n│   ├── tools/\n│   │   └── write-file.ts     (57 lines)\n│   ├── svid-loader.ts        (117 lines) ← DUPLICATED\n│   ├── mtls-client.ts        (76 lines)  ← DUPLICATED\n│   └── kahuna-client.ts      (122 lines) ← DUPLICATED\n```\n\n### After SDK (~55 lines)\n\n```\nexamples/kahuna-file-agent/\n├── src/\n│   ├── index.ts              (40 lines)  ← Simplified\n│   ├── agent.ts              (15 lines)  ← Simplified\n│   └── tools/\n│       └── write-file.ts     (45 lines)  ← Simplified\n└── package.json\n    └── dependencies:\n        └── @aurite-ai/kahuna-sdk: ^0.1.0\n```\n\n**Migration steps:**\n\n1. Install the SDK: `npm install @aurite-ai/kahuna-sdk`\n2. Replace local imports with SDK imports:\n   ```typescript\n   // Before\n   import { SvidFileLoader } from './svid-loader.js';\n   import { KahunaClient } from './kahuna-client.js';\n   \n   // After\n   import { SvidFileLoader, KahunaClient } from '@aurite-ai/kahuna-sdk';\n   ```\n3. Delete local infrastructure files:\n   - `src/svid-loader.ts`\n   - `src/mtls-client.ts`\n   - `src/kahuna-client.ts`\n4. Update imports in your agent code\n5. Test that everything still works\n\n**Result:** 87% reduction in code (429 → 55 lines)\n\n## Troubleshooting\n\n### SVID Load Errors\n\n**Problem:** `SvidLoadError: Failed to load SVID files after 5 attempts`\n\n**Causes:**\n- SVID files don't exist at the specified paths\n- Cert and key are mismatched (rotation race condition)\n- File permissions prevent reading\n\n**Solutions:**\n- Verify the spiffe-helper sidecar is running\n- Check file paths in environment variables\n- Ensure the agent has read permissions on SVID files\n- The loader retries automatically, but persistent failures indicate a configuration issue\n\n### SPIFFE ID Mismatch\n\n**Problem:** `SpiffeIdMismatchError: expected spiffe://kahuna.local/server, got spiffe://kahuna.local/other`\n\n**Causes:**\n- Wrong server SPIFFE ID configured\n- Connecting to wrong service\n- Man-in-the-middle attack (rare in Kubernetes)\n\n**Solutions:**\n- Verify `KAHUNA_SERVER_SPIFFE_ID` environment variable\n- Check that you're connecting to the correct Kahuna server URL\n- Review SPIRE server configuration\n\n### Authorization Denied\n\n**Problem:** `outcome: 'denied'` with `deny_reason_code`\n\n**Common reason codes:**\n- `credential_revoked` - Agent's credential was revoked (check kill switches)\n- `policy_deny` - No policy permits the action\n- `rate_limit_exceeded` - Too many requests\n- `invalid_parameters` - Action parameters don't match schema\n\n**Solutions:**\n- Check Kahuna policies for the action type\n- Verify action parameters match the expected schema\n- Review agent kill switch status\n- Check rate limits\n\n### Network Errors\n\n**Problem:** `Request failed: connect ECONNREFUSED`\n\n**Causes:**\n- Kahuna server is not running\n- Wrong URL configured\n- Network connectivity issues\n\n**Solutions:**\n- Verify Kahuna server is running: `kubectl get pods -n kahuna`\n- Check `KAHUNA_URL` environment variable\n- Test connectivity: `curl -k https://kahuna-server.kahuna.svc.cluster.local:8443/health`\n\n## Development\n\n### Building\n\n```bash\nnpm run build\n```\n\n### Type Checking\n\n```bash\nnpm run typecheck\n```\n\n### Testing\n\n```bash\nnpm test\n```\n\n### Cleaning\n\n```bash\nnpm run clean\n```\n\n## Version History\n\nSee [CHANGELOG.md](CHANGELOG.md) for version history.\n\n## License\n\nMIT — see [LICENSE](LICENSE), which ships in the package.\n\n## Support\n\nFor issues or questions:\n- Check the [troubleshooting section](#troubleshooting)\n- Review example agents in `examples/`\n- Consult the Kahuna documentation\n\n## Related Documentation\n\n- [Kahuna Integration Overhead Analysis](https://github.com/Aurite-ai/kahuna-runtime/blob/main/docs/kahuna-integration-overhead-analysis.md)\n- Kahuna Agent SDK Plan\n- [Example: File Agent](https://github.com/Aurite-ai/kahuna-runtime/blob/main/examples/kahuna-file-agent/README.md)\n- [Example: LangChain Agent](https://github.com/Aurite-ai/kahuna-runtime/blob/main/examples/langchain-kahuna-agent/README.md)\n","readmeFilename":"README.md"}