bland-finland-95044
06/09/2025, 6:38 PMrelated section of a permission model. How can I accomplish something similar to this?
class Feature implements Namespace {
related: {
// Feature requires entitlement
entitled: boolean;
// Users or customer entitled for this Feature
subscribers: (User | SubjectSet<Customer, "members">)[];
// Feature requires flag
flagged: boolean;
// Feature flag is enabled for these users
enabled_for: (User | SubjectSet<Customer, "members">)[];
}
permits = {
subscribe: (ctx: Context): boolean => !this.related.entitled || this.related.subscribers.includes(ctx.subject),
enabled_for: (ctx: Context): boolean => !this.related.flagged || this.related.enabled_for.includes(ctx.subject),
// Feature flag is enabled AND feature is entitled
activated_for: (ctx: Context): boolean => this.permits.enabled_for(ctx) && this.permits.subscribe(ctx)
};
}chilly-king-10285
06/09/2025, 6:38 PMchilly-king-10285
06/09/2025, 6:38 PMrelated section can only declare relationships to other namespaces (i.e., arrays of subjects or subject sets), not primitive types like boolean. The OPL is designed around expressing relationships between entities, and all relations must be many-to-many and reference other namespaces or subject sets, not simple values or flags [Ory Permission Language specification](https://www.ory.sh/docs/keto/reference/ory-permission-language#statements). To achieve similar logic to a boolean flag, you should model the "entitled" or "flagged" state as a relationship. For example, instead of a boolean entitled, you could have a relation like entitled_for: (User | SubjectSet<Customer, "members">)[], and then check if the subject is in that relation. If you want to represent a "global" flag, you could use a special subject (e.g., a "system" user or group) to indicate that the feature is globally enabled. Here’s how you might refactor your model: typescript class Feature implements Namespace { related: { // Users or customers entitled for this Feature entitled_for: (User | SubjectSet<Customer, "members">)[]; // Users or customers for whom the feature flag is enabled enabled_for: (User | SubjectSet<Customer, "members">)[]; } permits = { subscribe: (ctx: Context): boolean => this.related.entitled_for.includes(ctx.subject), enabled_for: (ctx: Context): boolean => this.related.enabled_for.includes(ctx.subject), // Feature flag is enabled AND feature is entitled activated_for: (ctx: Context): boolean => this.permits.enabled_for(ctx) && this.permits.subscribe(ctx), }; } If you want to support a "default allow" (i.e., if no one is entitled, then everyone is), you could model this by adding a special subject (like a "public" group) to the relation, or by handling the "empty" case in your application logic, since OPL itself does not support conditional logic based on the presence or absence of relationships [Ory Permission Language specification](https://www.ory.sh/docs/keto/reference/ory-permission-language#statements). In summary: - You cannot use booleans in related. - Model flags as relationships to special subjects or groups. - All permission logic must be expressed in terms of relationships and set membership. For more details, see the Ory Permission Language specification and examples [Ory Permission Language specification](https://www.ory.sh/docs/keto/reference/ory-permission-language#statements) [OPL Examples](https://www.ory.sh/docs/keto/reference/ory-permission-language#examples).bland-finland-95044
06/09/2025, 7:12 PMchilly-king-10285
06/09/2025, 7:12 PMchilly-king-10285
06/09/2025, 7:12 PMtraverse function is used to iterate over an array of related objects (such as parents, folders, or other namespaces) and evaluate a permission or condition for each element. It returns true if at least one of the elements in the array satisfies the provided condition (i.e., the callback function returns true for that element). For example, in the following OPL snippet: typescript class Document implements Namespace { related: { parents: Folder[]; }; permits = { view: (ctx: Context): boolean => this.related.parents.traverse((parent) => parent.permits.view(ctx)), }; } Here, this.related.parents.traverse((parent) => parent.permits.view(ctx)) checks if any parent folder grants the view permission to the subject in the context. If at least one parent folder allows viewing, the function returns true [OPL Examples](https://www.ory.sh/docs/keto/reference/ory-permission-language#examples). This is similar to the Array.prototype.some() method in JavaScript/TypeScript, where the function returns true if the callback returns true for any element in the array. Summary: - traverse iterates over related entities. - Returns true if the callback returns true for any element. - Commonly used to check permissions up a hierarchy or across related objects [OPL Examples](https://www.ory.sh/docs/keto/reference/ory-permission-language#examples).bland-finland-95044
06/09/2025, 7:16 PMrelated section of type declarations defines relations. Unlike regular TypeScript, RelationName must be a unique identifier used as the relation strings in the tuples.
Does that mean unique to the namespace or unique globally?chilly-king-10285
06/09/2025, 7:16 PMchilly-king-10285
06/09/2025, 7:16 PMRelationName must be a unique identifier refers to uniqueness within the namespace, not globally. Each namespace (represented as a class) defines its own set of relations, and the relation names must be unique within that class. This is supported by the documentation, which states: > The related section of type declarations defines relations. Unlike regular TypeScript, RelationName must be a unique identifier used as the relation strings in the tuples. The TypeName must be the name of another type that is defined above or below (in TypeScript: a class that implements Namespace) [Ory Permission Language specification](https://www.ory.sh/docs/keto/reference/ory-permission-language#statements). There is no indication in the documentation that relation names must be unique across all namespaces—only within the context of each namespace's definition.many-dawn-19625
06/10/2025, 5:39 AMchilly-king-10285
06/10/2025, 5:39 AMchilly-king-10285
06/10/2025, 5:39 AM/admin/relation-tuples endpoint, you need to provide at least the following fields in the request body: namespace, object, and relation. Additionally, you must specify either subject_id or a subject_set. This is the minimum required to define a relationship tuple in Ory Keto. Here is an example of the minimal JSON payload for the /admin/relation-tuples endpoint: json { "namespace": "feature", "object": "my-feature", "relation": "entitled_for", "subject_id": "user-alice" } This tuple states that the subject with ID user-alice is entitled for the feature my-feature in the feature namespace. You can also use a subject_set instead of subject_id if you want to refer to a group or another set of subjects: json { "namespace": "feature", "object": "my-feature", "relation": "entitled_for", "subject_set": { "namespace": "customer", "object": "customer-123", "relation": "members" } } This tuple states that all members of customer-123 are entitled for the feature my-feature. These examples follow the requirements described in the API reference for the /admin/relation-tuples endpoint [Ory Keto API - createRelationship](https://www.ory.sh/docs/keto/reference/rest-api#createrelationship).