Register an application
The core hardcodes no permissions. Every consuming app declares what it needs in a manifest, and
the Application Registry turns that declaration into governed policy. This is the package’s central
design decision — and its moat.
Motivation
If the server shipped a fixed permission list, every new app would need a core release. Instead, apps own
their permission vocabulary and submit it as data. The registry validates, diffs, approves, applies and
can roll back — so policy changes are reviewable and reversible, and the core stays generic.
The manifest
A manifest declares an app’s permissions, roles, scopes and ABAC conditions. Slugs are immutable and
namespaced app_key:permission:
{
"app_key": "warehouse",
"permissions": [
{ "key": "warehouse:stock.read", "label": "Read stock" },
{ "key": "warehouse:stock.adjust", "label": "Adjust stock",
"condition": { "attr": "amount", "op": "<=", "value": 1000 } },
{ "key": "warehouse:stock.write", "label": "Write stock", "relation": "editor" }
],
"roles": [
{ "key": "warehouse:operator", "permissions": ["warehouse:stock.read", "warehouse:stock.adjust"] },
{ "key": "warehouse:supervisor", "permissions": ["warehouse:stock.write"], "inherits": ["warehouse:operator"] }
]
}
conditionattaches an ABAC rule evaluated against request
context.relationbinds a permission to a ReBAC relation, so the check also
consults the relationship graph.inheritscomposes roles.
The lifecycle
Submit
curl -X POST https://iam.example.com/api/iam/v1/applications/warehouse/manifests \ -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" \ -d @warehouse-manifest.jsonManifestValidatorrejects malformed slugs, unknown operators and dangling role references up front.Diff — see exactly what would change before committing:
curl https://iam.example.com/api/iam/v1/manifests/{id}/diff -H "Authorization: Bearer $ADMIN_TOKEN"ManifestDiffercompares the submission against the currently-applied manifest: added/removed
permissions, changed conditions, role deltas.Approve (or reject)
curl -X POST https://iam.example.com/api/iam/v1/manifests/{id}/approve -H "Authorization: Bearer $ADMIN_TOKEN"Apply
curl -X POST https://iam.example.com/api/iam/v1/manifests/{id}/apply \ -H "Authorization: Bearer $ADMIN_TOKEN" -H "Idempotency-Key: $(uuidgen)"ManifestApplierwrites the new catalog state atomically and records an audit entry.Rollback if needed
curl -X POST https://iam.example.com/api/iam/v1/manifests/{id}/rollback -H "Authorization: Bearer $ADMIN_TOKEN"
From the CLI
The same operations are available offline for CI pipelines:
php artisan iam:manifest:validate warehouse-manifest.json
php artisan iam:manifest:apply warehouse-manifest.json --approve --by=ci-bot
php artisan iam:manifest:rollback warehouse
See the CLI reference.
ADR — why declared manifests instead of a core permission table
Problem. A hardcoded permission catalog couples every app’s vocabulary to the server’s release cycle and
makes the core a bottleneck.
Decision. Apps declare permissions/roles/scopes/conditions as a manifest; the registry validates, diffs,
approves, applies and can roll back. The core stores, never authors, policy.
Consequences. Apps evolve independently and safely; every policy change is reviewable (diff) and
reversible (rollback) and audited. The cost is a submission workflow instead of a migration — which is
exactly the governance you want for authorization.
A permission slug is an immutable identity (app_key:permission). Renaming means adding a new permission
and migrating grants — the differ will show a remove + add, not a rename. Plan your namespace before you
ship it.
Next
- Manifests & declared policy — the concept in depth.
- Ask the PDP — evaluate the permissions you just declared.
- Admin API reference — the full applications/manifests surface.