Skip to main content

Vocabulary

ninka/vocabulary.json declares project terms that Tegata policies may use, and the subject/resource attributes that may be recorded unmasked in the Decision Log.

The file is optional. Without it, role/action/resource-type governance falls back to the open-world checks Ninka can perform heuristically. When the file exists, Ninka applies the closed-world V1 check: a role, action, or resource type used by a policy must be declared in the corresponding vocabulary list. Attribute keys become closed-world when the attributes map is non-empty.

{
"roles": ["employee", "member"],
"actions": ["view", "edit"],
"resource_types": ["invoice", "document"],
"attributes": {
"subject.user_id": "string",
"resource.owner_id": "string",
"resource.amount": { "type": "integer" }
},
"decision_log": { "unmasked": ["resource.amount"] }
}

Why declare a vocabulary

Heuristics cannot reliably decide that two different strings were intended to be the same project term. A closed-world vocabulary moves that decision into an explicit file instead.

The review benefit is straightforward: introducing a role such as admin2 requires adding it to vocabulary.json. The new term therefore appears explicitly in the change being reviewed instead of becoming part of the policy set merely because an author typed a new string.

This does not make Ninka a semantic thesaurus. If the vocabulary explicitly contains both admin and Admin, they are still two different exact tokens. The closed-world contract guarantees declaration, not automatic equivalence detection.

Fields

The root accepts exactly these five keys. Any other root key is an error so that a misspelling cannot silently look like “nothing was configured.”

FieldTypeMeaning
rolesarray of stringsRoles that policies may name.
actionsarray of stringsActions that policies may name.
resource_typesarray of stringsResource types that policies may name.
attributesobjectScoped attribute path → declared value type.
decision_logobjectDecision Log disclosure settings. Only unmasked is defined.

All five fields are optional.

roles, actions, and resource_types

When vocabulary.json exists, every non-wildcard role, action, and resource type used by the policy set must appear in its corresponding list. An undeclared use is a V1 compile error.

Role values are compared exactly and may follow an external IdP's spelling within the role-value constraints. Actions and resource types are Ninka-owned vocabulary and follow the Tegata naming rules. See Tegata Schema.

attributes

Keys are scoped attribute paths such as subject.user_id or resource.owner_id.

Values may use either shorthand or object form:

{
"subject.user_id": "string",
"resource.amount": { "type": "integer" }
}

The allowed value types are:

  • string
  • integer
  • boolean

The object form may contain only type. Unknown keys or unknown types are rejected rather than ignored.

The declared type also participates in the cross-policy C4 input-contract check, so conflicting declarations or inferred uses of one scoped key are reported.

Attribute closed-world enforcement has one deliberate exception: an empty attributes: {} does not mean “this project uses no attributes.” Attribute-key V1 checks begin only when the map is non-empty. This lets a project introduce vocabulary governance gradually without instantly rejecting every existing attribute.

decision_log.unmasked

This list explicitly declares which attributes may be written with their raw values in the Decision Log.

Attributes omitted from the list remain masked. If decision_log is absent, or if unmasked is empty, no subject/resource attribute is implicitly disclosed by Ninka.

Each entry must:

  • be declared in attributes;
  • appear only once;
  • not be action.name or resource.type.

action.name and resource.type are always recorded raw by the Decision Log contract because they are policy-selection literals. Listing them in decision_log.unmasked is therefore an error.

Ninka sorts the unmasked list before carrying it into generated manifests so declaration order does not affect artifact bytes. ninka-authz verify recomputes the source-derived value and compares it with the manifest files at the artifact paths being checked. It does not inspect whether those files are tracked by Git.

The runtime applies the disclosure policy when it emits Decision Log entries. See Runtime API and .NET Runtime API.

How checks change when the file exists

No vocabulary.jsonWith vocabulary.json
Undeclared role / action / resource typeno closed-world declaration to checkerror (V1)
Cross-policy case-only vocabulary variantsfallback warning (C3)C3 is not used; every used token must instead be explicitly declared
Undeclared attribute keyno vocabulary-based key checkerror (V1) when attributes is non-empty

Without a vocabulary file, Sekisho can still report the fallback vocabulary-drift heuristics defined for that mode. With a vocabulary file, the control changes from “guess whether two terms look suspicious” to “require every used term to have been deliberately declared.”

Missing and malformed are different

A missing vocabulary.json is legal. A file that exists but is invalid is not treated as though it were missing.

Compilation stops for errors such as:

  • invalid JSON;
  • an unknown root key;
  • an invalid attribute declaration;
  • an invalid Decision Log disclosure entry.

This distinction prevents a configuration typo from silently turning governance off.

See also