You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
importsAliases on the policy — fan out subject operations to multiple entries additions targets
While powerful, this results in a complex model with multiple interacting concepts spread across different parts of the policy. This issue proposes a simplification that achieves the same functionality with fewer, more intuitive building blocks.
Proposal
Replace entriesAdditions and importsAliases with two new entry-level fields:
importReference on policy entries
An entry can declare an importReference that points to a specific entry in an imported policy. The entry inherits resources and namespaces from the referenced template entry, while defining its own local subjects (and optionally resources/namespaces if the template permits via allowedImportAdditions).
alias on policy entries
Multiple entries can share the same alias label. API operations on the alias (e.g., PUT /entries/{alias}/subjects/{subjectId}) fan out to all entries declaring that alias. This replaces importsAliases without introducing a separate top-level concept.
What changes
Removed concept
Replaced by
entriesAdditions on imports
local subjects/resources/namespaces on entry
importsAliases on policy
alias field on entries
entries filter on import
importReference on individual entries (always explicit)
importable: "explicit" vs "implicit"
every importReference is explicit by nature
What stays
Concept
Reason
transitiveImports
explicit control over multi-level resolution depth (no surprises)
allowedImportAdditions
template author retains control over what consumers may customize
importable: "never"
template author can prevent entries from being referenced
Example: 3-level policy hierarchy
A fleet management scenario with three levels:
Template defines roles with resources (what a driver can do)
Intermediate assigns regional drivers (who is a driver)
The intermediate has no inline entries — it only defines entriesAdditions on its import. The subjects and the entry they target are nested inside the import declaration. The intermediate's structure is opaque: reading it doesn't reveal what entries it provides.
The intermediate declares an explicit entry "driver" with an importReference to the template. Resources and namespaces are inherited; subjects are local. The policy is self-describing — reading it reveals it has a "driver" entry linked to the template.
The leaf declares a "driver" entry with importReference to the intermediate. It adds a truck-specific subject (charlie). transitiveImports ensures the intermediate's reference to the template is resolved first.
Same resolved result: resources from template + subjects from intermediate (alice, bob) + subjects from leaf (charlie).
Side-by-side comparison
Current (entriesAdditions)
Proposed (importReference)
Subject declaration
nested in imports.*.entriesAdditions.*.subjects
directly in entries.*.subjects
Entry visibility
intermediate has no entries (opaque until resolution)
intermediate has explicit entries (self-describing)
Entry selection
entries filter on the import + entriesAdditions keys
importReference on individual entries
Fan-out (multi-entry)
separate importsAliases top-level concept
alias field on entries
Subject API path
PUT /entries/{alias}/subjects/... (via importsAliases)
PUT /entries/{alias}/subjects/... (via alias field)
Summary
The Ditto 3.9.0 policy import model (not yet released) introduces several new concepts to enable multi-level policy template hierarchies:
entriesAdditionson imports — merge additional subjects/resources/namespaces into imported entriesallowedImportAdditionson template entries — control what importing policies may addtransitiveImportson imports — explicit multi-level resolution (Support transitive resolution of policy imports via transitiveImports #2420)importsAliaseson the policy — fan out subject operations to multiple entries additions targetsWhile powerful, this results in a complex model with multiple interacting concepts spread across different parts of the policy. This issue proposes a simplification that achieves the same functionality with fewer, more intuitive building blocks.
Proposal
Replace
entriesAdditionsandimportsAliaseswith two new entry-level fields:importReferenceon policy entriesAn entry can declare an
importReferencethat points to a specific entry in an imported policy. The entry inherits resources and namespaces from the referenced template entry, while defining its own local subjects (and optionally resources/namespaces if the template permits viaallowedImportAdditions).aliason policy entriesMultiple entries can share the same
aliaslabel. API operations on the alias (e.g.,PUT /entries/{alias}/subjects/{subjectId}) fan out to all entries declaring that alias. This replacesimportsAliaseswithout introducing a separate top-level concept.What changes
entriesAdditionson importssubjects/resources/namespaceson entryimportsAliaseson policyaliasfield on entriesentriesfilter on importimportReferenceon individual entries (always explicit)importable: "explicit"vs"implicit"importReferenceis explicit by natureWhat stays
transitiveImportsallowedImportAdditionsimportable: "never"Example: 3-level policy hierarchy
A fleet management scenario with three levels:
Current approach (Ditto 3.9.0 as implemented)
Template (
acme:fleet-roles):{ "policyId": "acme:fleet-roles", "entries": { "driver": { "subjects": {}, "resources": { "thing:/features/location": { "grant": ["READ"], "revoke": [] }, "thing:/features/fuel": { "grant": ["READ"], "revoke": [] }, "message:/features/fuel/inbox": { "grant": ["WRITE"], "revoke": [] } }, "namespaces": ["acme.vehicle"], "allowedImportAdditions": ["subjects"], "importable": "implicit" } } }Intermediate (
acme:fleet-west):{ "policyId": "acme:fleet-west", "imports": { "acme:fleet-roles": { "entriesAdditions": { "driver": { "subjects": { "oauth2:alice@acme.com": { "type": "employee" }, "oauth2:bob@acme.com": { "type": "employee" } } } } } }, "entries": {} }The intermediate has no inline entries — it only defines
entriesAdditionson its import. The subjects and the entry they target are nested inside the import declaration. The intermediate's structure is opaque: reading it doesn't reveal what entries it provides.Leaf (
acme.vehicle:truck-42):{ "policyId": "acme.vehicle:truck-42", "imports": { "acme:fleet-west": { "entries": ["driver"], "entriesAdditions": { "driver": { "subjects": { "oauth2:charlie@acme.com": { "type": "temp-driver" } } } }, "transitiveImports": ["acme:fleet-roles"] } }, "entries": { "owner": { "subjects": { "oauth2:fleet-admin@acme.com": { "type": "admin" } }, "resources": { "policy:/": { "grant": ["READ", "WRITE"], "revoke": [] } } } } }The leaf must:
"entries": ["driver"]to select which entries to importentriesAdditionsto add truck-specific subjectstransitiveImports: ["acme:fleet-roles"]so the intermediate's import of the template is resolvedThe resolved "driver" entry gets: resources from template + subjects from intermediate (alice, bob) + subjects from leaf (charlie).
Proposed approach (simplified)
Template (
acme:fleet-roles) — unchanged:{ "policyId": "acme:fleet-roles", "entries": { "driver": { "subjects": {}, "resources": { "thing:/features/location": { "grant": ["READ"], "revoke": [] }, "thing:/features/fuel": { "grant": ["READ"], "revoke": [] }, "message:/features/fuel/inbox": { "grant": ["WRITE"], "revoke": [] } }, "namespaces": ["acme.vehicle"], "allowedImportAdditions": ["subjects"], "importable": "implicit" } } }Intermediate (
acme:fleet-west):{ "policyId": "acme:fleet-west", "imports": { "acme:fleet-roles": {} }, "entries": { "driver": { "importReference": { "import": "acme:fleet-roles", "entry": "driver" }, "subjects": { "oauth2:alice@acme.com": { "type": "employee" }, "oauth2:bob@acme.com": { "type": "employee" } } } } }The intermediate declares an explicit entry "driver" with an
importReferenceto the template. Resources and namespaces are inherited; subjects are local. The policy is self-describing — reading it reveals it has a "driver" entry linked to the template.Leaf (
acme.vehicle:truck-42):{ "policyId": "acme.vehicle:truck-42", "imports": { "acme:fleet-west": { "transitiveImports": ["acme:fleet-roles"] } }, "entries": { "driver": { "importReference": { "import": "acme:fleet-west", "entry": "driver" }, "subjects": { "oauth2:charlie@acme.com": { "type": "temp-driver" } } }, "owner": { "subjects": { "oauth2:fleet-admin@acme.com": { "type": "admin" } }, "resources": { "policy:/": { "grant": ["READ", "WRITE"], "revoke": [] } } } } }The leaf declares a "driver" entry with
importReferenceto the intermediate. It adds a truck-specific subject (charlie).transitiveImportsensures the intermediate's reference to the template is resolved first.Same resolved result: resources from template + subjects from intermediate (alice, bob) + subjects from leaf (charlie).
Side-by-side comparison
entriesAdditions)importReference)imports.*.entriesAdditions.*.subjectsentries.*.subjectsentriesfilter on the import +entriesAdditionskeysimportReferenceon individual entriesimportsAliasestop-level conceptaliasfield on entriesPUT /entries/{alias}/subjects/...(via importsAliases)PUT /entries/{alias}/subjects/...(via alias field)entriesAdditions,allowedImportAdditions,importsAliases,entriesfilter,importableimportReference,alias,allowedImportAdditions,importable: "never"Multi-namespace fan-out example
A power plant template with entries scoped to different namespaces:
{ "policyId": "energy:plant-roles", "entries": { "reactor-operator": { "subjects": {}, "resources": { "thing:/features/reactor": { "grant": ["READ", "WRITE"], "revoke": [] } }, "namespaces": ["plant.reactor"], "allowedImportAdditions": ["subjects"] }, "turbine-operator": { "subjects": {}, "resources": { "thing:/features/turbine": { "grant": ["READ", "WRITE"], "revoke": [] } }, "namespaces": ["plant.turbine"], "allowedImportAdditions": ["subjects"] } } }Consuming policy with
aliasfor fan-out:{ "imports": { "energy:plant-roles": {} }, "entries": { "reactor-op": { "importReference": { "import": "energy:plant-roles", "entry": "reactor-operator" }, "alias": "operator" }, "turbine-op": { "importReference": { "import": "energy:plant-roles", "entry": "turbine-operator" }, "alias": "operator" } } }PUT /entries/operator/subjects/aliceadds alice to both entries — each retaining its own namespace scope. NoimportsAliasesneeded.Resolution semantics
importReferenceinherits resources, namespaces,allowedImportAdditions, andimportablefrom the referenced entrysubjects(and optionallyresources/namespacesifallowedImportAdditionspermits) are merged with the inherited valuestransitiveImportson the import controls whether the imported policy's ownimportReferencechains are resolved (explicit opt-in, no surprises)