Generated Files
Ninka writes two different kinds of generated output:
- canonical artifacts under
ninka/out/; - Consumer Projections in the application's source tree.
They serve different readers and are not one directory contract.
| Output | Typical location | Primary readers |
|---|---|---|
| Canonical artifacts | ninka/out/ | verify, Authorization Reference, artifact tooling, and optional directory-based runtime loading |
| TypeScript Consumer Projection | <source root>/generated/ninka.ts or --out | TypeScript application code and ninka-authz/runtime |
| C# Consumer Projection | path passed to --out-csharp | .NET application code and Ninka.Authz |
Artifact layout
ninka/out/
├── policies/
│ ├── <id>.rego
│ ├── <id>.manifest.json
│ └── <id>.validation.json
├── bundles/
│ ├── <bundle-id>.wasm
│ └── <bundle-id>.build.json
└── input-contract.json
Policy files describe one policy's compiled source, identity, lineage, and validation. Bundle files describe an execution unit. A bundle contains one WASM module with one entrypoint per policy bound to that bundle.
The artifact contract permits one or more bundles. The current toolchain emits one bundle for the workspace; bundle partitioning is not a user-configurable authorization concept.
Git tracking is a separate repository decision
Being a canonical Ninka artifact does not mean every repository must track that file in Git.
Ninka supports different repository roles:
| Repository role | Typically tracked | Raw WASM | Anchor for module identity |
|---|---|---|---|
| Projection-only application | Tegata + Consumer Projection | not required | module and build record embedded in the projection |
Reviewable-artifacts application (init default) | Tegata + ninka/out/ except raw WASM + projection | not required | build.json.wasm_sha256 |
| Artifact / conformance corpus | complete artifact set | tracked | module bytes themselves |
ninka-authz init adds /ninka/out/bundles/*.wasm to .gitignore, so the second row is the normal scaffolded application configuration.
All of these roles can use verify because verify does not read Git. It checks whether the required files are present at the resolved filesystem paths and whether they match the state reproduced from the current authorization inputs and toolchain contract.
The universal rule is not “commit every artifact.” It is: generated state a repository chooses to track must remain reproducible from the authorization inputs and pinned toolchain assumptions that repository tracks. The normative repository-role contract is in TOOLCHAIN_SPEC §10.4.
Files at a glance
| File | Produced | Purpose |
|---|---|---|
policies/<id>.rego | after source checks | deterministic Rego produced from one Tegata |
policies/<id>.manifest.json | after source checks | policy identity, Rego lineage, entrypoint, input requirements, Decision Log disclosure metadata |
input-contract.json | after source checks | merged project-wide input contract |
bundles/<bundle-id>.wasm | after successful OPA build | executable WASM module |
bundles/<bundle-id>.build.json | with the bundle; validation fields finalized after validation | module identity and policy-to-module bindings |
policies/<id>.validation.json | after generated validation succeeds against the built WASM | validation table derived from the current Tegata |
Consumer Projections are outside ninka/out/ and are written only when Ninka has a bundle to embed.
If OPA cannot be resolved, plain compile can still write the source-derived Rego, manifests, and input contract. build is the hard-fail command when producing a complete execution bundle is mandatory.
Consumer Projections
TypeScript
The generated TypeScript module exports the application-facing surface:
import { createNinka, policies } from "@/src/generated/ninka";
const authz = await createNinka({
onDecision: (event) => logger.info(event),
});
const allowed = authz.check(policies.invoiceAccess, input);
Its public exports are:
| Export | Meaning |
|---|---|
createNinka(options?) | Creates a Ninka instance from the embedded bundle. The generated option surface currently exposes onDecision for observing Decision Log entries. |
policies | Table keyed by a name derived from each policy's Tegata id (hyphens removed, camelCase). Each entry binds the exact, untransformed id and its tegataHash to the generated input type. |
The generated file has one deliberate runtime dependency: it imports ninka-authz/runtime. It does not import files from ninka/out/ at runtime. Instead, it carries the parsed build/manifest data and the WASM as base64 so the runtime can verify and instantiate the bundle without an artifact directory.
The projection does not reimplement authorization rules as application control flow. Decoding the module, checking artifact identities, instantiating WASM, Decision Log masking, and evaluating check() remain runtime responsibilities.
C#
The C# Consumer Projection is written only when --out-csharp <file> is supplied. It carries the equivalent compiled bundle information and generated input/policy references for Ninka.Authz.
The exact generated syntax differs from TypeScript, but the boundary is the same: the application imports generated source instead of reconstructing artifact bindings itself.
Keeping a projection current
The header records what generated the projection, but the header is not a freshness oracle.
ninka-authz verify regenerates the projection at the path it resolves (or the --out / --out-csharp path supplied to the command) and requires byte identity with the file on disk. verify does not rewrite the projection and does not care whether Git tracks it.
At runtime, a policy reference also carries tegataHash. If a stale projection is paired with artifacts from another specification revision, check() refuses the mismatch instead of evaluating under the wrong generated contract.
See Consumer Projection for the architectural role and the runtime references for the API surface.
policies/<id>.rego
The deterministic Rego emitted for one policy.
It contains authorization decision logic, but not review-only text such as policy.description or audit, and not build timestamps. Ninka emits it before the OPA build step.
The application runtimes do not evaluate .rego directly; execution uses the compiled WASM.
verify regenerates this Rego from the current Tegata and requires byte identity with the file on disk.
policies/<id>.manifest.json
The manifest owns the Tegata → Rego part of the artifact chain and the runtime contract for one policy.
| Field | Meaning |
|---|---|
policy_id | exact policy id |
tegata_version | Tegata language/schema version, currently "0.1" |
artifact_format_version | artifact contract version, currently 2 |
generated_by | Ninka package/release that generated the manifest; provenance, not specification identity |
tegata_hash | identity of the canonical Tegata authorization content |
rego_sha256 | SHA-256 of the emitted Rego bytes |
entrypoint | WASM entrypoint derived by the compiler and declared for this policy |
covered_resource_types | resource types named by the policy |
input_requirements | compiler-derived input keys/types/requiredness/origins for the policy |
decision_log.unmasked | attributes permitted to appear unmasked in Decision Log entries |
tegata_hash, rego_sha256, and generated_by have different jobs. A specification hash is not a generated-code hash, and the generating release is provenance rather than another identity for the specification.
The runtime requires a supported artifact_format_version. A missing version cannot be guessed safely. Unsupported older/newer formats are refused with remediation pointing toward recompilation or runtime upgrade as appropriate.
bundles/<bundle-id>.build.json
The build record owns the Rego → entrypoint → WASM part of the artifact chain for one execution bundle.
| Field | Meaning |
|---|---|
bundle_id | bundle id; corresponds to the build/module file names |
artifact_format_version | artifact contract version |
generated_by | Ninka release that built the bundle |
opa_version | OPA build provenance, normally the configured pinned version |
wasm_sha256 | SHA-256 identity of the emitted WASM bytes |
policies[] | one policy binding per policy in the bundle |
Each binding contains:
| Field | Meaning |
|---|---|
policy_id | policy bound to the module |
entrypoint | entrypoint for that policy in the module |
rego_sha256 | Rego lineage; must agree with that policy's manifest |
validation | successful validation metadata: vector count, vector digest, status |
The build record intentionally does not duplicate tegata_hash. The manifest connects Tegata to Rego; the build record connects Rego to the executable module. They meet at rego_sha256.
At load time the runtime checks the module hash, artifact formats, binding policy ids, Rego lineage, manifest/binding entrypoint agreement, and that the declared entrypoint actually exists in the module.
A bundle is one execution artifact. If a required link for any bound policy fails, the runtime refuses the whole bundle rather than serving a partially verified module.
policies/<id>.validation.json
The validation table is generated from the current Tegata and written only after the newly built WASM agrees with the required expected decisions.
A file contains the policy id, tegata_hash, validation format version, and the generated vectors.
Current evidence families include:
| Vector id prefix | Purpose |
|---|---|
allow:<rule-id> | positive witness that establishes coverage for an allow rule under the complete policy |
what-if:<rule-id>:... | decision after changing one generated witness facet |
deny:<allow-rule-id>:<deny-rule-id> | overlapping evidence where deny overrides allow |
Validation derivation distinguishes a provably impossible positive witness (UNSAT) from a bounded search that cannot establish one (UNKNOWN). Neither counts as successful positive coverage.
Expected decisions are determined independently from the emitted compiler transformation; the validation file is not produced by asking the generated Rego what answer it expects from itself.
verify derives the table again, compares it with the file on disk, checks the corresponding digest/count recorded in the bundle build record, and replays the required vectors against WASM when the toolchain path allows that obligation to be checked.
bundles/<bundle-id>.wasm
The executable OPA WASM module. Each policy bound to the bundle has its own entrypoint in this module.
The file's actual SHA-256 must equal build.json.wasm_sha256. WASM identity depends on the Rego bytes and the OPA build toolchain, which is why opa_version is recorded as build provenance and the default CLI path pins OPA.
NINKA_OPA_PATH can explicitly override the default binary. A caller-supplied override is not checksum-verified by Ninka as though it were the pinned distribution, so reproducibility responsibility for that override belongs to the caller.
input-contract.json
The merged project-wide input contract derived from the compiled policies.
{
"generated_by": { "package": "ninka-authz", "version": "0.7.0" },
"keys": [
{
"key": "subject.user_id",
"type": "string",
"required_by": ["invoice-access"],
"policies": ["invoice-access"]
}
]
}
It records the merged key/type contract, which policies use a key, and which policies require it. Per-policy manifest requirements and generated language input types come from the same compiler-derived contract information.
Language-specific type projections do not live under ninka/out/.
verify re-derives the contract and compares the recomputable content with the file on disk. generated_by is handled as provenance rather than part of the contract identity comparison.
Directory loading versus embedded loading
A runtime that loads artifacts from a directory needs the execution bundle and the manifest for every policy bound to it:
bundles/<bundle-id>.wasm
bundles/<bundle-id>.build.json
policies/<id>.manifest.json
That is the acquisition model used by Ninka.load(dir) in TypeScript and Authz.Load(dir) in .NET.
The normal generated application path does not require an artifact directory at runtime. Consumer Projections carry the equivalent bundle/module/manifest information in generated source and use the same verification rules after the runtime reconstructs the in-memory artifact shape.
Rego, validation tables, and input-contract.json are part of the wider compile/verification artifact set even though the runtime does not need all of them to execute a decision.
Exported Authorization Reference is not ninka/out/
ninka-authz docs -o <dir> creates a static Authorization Reference snapshot. It contains browser assets, data/reference.json, and content-digest-named copies of the exported execution bundle modules.
This is a different format with a different consumer:
ninka/out/ | docs -o snapshot | |
|---|---|---|
| Produced by | compile / build | docs -o (does not compile) |
| Consumed by | verification/tooling and optional directory runtime | browser |
| Repository tracking | depends on repository role | normally publish, retain, or discard as an exported snapshot |
| Identity | Tegata/Rego/WASM artifact identities | snapshot_hash for the captured authorization state |
Export time is stored separately as exported_at and is not part of snapshot_hash. Neither field represents human approval.
See Authorization Reference for LIVE/SNAPSHOT behavior and hosting considerations.