zynactradocs

Manifests

Write zaccess.yaml, the product manifest and the resource manifests, and keep the generated files in step.

A product has one zaccess.yaml, one product manifest and one resource manifest per area. zaccess gen compiles them, together with the built-in access.* manifest, into everything the runtime needs. Every field is in the manifest reference.

zaccess.yaml

zaccess.yaml
# yaml-language-server: $schema=https://raw.githubusercontent.com/zynactra/access/main/spec/zaccess.schema.json
product: ./access/access.yaml          # exactly one product manifest
resources:
  - ./access/plugins/*/access.yaml     # globs; one manifest per area
orgRules:
  - ./access/orgs/*.yaml               # optional; may match nothing
out:
  policies: ./access/generated/policies          # what Cerbos serves
  manifest: ./access/generated/access.manifest.json
  ts: ./src/access/generated.ts                  # optional: P constants and the manifest for TS
  go: { file: ./access/generated/accessgen/access.go, package: accessgen }   # optional
tests: ./policy-tests                  # Cerbos test suites

The yaml-language-server comment gives editors completion and validation from spec/zaccess.schema.json (and spec/manifest.schema.json for manifests). zaccess check is the source of truth.

The product manifest

Scope types and system roles: the parts every area shares.

access/access.yaml
product: leave-demo
scopeTypes:
  team:
    kind: prefix
    principalAttr: reportingPath
    resourceAttr: ownerPath
    excludeSelf: true
  org_unit:
    kind: set
    resourceAttr: orgUnitId
    expand: true
systemRoles:
  employee:
    name: Employee
    description: Every member of staff.
    permissions:
      leave.request:create: self
      leave.request:read: self
  line_manager:
    name: Line manager
    description: Assign with scope team.
    permissions:
      leave.request:read: inherit
      leave.request:approve: inherit
  hr_officer:
    name: HR officer
    description: Assign with scope org_unit.
    permissions:
      leave.request:read: inherit
      leave.request:approve: inherit
      access.assignment:read: global
      access.assignment:manage: global

Name roles after jobs people recognise, and say in the description which scope to assign them with. A system role may include access.* permissions, as HR does above, so delegated administration needs no custom roles.

Resource manifests

One per area of the product, owned by the team that owns the area:

access/plugins/leave/access.yaml
source: leave-demo/leave
resources:
  leave.request:
    attributes:
      ownerId: string
      ownerPath: string
      orgUnitId: string
      status: { enum: [pending, approved, rejected] }
      days: number
    actions:
      create:   { scopes: [self, global], description: Request leave }
      read:     { scopes: [self, team, org_unit, global], description: View leave requests }
      approve:
        scopes: [team, org_unit, global]
        description: Approve or reject leave
        condition: R.attr.status == "pending" && R.attr.ownerId != P.id
      read:reason:
        scopes: [self, team, global]
        description: See the reason given for leave
        sensitive: true

Descriptions are what admins read in the role editor; write them for people. Permissions covers how to model attributes, actions and conditions.

What zaccess check enforces

  • Every action has a description and at least one scope; the scopes exist; a scope's resource attribute is declared on the kind; self and excludeSelf need ownerId.
  • Attribute types are string, number or boolean; enum only on strings.
  • Conditions use only R.attr.<declared>, P.id, P.attr.orgId and scope principal attributes, never P.attr.perms, with balanced parentheses.
  • Kinds are unique across manifests and never access.*; system role keys are unique; access_admin can't be redeclared.
  • Every role permission exists, and its mode is one of the scopes the action allows.

What gets generated

OutputUsed by
policies/leave_request.yaml: one resource policy per kindCerbos
policies/orgs/<org>/employee.yaml: org rules as scoped policiesCerbos
policies/_schemas/: a JSON schema per kind and for the principalCerbos (enforced with reject)
access.manifest.jsonboth SDKs, at startup
ts / go constants and the embedded manifestproduct code
<tests>/<kind>_test.yaml, once per new kindyou: the scaffold is yours to fill in

Generated files are committed, never edited. CI runs zaccess check -verify, which fails when they're stale.

The change workflow

  1. Edit the manifest.
  2. zaccess gen.
  3. Update the policy tests, zaccess test.
  4. Update the code: the three checks for new actions, the resource function for new attributes, the SQL column map for list filters.
  5. Deploy. Startup SyncCatalog (or zaccess sync in the deploy) brings the catalog and system roles in line: new permissions appear in the role editor, removed ones are deprecated, a removed system role becomes an ordinary role admins can delete.

Deploy the generated policies with the code that needs them: a service that checks a permission Cerbos doesn't know is denied.

Edit on GitHub

On this page