> ## Documentation Index
> Fetch the complete documentation index at: https://docs.alfiz.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Alfiz Catalog: Permissions, Scopes, and Navigation

> The Alfiz catalog declares every permission, scope type, and navigation entry in code, verified at compile time and never configured in a dashboard.

The catalog is the single source of truth for everything Alfiz knows about your application's permission model. You declare it once in TypeScript, call `defineCatalog()`, and from that moment every permission key in your codebase is verified at compile time against the shape you described. There is no dashboard to drift, and no runtime surprise from a misspelled key.

## What the catalog contains

The catalog is not an enum of strings. It is a structured description of four things:

* **Permissions.** Every leaf action and read your application exposes, organized into named groups.
* **Scope types.** The resource hierarchy your permissions apply to (folders, documents, projects, or nothing at all for flat apps).
* **Navigation entries.** The menu structure your UI renders, wired to permission patterns so items hide automatically for users who hold nothing under them.
* **Namespaces.** The ownership prefixes that scope every key to your application, preventing collisions when multiple permission catalogs are composed together. Permissions your application references but does *not* own, whether from Alfiz Cloud or another application, are declared separately as [imports](/catalog/imports).

None of this is inferred from call sites. Nothing is configured in a dashboard. Every structural fact is in code, at the top of your authorization module, readable by any engineer and provable by CI.

## Keys and namespaces

A permission key is a dotted string. Only one thing about its shape is structural: **the first segment must be a namespace your application owns**, declared in `namespaces`. Everything after that is yours to organize.

```
docs.files.read
docs.files.update_file
docs.files.delete

billing.invoices.read
billing.subscriptions.manage

zoom.host
```

You declare permissions using exactly these strings. The catalog is written in the same notation as every check, grant, role pattern, and nav entry, so a key at a call site greps straight back to its declaration.

The intermediate levels are **inferred**: every dotted prefix of a declared key is a group. Nothing declares them into existence, which is why depth costs nothing and why a two-segment key like `zoom.host` and a four-segment one are both perfectly ordinary catalogs.

<Note>
  `conventions: { depth }` is a **house-style setting** rather than a rule of the system. It defaults to `3`, which is what most application catalogs land on naturally, but it is one line to change and `lintCatalog` is the only thing that reads it. Set `{ depth: 2 }`, `{ depth: 4 }`, or `{ depth: "any" }` to match how you already name things.
</Note>

## `defineCatalog()`: function signature and minimal example

`defineCatalog` accepts a `CatalogInput` object and returns a typed `Catalog<C>` instance. It throws a `CatalogError` at boot when the input is structurally invalid, so a broken catalog fails immediately, before the first check.

```ts theme={null}
import { defineCatalog } from "@alfiz/core";

export const catalog = defineCatalog({
  namespaces: ["docs"],

  permissions: {
    "docs.files.read":        { description: "View files and folders", kind: "read" },
    "docs.files.update_file": { description: "Edit file contents" },
    "docs.files.delete":      {
      description: "Delete a folder and its contents",
      scopes: ["docs.folder"],
      destructive: true,
    },
  },

  scopeTypes: {
    "docs.folder": { parent: null },
    "docs.doc":    { parent: "docs.folder" },
  },

  navigation: [
    {
      label: "Files",
      href: "/files",
      permission: "docs.files.*",
    },
  ],
});
```

`defineCatalog` throws on:

* Invalid key segments (segments must match `/^[a-zA-Z][a-zA-Z0-9_]*$/`)
* Single-segment keys (the first segment is the namespace, and a namespace is a group)
* A key that is also a group path, since `docs.files` declared alongside `docs.files.read` would be both a folder and a leaf
* Duplicate permission keys
* Keys outside the declared namespaces
* References to undeclared scope types

House conventions such as key depth, the naming floor, action naming style and nav reference validity are reported by `lintCatalog` and surfaced in CI by `alfiz-verify`, never at boot. The split is deliberate: what is structurally broken fails at boot, and what is merely off-convention is a CI finding you can configure or switch off.

## Derived template-literal types

When you pass a literal object to `defineCatalog`, TypeScript derives a union of every valid key from your input shape. Those unions flow into the typed properties `catalog.$key` and `catalog.$pattern`, and Alfiz's check methods are typed against them:

```ts theme={null}
// "docs.files.read" | "docs.files.update_file" | "docs.files.delete"
//   plus the twelve built-in alfiz_internal.* keys, unless the catalog
//     was built with includeAlfizInternal: false.
type MyKey = typeof catalog.$key;

// Every key is checked at compile time:
await alfiz.can(actor, "docs.files.read");          // ✅
await alfiz.can(actor, "docs.files.reead");         // ✖ TypeScript error
await alfiz.canAny(actor, "docs.files.*");          // ✅ wildcard pattern
await alfiz.canAny(actor, "docs.typo.*");           // ✖ TypeScript error
```

Two utility types let you reference these unions by name without re-declaring them:

```ts theme={null}
import type { KeyOf, PatternOf } from "@alfiz/core";

type MyPermissionKey     = KeyOf<typeof catalog>;
type MyPermissionPattern = PatternOf<typeof catalog>;
```

Use `KeyOf` and `PatternOf` to type function parameters that receive permission keys or patterns at runtime.

## Emitting the catalog for `alfiz-verify`

`alfiz-verify` reads your catalog from a JSON snapshot rather than importing your source module directly. Emit the snapshot with:

```bash theme={null}
node --experimental-strip-types -e \
  'import("./src/alfiz.ts").then(m => console.log(JSON.stringify(m.catalog.toDocument())))' \
  > alfiz-catalog.json
```

<Note>
  `--experimental-strip-types` needs **Node 22.6 or newer**. On an older runtime, compile first and run the emitter against the built output.
</Note>

`toDocument()` returns a `CatalogDocument`, a serializable snapshot of the catalog, which `alfiz-verify` reads to check coverage, gate shapes, and naming conventions. Add this step to your CI pipeline before running the verifier.

<Note>
  The `alfiz_internal.*` permissions (access administration, access requests, audit log, and catalog management) are included in every catalog by default. Set `includeAlfizInternal: false` in your `CatalogInput` only when your application renders no Alfiz administration surface.
</Note>

## What's next

<CardGroup cols={3}>
  <Card title="Permissions" href="/catalog/permissions">
    Keys, kinds, wildcards, and the naming floor
  </Card>

  <Card title="Scope Types" href="/catalog/scope-types">
    Resource hierarchies and per-type grantability
  </Card>

  <Card title="Navigation Wiring" href="/catalog/navigation-wiring">
    Menu visibility tied to real permissions
  </Card>
</CardGroup>
