elsa-core/docs
Sipke Schoorstra 3921715060
docs: authorization model design (spec 013) (#7978)
* docs: add authorization model design (spec 013)

Replaces Elsa's ad-hoc permission vocabulary with a structured two-axis
model. Design only -- no code changes.

A census of all 150 permission-declaring endpoints found the current
vocabulary has no model behind it: "read:*" is a literal claim value
rather than a pattern, so it authorizes 12 of roughly 40 read endpoints;
57 permission strings appear as inline literals across 174 call sites in
three competing naming schemes; omitting a declaration fails open; and
four parallel enforcement mechanisms leave no single place to audit.

A permission becomes {resource}:{verb}, with both axes open and
string-keyed and contributed by modules through descriptors. A trailing
wildcard matches the named node and all descendants, so workflows/*:view
is a single grant covering definitions, instances, executions and every
descriptor endpoint, including ones registered in later releases.
Wildcards are the only construct with forward reach; there are no
aggregates and no verb implies another. Coherence without closure comes
from a recommended core verb set as convention, per Principle III.

A closed verb enumeration was drafted and rejected: fitting the census to
seven verbs forced six mappings, invented three sub-resources, and every
open question it produced was an artefact of the closure.

Contents:
- spec.md: 41 functional requirements, 9 user stories, 7 success criteria
- plan.md: 5 milestones, constitution check, project structure
- research.md: grounded assessment and decision record D1-D23
- contracts/permissions.md: resource tree plus the full migration mapping,
  verified complete against all 57 literal permissions in the codebase
- contracts/rest-api.md: catalog, reach report, and introspection
- tasks.md: 63 tasks across 5 phases, tagged by user story

Breaking changes are documented in the spec and tracked in the issue:
legacy permission strings stop authorizing; the migration expands rather
than renames, because several sub-resources are granularity increases;
read:* and exec:* become materially more powerful; and the C#/Python
expression permissions are removed rather than translated, which is a
deliberate reduction in control.

Refs #7974, #7972, #7975

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: scope the evaluator consolidation to permission checks

FR-016 and FR-017 swept in the mid-handler AuthorizeAsync calls, which in
the workflow API are the NotReadOnlyPolicy checks. Those enforce
deployment read-only mode -- whether the instance accepts mutations at
all -- which is orthogonal to whether a principal holds a permission. A
workflow author with full grants is still refused while the deployment is
read-only, and correctly so. Folding them into the permission evaluator
would conflate two independent axes and make read-only mode expressible
as a grant, which it must not be.

The consolidation still covers four parallel mechanisms, but not the same
four: FastEndpoints permissions, named ASP.NET policies (3 sites),
hand-rolled claim inspections (15 files), and SignalR hub checks (4 hubs).

- FR-016 scoped to permission decisions, with the separate axis named
- FR-017 states the NotReadOnlyPolicy exclusion and why
- FR-018 said "scope value"; corrected to "verb" after D13 opened the
  verb axis
- SC-003, the plan's constitution row, scale figures and milestone 3
  updated to match
- D5 and D6 marked where they still reference the withdrawn mask
- D24 records the correction rather than rewriting the assessment
- Permission string count corrected from 56 to the verified 57

Refs #7974

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: apply module-owner review outcomes to the authorization model

Resolves the five open vocabulary questions and the two model gaps they
surfaced.

- External Authentication descriptors get their own resource,
  external-authentication/descriptors:view, as a single node. One legacy
  permission governs all six endpoints, which is the same principle that
  gives workflows/descriptors nine separate resources -- those were
  separately permissioned already. The tree reflects the API in both cases.
- /user-options stays on identity-links:view. It is a user search backing
  the link picker and the linking UI cannot function without it. Recorded
  consequence: identity-link rights confer tenant-wide user enumeration in
  a reduced projection, without identity/users:view.
- The roles:assign descriptor is corrected to describe what it guards.
  Setting defaultRoleIds is guarded by the ordinary subset rule, so no
  escalation was possible either way.
- The two Broker/Logout.cs endpoints declare differently: Logout is
  authenticated-only because it reads the session claim from the
  principal, ContinueLogout is anonymous because the route handle carries
  the authority. ContinueLogout inheriting the authenticated default today
  is a probable live bug -- the identity provider redirects the browser
  there during upstream logout, possibly after the Elsa session is gone.
  The fail-closed gate surfaced it; this work did not introduce it.
- T028 splits four ways along resource-group seams (31/20/15/12 files)
  rather than landing as one 78-file pull request.

Two model gaps followed, one closed and one recorded:

- FR-019 now accepts a third declaration state, authenticated-only.
  Logout needs an identity but no grant, which the two-state rule could
  not express without either a fabricated permission or a gate exemption,
  and an exemption list is a hole in a fail-closed guarantee.
- Conjunctive requirements remain unexpressible. An endpoint declares one
  resource and one verb, so "needs link rights and user read" cannot be
  stated declaratively. Recorded so the next case is not solved ad hoc.

Refs #7974

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: link the authorization review follow-ups

Refs #7976, #7977

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: mark T062 complete

The five module-owner questions are resolved and folded into the
vocabulary, so Phase 2 is unblocked.

Refs #7974

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: address review findings on the authorization model

Automated review on #7978 surfaced several genuine gaps. Two changed the
model rather than the prose.

A bare `*` now parses as `*:*`. FR-021 forbids a superuser sentinel while
D2 requires a stored `*` to keep authorizing so no instance locks itself
out, and the seed default is ["*"]. These are reconciled at the parse
layer rather than the evaluation layer: a string with no colon consisting
solely of `*` normalizes to resource `*`, verb `*`. The evaluator never
sees a sentinel, so FR-021 holds.

Wildcards are validated structurally, not against the catalog.
`workflows/*` matches no single descriptor and `*` is deliberately absent
from supported verbs, so naive descriptor validation would have rejected
the grants US1 is built on. Concrete resources and verbs validate against
the registry; wildcard segments are accepted when syntactically well
formed, including when they match nothing today, since installing a
module later is what gives such a grant meaning. Adds FR-012a and
T022a/T022b, which also close a real gap: the role write paths persist
request.Permissions after only the caller-subset check, and no task had
wired registry validation into them.

Also:
- Counts corrected to 47 resources and 23 verbs; the PR said 45/21 and
  the tracking issue 44/21, having drifted as resources were added
- Post-design constitution re-check performed and recorded, with the 17
  module-specific verbs called out as a Principle VII note
- T025 scoped to descriptor consistency; it asserted endpoint resolution
  during Phase 2, when endpoints still declare legacy strings
- T047a carries the security stamp's provider migrations, so Phase 4 no
  longer depends on Phase 5 to be shippable
- T038a covers wildcard containment in RoleAuthorizationService
- Staleness guidance corrected: the catalog and reach report are registry
  snapshots, not token projections
- Abbreviated migration rows (`:write`, `:delete`, `:update`) spelled out
  so the table is mechanically checkable
- rest-api.md now lists all three endpoints and their differing access
- American English throughout, per the constitution

Refs #7974

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: publish the migration guide alongside the contract

The vocabulary contract named docs/migrations/authorization-model.md as
the authoritative source for converting stored permissions, but the file
lived only on the implementation branch. A design change that
intentionally stops legacy grants authorizing must not point operators at
an upgrade guide it does not ship: following a dangling reference is how
roles get silently narrowed, or non-admin roles locked out, during an
upgrade.

Publishes the guide and ADR 0012 here, and marks T057-T061 complete. The
contract's reference is now a working relative link.

Refs #7974

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 16:19:22 +02:00
..
adr docs: authorization model design (spec 013) (#7978) 2026-08-24 16:19:22 +02:00
codebase Add architecture and practices documentation 2026-08-03 23:46:38 +02:00
migrations docs: authorization model design (spec 013) (#7978) 2026-08-24 16:19:22 +02:00