Summary
Now that policy bundles are a first-class top-level groups: section (PR #131), the
one remaining issue is the reserved all name. all is a magic key that means
"apply to every request" — it is not something a route ever opts into, yet it sits in
the same map as the opt-in bundles. This issue tracks moving the always-on policy to a
dedicated global.authorization block (beside the existing global.authentication),
so groups: holds only opt-in bundles with no reserved names.
Split out of the PR #131 review
(araujof) because it is (a) an open design decision, (b) cross-crate, and (c) orthogonal
to that PR's thesis (RFC 8693 actor tokens + subjects). PR #131 ships groups: with
all kept working as the reserved always-on group — this is the interim state below.
Current state (as of PR #131)
- Bundles live at top-level
groups: (canonical); global.policies: is a deprecated
alias, merged into the same internal map at parse time.
all is still a reserved key inside that bundle map. It works as it always has:
- its
plugins are always included (cpex-core::config::resolve_plugins_for_entity,
step 1: config.global.policies.get("all"));
- the identity resolver's always-on layer is the separate
global.authentication:
block (GlobalConfig.identity), not the all group.
- This is effectively option (a) below, documented as interim in the cookbook's
scoping table.
The decision: (a) vs (b)
(a) Keep all as a reserved key inside groups:. Document that groups.all
applies to every request and every other key is opt-in via a route's groups: / tags.
Minimal change; keeps the magic name.
(b) Retire the magic name. Move the always-on
policy to a dedicated global.authorization block, symmetric with the existing
global.authentication. Then groups: holds only opt-in bundles — no reserved names,
three clean top-level concerns:
global:
authentication: [jwt-user] # always-on identity (exists today)
authorization: # always-on policy (NEW — replaces groups.all)
pre_invocation:
- "require(role.employee)"
groups: # opt-in bundles only, no reserved names
hr-tools:
authentication: [jwt-manager]
authorization:
pre_invocation: [ "require(role.hr)" ]
routes:
- tool: get_compensation
groups: hr-tools
Scope
The always-on all layer is applied in two places, so both must move:
- cpex-core —
resolve_plugins_for_entity step 1 reads global.policies.get("all")
for always-on plugins. Point this at global.authorization (+ the always-on
plugins it carries).
- apl-cpex — always-on authorization (APL) steps compile through the visitor's
layers. Confirm how the all bundle's authorization: steps reach every route today
(tag_layers["all"] vs global_layer) and route them from global.authorization
instead.
global.authorization should accept the same shape as a group's authorization: /
plugins (reuse PolicyGroup or a focused subset).
Back-compat (required)
all (at groups.all and the deprecated global.policies.all) must keep working
as a deprecated alias for global.authorization, merged at parse time. Emit a
deprecation tracing::warn! when the all spelling is used.
- Existing configs (the CPEX demo
cpex.yaml, praxis fixtures) must load unchanged.
Acceptance criteria
Summary
Now that policy bundles are a first-class top-level
groups:section (PR #131), theone remaining issue is the reserved
allname.allis a magic key that means"apply to every request" — it is not something a route ever opts into, yet it sits in
the same map as the opt-in bundles. This issue tracks moving the always-on policy to a
dedicated
global.authorizationblock (beside the existingglobal.authentication),so
groups:holds only opt-in bundles with no reserved names.Split out of the PR #131 review
(araujof) because it is (a) an open design decision, (b) cross-crate, and (c) orthogonal
to that PR's thesis (RFC 8693 actor tokens + subjects). PR #131 ships
groups:withallkept working as the reserved always-on group — this is the interim state below.Current state (as of PR #131)
groups:(canonical);global.policies:is a deprecatedalias, merged into the same internal map at parse time.
allis still a reserved key inside that bundle map. It works as it always has:pluginsare always included (cpex-core::config::resolve_plugins_for_entity,step 1:
config.global.policies.get("all"));global.authentication:block (
GlobalConfig.identity), not theallgroup.scoping table.
The decision: (a) vs (b)
(a) Keep
allas a reserved key insidegroups:. Document thatgroups.allapplies to every request and every other key is opt-in via a route's
groups:/ tags.Minimal change; keeps the magic name.
(b) Retire the magic name. Move the always-on
policy to a dedicated
global.authorizationblock, symmetric with the existingglobal.authentication. Thengroups:holds only opt-in bundles — no reserved names,three clean top-level concerns:
Scope
The always-on
alllayer is applied in two places, so both must move:resolve_plugins_for_entitystep 1 readsglobal.policies.get("all")for always-on plugins. Point this at
global.authorization(+ the always-onpluginsit carries).layers. Confirm how the
allbundle'sauthorization:steps reach every route today(
tag_layers["all"]vsglobal_layer) and route them fromglobal.authorizationinstead.
global.authorizationshould accept the same shape as a group'sauthorization:/plugins(reusePolicyGroupor a focused subset).Back-compat (required)
all(atgroups.alland the deprecatedglobal.policies.all) must keep workingas a deprecated alias for
global.authorization, merged at parse time. Emit adeprecation
tracing::warn!when theallspelling is used.cpex.yaml, praxis fixtures) must load unchanged.Acceptance criteria
global.authorizationblock accepted and applied always-on for both authorizationsteps and plugins.
all(both locations) still works, merged intoglobal.authorization, with adeprecation warning.
tests proving an always-on rule from
global.authorizationfires on a route thatdeclares no groups.
global.authorization; drop the"reserved
all" interim note.configuration.mddocumentsglobal.authorizationand thealldeprecation.cargo fmt --check+cargo clippy -- -D warningsclean; workspace green.make lint)