Problem
An intent's permissions: block is the one place an author states who may do what:
permissions:
- { role: ProvisioningAdmin, can: [Tenant:read, TenantApplication:create] }
- { role: ProvisioningViewer, can: [Tenant:read] }
The generated application never checks those roles. Two disjoint namespaces exist side by side:
- Authored —
PermissionIntentGenerator emits <intent>.roles from permissions[].role. Its
own javadoc is explicit that the can: tokens are not translated into anything: "the tokens are
informational; document them but do not generate .access from them."
- Enforced —
EdmIntentGenerator gives every entity convention-derived gates
(roleRead = <project>.<perspective>.<Entity>ReadOnly,
roleWrite = <project>.<perspective>.<Entity>FullAccess, generateDefaultRoles = true).
ModelParameterProcessor.collectRoles materialises those into the app's own roles, and the
generated controller enforces them in checkPermissions("read"|"write").
So granting ProvisioningAdmin grants nothing at all — the gate the controller actually consults is
<project>.<perspective>.TenantApplicationFullAccess, a name no intent construct mentions. The
author's stated access model is silently unconsumed, which is the failure class of #6646: it does
not error, it does not warn, and the roles show up in the UI so it looks wired.
The web/UI surface has no gate at all: nothing emits an .access. Note .access is already listed
in IntentGenerationService.INTENT_OWNED_EXTENSIONS, so a hand-authored .access at the project
root is scrubbed by the next Generate — it survives only under custom/, which is not
documented anywhere and is discovered the hard way.
This needs no spec change. The specification deliberately leaves URL-shaped rules to "whichever
downstream template materialises the UI, because only that template knows the paths it publishes" —
Dirigible is that template, and it knows exactly which paths it publishes.
Ask
- Bind
can: to the entity gates. When any can: token names an entity, that entity's
roleRead / roleWrite should be the authored roles rather than (or in addition to) the
convention names, so the role the author declared is the role the controller checks. Entities no
token names keep today's convention behaviour, so existing generated apps are unchanged.
- Emit
<intent>.access for the paths the templates publish — the REST base and per-entity
controller paths (/services/java/<project>/gen/<module>/api/<perspective>/<Entity>Controller),
the generated SPA pages, the report pages — from the same tokens. Opt-in through the project's
generation recipe in .settings, not a new DSL key (the DSL surface is the spec's; the
path mapping is ours).
- Say that root-level
.access is scrub-owned in the intent docs, and keep custom/ as the
documented home for hand-authored access constraints.
Worth deciding as part of this: the action vocabulary. The controller gate is binary (read / write)
while can: tokens are free-form (create, approve, …); a mapping — or a rejection of tokens
that cannot map — is better than quietly dropping the ones that do not fit.
Reported from a service whose whole domain layer is intent-first and which had to hand-author
custom/security/*.access to gate its endpoints at all.
Problem
An intent's
permissions:block is the one place an author states who may do what:The generated application never checks those roles. Two disjoint namespaces exist side by side:
PermissionIntentGeneratoremits<intent>.rolesfrompermissions[].role. Itsown javadoc is explicit that the
can:tokens are not translated into anything: "the tokens areinformational; document them but do not generate
.accessfrom them."EdmIntentGeneratorgives every entity convention-derived gates(
roleRead = <project>.<perspective>.<Entity>ReadOnly,roleWrite = <project>.<perspective>.<Entity>FullAccess,generateDefaultRoles = true).ModelParameterProcessor.collectRolesmaterialises those into the app's own roles, and thegenerated controller enforces them in
checkPermissions("read"|"write").So granting
ProvisioningAdmingrants nothing at all — the gate the controller actually consults is<project>.<perspective>.TenantApplicationFullAccess, a name no intent construct mentions. Theauthor's stated access model is silently unconsumed, which is the failure class of #6646: it does
not error, it does not warn, and the roles show up in the UI so it looks wired.
The web/UI surface has no gate at all: nothing emits an
.access. Note.accessis already listedin
IntentGenerationService.INTENT_OWNED_EXTENSIONS, so a hand-authored.accessat the projectroot is scrubbed by the next Generate — it survives only under
custom/, which is notdocumented anywhere and is discovered the hard way.
This needs no spec change. The specification deliberately leaves URL-shaped rules to "whichever
downstream template materialises the UI, because only that template knows the paths it publishes" —
Dirigible is that template, and it knows exactly which paths it publishes.
Ask
can:to the entity gates. When anycan:token names an entity, that entity'sroleRead/roleWriteshould be the authored roles rather than (or in addition to) theconvention names, so the role the author declared is the role the controller checks. Entities no
token names keep today's convention behaviour, so existing generated apps are unchanged.
<intent>.accessfor the paths the templates publish — the REST base and per-entitycontroller paths (
/services/java/<project>/gen/<module>/api/<perspective>/<Entity>Controller),the generated SPA pages, the report pages — from the same tokens. Opt-in through the project's
generation recipe in
.settings, not a new DSL key (the DSL surface is the spec's; thepath mapping is ours).
.accessis scrub-owned in the intent docs, and keepcustom/as thedocumented home for hand-authored access constraints.
Worth deciding as part of this: the action vocabulary. The controller gate is binary (read / write)
while
can:tokens are free-form (create,approve, …); a mapping — or a rejection of tokensthat cannot map — is better than quietly dropping the ones that do not fit.
Reported from a service whose whole domain layer is intent-first and which had to hand-author
custom/security/*.accessto gate its endpoints at all.