Skip to content

SDK reference: validation & events

Validate a directed acyclic graph for correctness.

function validateDag(nodes: DagNode[]): DagValidationResult;

DagNode:

interface DagNode {
id: string;
needs: string[];
}

DagValidationResult (discriminated union):

// Valid graph with topological sort order
{ valid: true; sortedOrder: string[] }
// Cycle detected
{ valid: false; error: 'cycle'; nodesInCycle: string[] }
// Job depends on itself
{ valid: false; error: 'self-reference'; nodeId: string }
// Job depends on non-existent job
{ valid: false; error: 'missing-dependency'; nodeId: string; missingDep: string }

Checks (in order): self-references, missing dependencies, cycles (Kahn’s algorithm).

const result = validateDag([
{ id: 'lint', needs: [] },
{ id: 'test', needs: ['lint'] },
{ id: 'deploy', needs: ['test'] },
]);
if (result.valid) {
console.log(result.sortedOrder); // ['lint', 'test', 'deploy']
}

The defineEvent() helper creates typed event definitions with Zod validation schemas. Event definitions serve as contracts for custom event payloads used with ctx.emit() and kiciEvent().

function defineEvent<T extends z.ZodTypeAny>(name: string, schema: T): EventDefinition<T>;

Parameters:

ParameterTypeRequiredDescription
namestringyesUnique event name
schemaz.ZodTypeyesZod schema for payload validation

Returns: EventDefinition<T> — a frozen event definition with name and schema.

import { defineEvent, z } from '@kici-dev/sdk';
const deployComplete = defineEvent(
'deploy-complete',
z.object({
env: z.string(),
version: z.string(),
services: z.array(z.string()),
}),
);

The z (Zod) module is re-exported from @kici-dev/sdk so you can define event schemas without adding Zod as a direct dependency.

Workflow steps can emit custom events via ctx.emit(). Emitted events are delivered immediately (mid-workflow, not queued until completion) and can trigger other workflows that listen with kiciEvent(), workflowComplete(), or jobComplete() triggers.

emit(
eventName: string,
payload?: Record<string, unknown>,
options?: EventEmitOptions,
): Promise<{ deliveryId: string }>;

Parameters:

ParameterTypeRequiredDescription
eventNamestringyesName of the event to emit
payloadRecord<string, unknown>noEvent payload data
options.target{ repos?: string[] }noTarget specific repos for cross-repo delivery

Returns: Promise<{ deliveryId: string }> — a delivery receipt after the event is persisted and routed.

Examples:

// Emit a simple event
step('notify', async (ctx) => {
await ctx.emit('deploy-complete', { env: 'prod', version: '1.2.3' });
});
// Cross-repo targeting
step('notify-other-repos', async (ctx) => {
await ctx.emit(
'deploy-complete',
{ env: 'prod' },
{
target: { repos: ['org/other-repo', 'org/monitoring'] },
},
);
});

Events emitted from one repo can trigger workflows in another repo, provided:

  1. A trust relationship exists between the source and target repos (configured via the admin API)
  2. The target workflow uses a trigger with source filter matching the emitting repo
// In repo A: emit event
step('deploy', async (ctx) => {
await ctx.emit(
'deploy-complete',
{ env: 'prod' },
{
target: { repos: ['org/repo-B'] },
},
);
});
// In repo B: listen for event from repo A
workflow('post-deploy', {
on: kiciEvent({ name: 'deploy-complete', source: 'org/repo-A' }),
jobs: [postDeployJob],
});

The orchestrator automatically emits system events for workflow and job completions. You do not need to call ctx.emit() for these — they are generated by the orchestrator after execution. Listen for them with workflowComplete() and jobComplete() triggers.