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_hash —
policy.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
- Upgrade to 0.7.0 — the previous upgrade, renaming the CLI command.
- Runtime API — the
policiestable and the derivation rule. - .NET Runtime API — the
Policiescontainer. - Test Authorization — call-site examples in their current form.