Skip to main content

Upgrade to Ninka 0.8.0

0.8.0 changes how application code addresses a policy in the generated Consumer Projection, in both languages. No artifact changes shape — artifact_format_version stays 2, and Tegata, Rego and WASM identity are unaffected. What breaks is consumer source: the property name you look up in TypeScript's policies table, and the reference C# code reads off the generated input contract.

npm install ninka-authz@0.8.0

.NET projects also update the Ninka.Authz package reference to 0.8.0. This release does not touch the artifact contract, so there is no load-time version refusal forcing an order between the two — update the languages you have, at whatever pace suits a larger codebase.


Step 1 — Check for a policy id a rename will now catch

0.8.0 derives a reference name from every policy id: hyphens removed, the following letter capitalised. That derivation is not injective. A digit has no capital form, so a-1 and a1 both become a1 in TypeScript and A1 in C#.

Rule C7 (TEGATA_SPEC §10.1) refuses such a pair at compile time, before either generated projection exists. What C7 compares is the ids with their hyphens removed, and nothing else — it never mentions capitalisation, which makes it wider than the derivation. document-access and documentaccess derive to two different names (documentAccess and documentaccess), and C7 still refuses them. That is deliberate: two ids that differ only in a hyphen are a hazard to whoever reads the diff, whatever any language would call them. So the rule you check your project against is the one below, not the derivation above:

Two policy ids in one workspace must not be equal once hyphens are removed.

Check:

npx ninka-authz build

If your project has such a pair, this is where you find out — at the door, not inside a generated file two steps from now:

[error] [C7] policy ids "a-1" and "a1" are the same once hyphens are removed; rename one so the two can be told apart in a diff

Rename one of the two ids in its Tegata file. The rename changes that policy's tegata_hashpolicy.id is a canonical field — so treat it like any specification change: it needs review, and it rebuilds every artifact for that one policy. The rules themselves do not have to change.

If build already exits 0, nothing in your project collides, and steps 2 and 3 are the whole upgrade.


Step 2 — TypeScript: address policies by the derived name, not the raw id

// before — 0.7.x
const allowed = authz.check(policies["invoice-access"], input);

// after — 0.8.0
const allowed = authz.check(policies.invoiceAccess, input);

policies is exported from the same generated file as before, and the reference itself still carries the exact, untransformed id — policies.invoiceAccess.id === "invoice-access" — only how you index into the table changes.

There is no bridge for the old syntax. The generated policies object is no longer indexable by a raw string, so a project that skips this step fails at tsc. It fails without TypeScript too: check() refuses a policy id string at run time and throws, so a JavaScript caller cannot reach a decision through it either. The low-level runtime's checkByPolicyId is a separate method rather than an overload of check(), and it was always documented as internal verification/CLI plumbing rather than application API; 0.8.0 strips it from the published types to match, so a project that had been calling it directly loses that path too and moves to a generated reference instead.

Find every call site:

grep -rnE 'policies\["[^"]+"\]' --include='*.ts' --include='*.tsx' . | grep -v node_modules

Rewrite each: drop the brackets and quotes, remove the hyphens from the id, and capitalise the letter that followed each removed hyphen.


Step 3 — C#: read the reference off Policies, not off the input type

// before — 0.7.x
var allowed = authz.Check(InvoiceAccessInput.Policy, new InvoiceAccessInput { … });

// after — 0.8.0
var allowed = authz.Check(Policies.InvoiceAccess, new InvoiceAccessInput { … });

InvoiceAccessInput — the input contract type — is unchanged. Only the policy reference moves off it, onto a new generated Policies container.

Regenerate the projection first, so Policies exists for the compiler to resolve against:

npx ninka-authz build --out-csharp Generated/Ninka.g.cs

Find every call site:

grep -rnE '\w+Input\.Policy\b' --include='*.cs' . | grep -v /obj/ | grep -v /bin/

Rewrite each <Name>Input.Policy as Policies.<Name>. A build error at any remaining old-syntax call site is expected, and is the compiler doing the rest of this step for you: the .Policy member no longer exists on the input type.


Check

npx ninka-authz build --out <your projection path> [--out-csharp <your C# projection path>]
npx ninka-authz verify --out <your projection path> [--out-csharp <your C# projection path>]
✔ invoice-access: match (tegata_hash c2b55b92fb53…)
✔ src/generated/ninka.ts: Consumer Projection matches (1 policy)
✔ Generated/Ninka.g.cs: Consumer Projection matches (1 policy)
verify: all artifacts match

Then run your application's own tests. Anything mocking or asserting policies["<id>"] in TypeScript, or an <Name>Input.Policy reference in C#, needs the same rewrite as the call sites above — see Test Authorization for what the current form looks like in a test.

Commit the regenerated projection(s) alongside your source changes. If step 1 renamed a policy, commit its rebuilt artifacts too — git status should show nothing generated left untracked or modified.

See also