Runtime API
Complete reference for the Ninka runtime — the API your application uses to load compiled policies
and make authorization decisions. Decisions run in-process via WASM: no network, no opa binary on
the request path, no cloud.
Import
import { Ninka, type AuthzInput, type DecisionLogEntry } from "ninka-authz/runtime";
Exported from ninka-authz/runtime: the class Ninka and the interfaces AuthzInput,
InputValidationError, DecisionLogEntry, DecisionLogOptions, NinkaLoadOptions.
The sole runtime dependency is @open-policy-agent/opa-wasm.
class Ninka
The constructor is private — construct an instance with Ninka.load.
static load(dir?, options?)
static async load(dir = "authz/out", options: NinkaLoadOptions = {}): Promise<Ninka>
Loads every *.build.json in dir and returns a ready Ninka instance.
Fail-closed load-time integrity. load throws if:
diris missing,- there are no policies, or
- a WASM's SHA-256 does not match its
build.json(wasm_sha256) or the manifest'srego_sha256lineage.
Error messages point to npx ninka build. A tampered or stale authz/out/ fails to load rather than
being used.
check(policy, input)
Call check through the generated lib/authz.ts boundary, passing a typed policy reference from
the generated policies table — never a bare string:
import { check, policies } from "@/lib/authz";
const allowed = await check(policies.invoiceAccess, {
subject: { properties: { roles: ["accountant"], user_id: "user-123" } },
action: { name: "view" },
resource: { type: "invoice", properties: { amount: 1200, submitted_by: "user-123" } },
});
The boundary is typed as:
check<In extends AuthzInput>(policy: PolicyRef<string, In>, input: In): Promise<boolean>
policies.invoiceAccess(fromtypes.gen.ts) is a generated, typedPolicyRef.- Its type carries the policy's required input shape (
InvoiceAccessInput). - TypeScript checks the second argument against that type — a wrong shape or an unknown action is a compile-time error.
- The underlying runtime id is a string, but callers pass
policies.<id>, never an arbitrary string.
Returns true (allow) or false (deny). An unknown policy throws (fail-closed); deny-overrides
is resolved inside the compiled policy; a DENY caused partly by missing required input fires
onInputError without changing the decision; one decision-log entry is emitted when
logging is on.
On the Ninka instance itself the method is check(policyId: string, input: AuthzInput): boolean
(synchronous). The generated boundary adds the PolicyRef typing and awaits the memoized
load, so application code sees Promise<boolean>.
get policyIds()
get policyIds(): string[]
Runtime introspection — the string ids currently loaded. This is not the type-safe application
API: to make a decision, pass a typed PolicyRef from the generated policies table (see
check), not a string taken from policyIds.
interface AuthzInput
The input to check.
export interface AuthzInput {
subject: { properties: Record<string, unknown> };
action: { name: string };
resource: { type: string; properties?: Record<string, unknown> };
context?: Record<string, unknown>;
}
Tegata attribute keys map onto this shape as follows:
| Tegata key | Input location |
|---|---|
action | action.name |
resource_type | resource.type |
subject.* | subject.properties.* |
resource.* | resource.properties.* |
environment.* | context.* |
custom.* | context.custom.* |
The generated types.gen.ts gives you a policy-specific type that pins this shape exactly — see
Generated Files and Tegata Schema.
interface NinkaLoadOptions
export interface NinkaLoadOptions {
onInputError?: (error: InputValidationError) => void; // { policyId, missing: string[] }
decisionLog?: DecisionLogOptions; // { enable?: boolean; sink?: (e) => void }
}
onInputError
Called when a DENY was caused, in part, by missing required input — this distinguishes data-caused denies from judgment-caused ones. It never changes the decision. Default: one line on stderr.
decisionLog
Off by default. When enabled, check emits OPA-Decision-Log-compatible entries.
The logged input is masked. Only attributes the vocabulary marks audit: true appear raw;
every other value becomes "***". The default sink writes one JSON line to stdout.
export interface DecisionLogOptions {
enable?: boolean;
sink?: (entry: DecisionLogEntry) => void;
}
interface DecisionLogEntry
An OPA-compatible decision-log entry, with Ninka's own data under ninka.
| Field | Value |
|---|---|
decision_id | Unique id for the decision. |
timestamp | When the decision was made. |
path | "ninka/result". |
result | boolean — the decision. |
input | The masked input (only audit: true attributes appear raw). |
bundles | Policy → { revision }, where revision is the tegata_hash. |
labels | Log labels. |
ninka | { schema: 1, policies: [{ id, verdict, missing? }] }. |
interface InputValidationError
export interface InputValidationError {
policyId: string;
missing: string[];
}
Passed to onInputError: which policy denied, and which required input keys were missing.
See also
- Quickstart — wiring
checkinto your app. - Generated Files — the artifacts
loadreads. - Tegata Schema — how a policy's attributes become the input shape.