---
type: Axis
title: authz-granularity (authorization-granularity axis)
description: The authorization-granularity architecture axis — how finely access decisions are made (none → resource → field → row-level/policy-as-data). A system-singleton axis whose commitment surface is the persistent-data schema. DEEP (RFC #75 5A.4 / P1 #851 → promoted 4.5.0a6 #856); worked example is the gate registry + project-rule severity overrides (the mechanism/policy split, #288) as resource-rung action-authorization.
tags: [axis, architecture, authorization, authz, granularity, policy, rfc-75, deep]
timestamp: 2026-06-18T00:00:00Z
---

# Authz-granularity (authorization-granularity axis)

> **DEEP axis (P1 #851 → 4.5.0a6 #856).** Full §6 depth with a worked example
> from the skill's own history — application authz is degenerate (single
> operator), so the load-bearing analog is the gate registry + project-rule
> severity overrides (gates.yaml mechanisms × .claude/project-rules.md policy,
> the #288 mechanism/policy split): authorization *of actions* at the resource
> rung, with the never-needed climb to per-principal authz spelled out.

## Concept

Authz-granularity is the **authorization-granularity** axis (RFC #75 §3): how
finely the system decides *what a principal may do to which resource*. The
ladder runs none (no access control; the authenticated principal can do
everything) → resource (grants are per resource-type / per object) → field
(grants are per field within a resource — this principal may read but not write
column X) → row-level/policy-as-data (access is computed per row by declarative
policy evaluated against principal + resource attributes). Its **commitment
surface is the persistent-data schema** (§4): grant tables, per-field ACLs, and
policy rows live in the schema. Its **scope is system-singleton** (§5). It is
the **product** axis behind access control = principal × authz-granularity (§5).

## Why it is an axis (discriminator test — §2)

1. **Ordered tiers** — each rung makes access decisions finer-grained.
2. **Cross-cutting bundle** — a rung dictates the grant model, the enforcement
   point (every handler's check), the policy store, and the deny/allow tests.
3. **Asymmetric migration cost** — going from resource to field/row-level means
   retrofitting a check at every field/row access path — pervasive, easy to miss
   one (a leak).
4. **Requirement-keyed selection** — falsifiable probes pick the rung.

## Schema view (the §6 entry — validated by `axis_catalog`)

```yaml
id: authz-granularity
kind: architecture-axis
scope: system-singleton
selection:
  probes:
    - "Can any authenticated principal do everything (single-operator tool)? → none."
    - "Are grants per resource-type / per object? → resource."
    - "Must some fields of a resource be restricted independently (read X but not Y)? → field."
    - "Must access be computed PER ROW from principal + resource attributes by declarative policy? → row-level/policy-as-data."
    - "Did a handler skip its access check (a missing-enforcement leak)? → the current rung's enforcement obligation was unmet."
composes_with:
  - "access control = principal × authz-granularity"
  - "audit log = principal × temporality × authorization"
applies_when: []
tiers:
  - tier: none
    commitment_delta:
      surface: persistent-data-schema
      delta: "no access control; an authenticated principal may perform any operation. No grant model."
    admin_tool: "none."
    enforcement:
      - "no per-operation check; authentication (if any) is the only gate."
    tests:
      - "any authenticated principal succeeds at any operation."
    forecloses: "differential access; everyone has full authority."
    migration_cost_to_next: "medium — add per-resource grants + a check at each resource handler."
  - tier: resource
    commitment_delta:
      surface: persistent-data-schema
      delta: "grants are per resource-type / per object; a check at each handler asks 'may this principal act on this resource?'."
    admin_tool: "grant editor (principal/role × resource → allow)."
    enforcement:
      - "every resource handler performs an authorization check before acting."
    tests:
      - "a principal without a grant is denied; with a grant, allowed; an uncovered handler is flagged."
    forecloses: "field-level distinctions within a resource."
    migration_cost_to_next: "medium — restrict individual fields within a resource."
  - tier: field
    commitment_delta:
      surface: persistent-data-schema
      delta: "grants distinguish fields within a resource; a principal may read/write some fields but not others."
    admin_tool: "per-field ACL editor."
    enforcement:
      - "field reads/writes are filtered by the principal's field grants; ungranted fields are masked/rejected."
    tests:
      - "a principal reads permitted fields, is denied/masked on the rest; a write to a restricted field is rejected."
    forecloses: "whole-resource grant simplicity."
    migration_cost_to_next: "high — compute access per row from attributes + declarative policy."
  - tier: row-level-policy
    commitment_delta:
      surface: persistent-data-schema
      delta: "access is decided per row by declarative policy (policy-as-data) evaluated against principal + resource attributes; the policy is data, not code."
    admin_tool: "policy editor + a decision explainer (why allowed/denied)."
    enforcement:
      - "every access evaluates the policy against the row + principal; the policy store is the single source of authority."
    tests:
      - "the same query returns different rows for two principals per policy; a policy change re-decides without code change."
    forecloses: "static grant tables as the only model."
    migration_cost_to_next: "n/a — top of the ladder."
examples:
  - "The skill sits at NONE for application data (single operator, no per-subject grants) but at RESOURCE for authorization-of-ACTIONS, which is the load-bearing analog. The gate registry is the grant model: gates.yaml declares the mechanisms (resource-typed: which release checks exist and which may run) and .claude/project-rules.md applies per-project policy OVERRIDES on severity (warn↔fail), the mechanism/policy split RFC made explicit in #288 — the mechanism lives in the shared catalog, the policy is project-scoped data, exactly the resource-rung 'may this actor invoke this action on this resource-type, under this project's policy?' decision. The release flow's enforcement obligation is the validate pass: a gate that would block (a fail-severity finding) denies the action (the cut), and the recurring scar is the OPPOSITE failure — a VACUOUS gate (the open-issue gate reading meta.json and going blind to milestone.txt-only issues, #715; substrate gates absent from the suite, a3-a9) is the missing-enforcement leak this axis names, an authorization check that silently admits every request. The climb to FIELD/ROW-LEVEL (per-principal, per-record authz) has never been reached because the principal axis sits at singular; the cost named here — retrofitting a check at every action path — is exactly why it has stayed at resource."
```

## Selection procedure (§8)

Bidirectional check; chosen once. Recorded in the system architecture record; a
handler that touches a resource without the declared authz check is the
missing-enforcement systems-analysis D-finding.

## Composition (§5)

Authz-granularity × principal = **access control**; × temporality × principal =
**audit log**. Rules authored in 5A.3.
