Part of the Keon Governance Platform. Documentation: keon-docs Website: keon.systems
Governance-first execution platform - Law before action, safety by default
Safe-by-default TypeScript client for Keon Runtime with strict validation, automatic retries, and structured error handling.
- Law before execution - Every action requires a policy decision
- Execute requires receipt - Hard fail if
DecisionReceiptIdis absent - CorrelationId is mandatory - Canonical format:
t:<TenantId>|c:<uuidv7> - Bounded retries - Automatic retries for transient failures with exponential backoff
# Using pnpm (recommended)
pnpm add @keon/sdk
# Using npm
npm install @keon/sdk
# Using yarn
yarn add @keon/sdkimport { KeonClient } from '@keon/sdk';
const client = new KeonClient({
baseUrl: 'https://api.keon.systems/runtime/v1',
apiKey: 'your-api-key',
});
// 1. Request a policy decision
const receipt = await client.decide({
tenantId: 'tenant-123',
actorId: 'user-456',
action: 'execute_workflow',
resourceType: 'workflow',
resourceId: 'workflow-789',
context: { environment: 'production' },
});
// 2. Execute (requires receipt)
if (receipt.decision === 'allow') {
const result = await client.execute({
receipt,
action: 'execute_workflow',
parameters: { workflowId: 'workflow-789' },
});
console.log('Execution result:', result);
}// Automatically handles decision + execution in one call
try {
const result = await client.decideAndExecute({
tenantId: 'tenant-123',
actorId: 'user-456',
action: 'delete_resource',
resourceType: 'document',
resourceId: 'doc-456',
parameters: { force: false },
});
console.log('Success:', result);
} catch (error) {
if (error instanceof ExecutionDeniedError) {
console.log('Policy denied:', error.message);
}
}import {
ExecutionDeniedError,
InvalidCorrelationIdError,
MissingReceiptError,
NetworkError,
ServerError,
} from '@keon/sdk';
try {
const result = await client.decideAndExecute({
tenantId: 'tenant-123',
actorId: 'user-456',
action: 'sensitive_operation',
resourceType: 'data',
resourceId: 'data-123',
parameters: {},
});
} catch (error) {
if (error instanceof ExecutionDeniedError) {
console.log('Policy denied:', error.message);
console.log('Reason:', error.details);
} else if (error instanceof InvalidCorrelationIdError) {
console.log('Invalid correlation ID:', error.message);
} else if (error instanceof MissingReceiptError) {
console.log('Missing receipt:', error.message);
} else if (error instanceof NetworkError) {
console.log('Network error:', error.message);
} else if (error instanceof ServerError) {
console.log('Server error:', error.message);
}
}class KeonClient {
constructor(config?: KeonClientConfig);
decide(params: DecideParams): Promise<DecisionReceipt>;
execute(params: ExecuteParams): Promise<ExecutionResult>;
decideAndExecute(params: DecideAndExecuteParams): Promise<ExecutionResult>;
}interface KeonClientConfig {
baseUrl?: string; // Default: 'http://localhost:8080/runtime/v1'
apiKey?: string; // Optional API key
bearerToken?: string; // Optional bearer token
retryPolicy?: RetryPolicy; // Default: RetryPolicy.default()
timeout?: number; // Default: 30000ms
gateway?: RuntimeGateway; // Optional custom gateway
}Request a policy decision.
await client.decide({
tenantId: string;
actorId: string;
action: string;
resourceType: string;
resourceId: string;
context?: Record<string, unknown>;
correlationId?: CorrelationId; // Auto-generated if not provided
});Returns: DecisionReceipt with decision (allow/deny) and receiptId
Throws:
InvalidCorrelationIdError- CorrelationId format invalidValidationError- Request validation failedNetworkError- Connection/timeout issuesServerError- 5xx server errors
Execute an action with a decision receipt.
await client.execute({
receipt: DecisionReceipt; // REQUIRED - from decide()
action: string;
parameters: Record<string, unknown>;
correlationId?: CorrelationId; // Optional override
});Returns: ExecutionResult with status and result
Throws:
MissingReceiptError- Receipt is missing or invalidExecutionDeniedError- Receipt has decision=denyInvalidCorrelationIdError- CorrelationId format invalidValidationError- Request validation failedNetworkError- Connection/timeout issuesServerError- 5xx server errors
Decide and execute in one call (convenience method).
await client.decideAndExecute({
tenantId: string;
actorId: string;
action: string;
resourceType: string;
resourceId: string;
parameters: Record<string, unknown>;
context?: Record<string, unknown>;
correlationId?: CorrelationId;
});Returns: ExecutionResult if allowed
Throws: Same as execute()
// Default retry policy (3 attempts with exponential backoff)
RetryPolicy.default()
// No retries
RetryPolicy.none()
// Custom retry policy
new RetryPolicy({
maxAttempts: 3,
initialDelayMs: 100,
maxDelayMs: 1000,
backoffMultiplier: 2,
retryableStatusCodes: [408, 429, 500, 502, 503, 504],
})import { RuntimeGateway } from '@keon/sdk';
class CustomGateway implements RuntimeGateway {
async decide(request: DecideRequest): Promise<DecideResponse> {
// Custom implementation
}
async execute(request: ExecuteRequest): Promise<ExecuteResponse> {
// Custom implementation
}
}
const client = new KeonClient({
gateway: new CustomGateway(),
});// This will throw MissingReceiptError
await client.execute({
receipt: null,
action: 'test',
parameters: {},
});β
CORRECT: Always call decide() first
const receipt = await client.decide({
tenantId: 'tenant-123',
actorId: 'user-456',
action: 'test',
resourceType: 'resource',
resourceId: 'res-1',
});
await client.execute({
receipt,
action: 'test',
parameters: {},
});const receipt = await client.decide({ /* ... */ });
// This will throw ExecutionDeniedError if decision=deny
await client.execute({ receipt, /* ... */ });β CORRECT: Check decision before executing
const receipt = await client.decide({ /* ... */ });
if (receipt.decision === 'allow') {
await client.execute({ receipt, /* ... */ });
} else {
console.log('Denied:', receipt.reason);
}// This will throw InvalidCorrelationIdError
await client.decide({
tenantId: 'tenant-123',
actorId: 'user-456',
action: 'test',
resourceType: 'resource',
resourceId: 'res-1',
correlationId: 'invalid-format', // β Not canonical format
});β CORRECT: Use canonical format or let SDK auto-generate
// Auto-generate (recommended)
await client.decide({
tenantId: 'tenant-123',
actorId: 'user-456',
action: 'test',
resourceType: 'resource',
resourceId: 'res-1',
// correlationId will be auto-generated
});
// Or use canonical format
await client.decide({
tenantId: 'tenant-123',
actorId: 'user-456',
action: 'test',
resourceType: 'resource',
resourceId: 'res-1',
correlationId: 't:tenant-123|c:01932b3c-4d5e-7890-abcd-ef1234567890',
});Run tests:
pnpm testRun tests in watch mode:
pnpm test:watchApache License 2.0
Contributions welcome! Please read the contributing guidelines first.