Skip to content
AI & Agents
Skill

/declaring-plugin-permissions

Interactively authoring an access-manager permission-declaration manifest (permissions.yaml) for the access-manager "inversão de responsabilidade": drives a plugin team through discovering its real Authorize() surface, normalizing every action to the SEMANTIC standard (never

From plugin
ring
20577 skills42 agents1 command
Install
$ npx -y skills add LerianStudio/ring --skill declaring-plugin-permissions --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/declaring-plugin-permissions

Context preview

The summary Claude sees to decide when to auto-load this skill.

Interactively authoring an access-manager permission-declaration manifest (permissions.yaml) for the access-manager "inversão de responsabilidade": drives a plugin team through discovering its real Authorize() surface, normalizing every action to the SEMANTIC standard (never

SKILL.md

declaring-plugin-permissions.SKILL.md
name: ring:declaring-plugin-permissions
description: >-
  Interactively authoring an access-manager permission-declaration manifest
  (permissions.yaml) for the access-manager "inversão de responsabilidade": drives
  a plugin team through discovering its real Authorize() surface, normalizing every
  action to the SEMANTIC standard (never HTTP verbs), declaring roles/group grants
  and the M2M contract, then emits and validates a manifest that matches the
  lib-auth/v3 auth/declaration schema. Use when a plugin must publish its own
  permissions at boot (WireFromEnv) instead of the access-manager seed owning them,
  or when writing/fixing a permissions.yaml. It also bumps the repo's
  github-actions-shared-workflows CI pin to the release carrying the
  permission-manifest nudge. Skip when the plugin has no auth guards,
  or you only need the wiring (see WireFromEnv) and the manifest already exists.
allowed-tools:
  - AskUserQuestion
  - Read
  - Grep
  - Glob
  - Write
  - Edit
  - Bash

Declaring Plugin Permissions

Drive a Lerian plugin team through authoring `permissions.yaml` — the client-side, SEMANTIC declaration the plugin PUBLISHES at boot under the access-manager inversion. This is an **interactive workflow**: follow the steps in order and use `AskUserQuestion` to gather every choice. Do not free-hand a manifest.

Overview

Schema authority: `lib-auth/v3 auth/declaration/manifest.go` (>= `v3.4.0-beta.1`), mirrored server-side by `plugin-access-manager identity/pkg/model/declaration.go`. The reconciler validates this exact shape at boot and refuses a bad manifest.

**THE CRITICAL INVARIANT:** every `(resource, action)` pair in the manifest MUST exactly match a real `AuthClient.Authorize(service, resource, action)` guard in the plugin (`lib-auth auth/middleware/middleware.go`). If they diverge, authz silently breaks — the guard demands a permission the manifest never declared. Adopting the semantic standard therefore means the **route guards AND the manifest move together**.

  • CANONICAL model (semantic, complies): `br-sisbajud`

(`internal/auth/declaration/permissions.yaml`).

  • ANTI-EXAMPLE (HTTP verbs, do NOT copy): `midaz-fees` — its guards pass

`Authorize("plugin-fees","estimates","post")`. Legacy. Never emit verb actions.

When to use

  • A plugin must publish its own permissions at boot (inversion) — authoring a new

`permissions.yaml`.

  • Fixing or migrating a plugin whose guards/manifest use HTTP verbs to the semantic

standard.

Skip when

  • The plugin has no `Authorize(...)` guards / no RBAC surface.
  • Only the wiring is needed and the manifest already exists — point to

`authdecl.WireFromEnv` and stop.

The naming standard (non-negotiable)

The schema doc comment says verbatim: *"The action is SEMANTIC (create/read/update/delete), never an HTTP verb."*

| HTTP verb (FORBIDDEN) | Semantic action (REQUIRED) | |-----------------------|----------------------------| | post | create | | get | read | | put / patch | update | | delete (method) | delete / remove |

Domain verbs are first-class and encouraged where CRUD does not fit: `rotate`, `trigger`, `justify`, `generate`, `reprocess`, `read_pii`, `request_read`, `request_write`, `receive`. Keep them; do not force them into CRUD.

**This is ENFORCED, not just advised.** `post`/`get`/`put`/`patch` are rejected by the lib-auth manifest validator at boot AND by the `check-manifest-actions` CI guard (Step 9). Only `delete` among HTTP methods is allowed — it is also a valid semantic action. Never emit `post`/`get`/`put`/`patch` as an `action`.

Manifest schema (author against THIS)

Top-level YAML: `service` (str, REQUIRED), `version` (int, REQUIRED), `permissions` (list), `roles` (list), `m2m` (object). All bare-name rules below: **the server composes the prefix — never pre-prefix.**

| Field | Rule | |-------|------| | `service` | REQUIRED, non-empty, no `.`/`..` segment. KEEP any `plugin-` prefix. MUST equal the M2M app slug AND DisplayName (BOLA, enforced at boot). Also the 1st arg of `Authorize`. | | `version` | REQUIRED int >= 1. ADVISORY — excluded from content hash; bumping alone is a no-op publish. | | `permissions[].resource` | REQUIRED, **BARE** (server composes `{service}/`). | | `permissions[].action` | REQUIRED, **SEMANTIC** — never an HTTP verb. | | `permissions[].effect` | `allow` or `deny` ONLY. | | `permissions[].roles` | >= 1 BARE role name, each MUST be declared in `roles:`. | | `roles[].name` | REQUIRED, BARE. `/` allowed as hierarchy separator (`fees/editor`). | | `roles[].granted_to` | list of `{ group: <bare-name> }`. **GROUP-ONLY** — there is no `user` grantee. Server composes the `{owner}/` prefix. | | `m2m.exposed` | bool — this plugin is callable as an M2M target. | | `m2m.needs` | list of target service slugs this plugin CALLS via M2M (e.g. `midaz`). |

Composed names the server builds: permission `{service}/{resource}:{action}`, role `{service}/{name}`, group `{owner}/{group}`.

See `template.permissions.yaml` in this folder for a compact, valid, commented example.

---

Interactive workflow — follow in order

Step 1 — Determine `service`

Grep the plugin for its product/slug constant before asking:

grep -rn -iE "ProductName|ApplicationName|ModuleName|feesApplicationName|Slug\s*=" \
  --include="*.go" <plugin-root> | head

Propose the found constant as the default. Confirm with `AskUserQuestion`, and **warn**: it MUST equal the M2M app DisplayName and be the first arg of every `Authorize(...)` call (BOLA). Keep any `plugin-` prefix.

Step 2 — Discover the REAL authorization surface

Enumerate what the code actually enforces today:

grep -rn "\.Authorize(" --include="*.go" <plugin-root>

Extract each `(service, resource, action)` triple from the guard chains (and route tables). Present the full list. **This list is ground truth** — the manifest must cover exactly these pairs (Step 8 re-checks).

Step 3 — Normalize actions to the SEMANTIC

Read more
Ships withring

Proven engineering practices, enforced through skills. Ring is a comprehensive skills library and workflow system for AI agents that transforms how AI assistants approach software development.

Get the whole plugin

Other skills on ring.