Skip to content

Add entity design documentation for OpenFGA and API patterns#112

Merged
andrest50 merged 5 commits intomainfrom
ems/entity-docs
Mar 20, 2026
Merged

Add entity design documentation for OpenFGA and API patterns#112
andrest50 merged 5 commits intomainfrom
ems/entity-docs

Conversation

@emsearcy
Copy link
Copy Markdown
Contributor

This documentation provides comprehensive guidance for designing entities, API layouts, and OpenFGA types in the LFX v2 platform. It covers:

  • Introduction to OpenFGA permissions and relationship-based access control
  • When to create OpenFGA types vs. entity classifications for indexing
  • API design patterns and permission mapping
  • Conditional relationships and optional tuples
  • Query Service integration and best practices

Includes practical GitHub PR example and reference to voting service implementation for developers onboarding to the platform.

🤖 Assisted with GitHub Copilot (via Zed)

This documentation provides comprehensive guidance for designing entities,
API layouts, and OpenFGA types in the LFX v2 platform. It covers:

- Introduction to OpenFGA permissions and relationship-based access control
- When to create OpenFGA types vs. entity classifications for indexing
- API design patterns and permission mapping
- Conditional relationships and optional tuples
- Query Service integration and best practices

Includes practical GitHub PR example and reference to voting service
implementation for developers onboarding to the platform.

🤖 Assisted with GitHub Copilot (via Zed)

Signed-off-by: Eric Searcy <eric@linuxfoundation.org>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 20, 2026

Walkthrough

Added four new words to the spell-check configuration dictionary and created a comprehensive entity design documentation file covering OpenFGA-based permissions, ReBAC concepts, design guidance, and best practices for the LFX v2 platform.

Changes

Cohort / File(s) Summary
Spell-Check Configuration
.cspell.json
Added four accepted words to dictionary: "excalidraw", "pullrequest", "pullrequests", and "rebac".
Entity Design Documentation
docs/entity-design.md
New comprehensive guide covering OpenFGA permissions, ReBAC concepts, entity type design, indexing considerations, conditional relations, relation naming conventions, API endpoint design patterns, and detailed reference examples for the LFX v2 platform.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add entity design documentation for OpenFGA and API patterns' accurately reflects the main changeset, which adds comprehensive documentation for entity design and API patterns in the LFX v2 platform.
Description check ✅ Passed The description is directly related to the changeset, detailing the specific documentation content added including OpenFGA permissions, entity design guidance, API patterns, and conditional relationships.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ems/entity-docs

Comment @coderabbitai help to get the list of available commands and usage tips.

Signed-off-by: Eric Searcy <eric@linuxfoundation.org>
Signed-off-by: Eric Searcy <eric@linuxfoundation.org>
Signed-off-by: Eric Searcy <eric@linuxfoundation.org>
@emsearcy emsearcy marked this pull request as ready for review February 24, 2026 18:45
@emsearcy emsearcy requested a review from a team as a code owner February 24, 2026 18:45
Copilot AI review requested due to automatic review settings February 24, 2026 18:45
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds developer-onboarding documentation for LFX v2 entity modeling, tying together OpenFGA types/tuples/relationships with API design and Query Service indexing patterns.

Changes:

  • Introduces docs/entity-design.md with guidance and examples for OpenFGA modeling, conditional relationships, and API permission mapping.
  • Documents entity-type classification patterns for indexing/Query Service and attribute-set splitting for granular permissions.
  • Updates .cspell.json to allow new domain terms used in the documentation.

Reviewed changes

Copilot reviewed 2 out of 3 changed files in this pull request and generated 9 comments.

File Description
docs/entity-design.md New comprehensive design guide for entity modeling, OpenFGA patterns, and API/Query Service conventions.
.cspell.json Adds documentation-related terms to spellchecker allowlist.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/entity-design.md

## Introduction to OpenFGA permissions

OpenFGA (<https://openfga.dev>) is a relationship-based access control (ReBAC) system, which is a permission system where access decisions take into account a network of relationships between entities. Most systems implementing this derive from the Google Zanzibar paper. For example, in order to have "writer" access to a Google document, I might be a "writer" on the document itself, or I might be a "member" of a team that has "writer" access to folder which is a "parent" of yet another folder that is the "parent" of the document, or I might have "owner" access to a team drive containing that folder! The only check for granting write access is whether I have a "writer" relationship to the document, and this relation will evaluate as true even if it is an transitive relationship across _multiple_ hops in the relationship graph. This kind of access inheritance is also intuitive for users. Nobody has to show them a complex graph of relationships to understand why they have access to a given document: it just works the way they expect.
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo/grammar: "an transitive" should be "a transitive" (or rephrase the sentence) to read naturally.

Suggested change
OpenFGA (<https://openfga.dev>) is a relationship-based access control (ReBAC) system, which is a permission system where access decisions take into account a network of relationships between entities. Most systems implementing this derive from the Google Zanzibar paper. For example, in order to have "writer" access to a Google document, I might be a "writer" on the document itself, or I might be a "member" of a team that has "writer" access to folder which is a "parent" of yet another folder that is the "parent" of the document, or I might have "owner" access to a team drive containing that folder! The only check for granting write access is whether I have a "writer" relationship to the document, and this relation will evaluate as true even if it is an transitive relationship across _multiple_ hops in the relationship graph. This kind of access inheritance is also intuitive for users. Nobody has to show them a complex graph of relationships to understand why they have access to a given document: it just works the way they expect.
OpenFGA (<https://openfga.dev>) is a relationship-based access control (ReBAC) system, which is a permission system where access decisions take into account a network of relationships between entities. Most systems implementing this derive from the Google Zanzibar paper. For example, in order to have "writer" access to a Google document, I might be a "writer" on the document itself, or I might be a "member" of a team that has "writer" access to folder which is a "parent" of yet another folder that is the "parent" of the document, or I might have "owner" access to a team drive containing that folder! The only check for granting write access is whether I have a "writer" relationship to the document, and this relation will evaluate as true even if it is a transitive relationship across _multiple_ hops in the relationship graph. This kind of access inheritance is also intuitive for users. Nobody has to show them a complex graph of relationships to understand why they have access to a given document: it just works the way they expect.

Copilot uses AI. Check for mistakes.
Comment thread docs/entity-design.md
Likewise, in the previous "ACS" LFX RBAC system, we have project roles that inherit policies for how you can interact with a project: managing committees, scheduling meetings, managing mailing lists, etc. However, it cannot "fan out" access to these project resources on a contextual level:

- LFX with ACS: **project meeting-managers** can access all _past meeting recordings_ for **all meetings associated with the project**
- LFX with OpenFGA: you can access the _past meeting recording_ if you were **invited to the meeting**, or if you are currently a **member of the committee** that held this meeting in the past, or if you are a **project meeting-manager** on the project the meeting was held on"
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second bullet ends with an extra quote character. Remove the trailing '"' so the sentence punctuation is consistent.

Suggested change
- LFX with OpenFGA: you can access the _past meeting recording_ if you were **invited to the meeting**, or if you are currently a **member of the committee** that held this meeting in the past, or if you are a **project meeting-manager** on the project the meeting was held on"
- LFX with OpenFGA: you can access the _past meeting recording_ if you were **invited to the meeting**, or if you are currently a **member of the committee** that held this meeting in the past, or if you are a **project meeting-manager** on the project the meeting was held on

Copilot uses AI. Check for mistakes.
Comment thread docs/entity-design.md

**Note on "maintainer" as a role**: at this time, we do not have a "maintainer" relationship at the project level. Depending on business need, we may choose to track maintainers:
- As synonymous with the `writer` project relation: we might "brand" project admins as maintainers, understanding that the individuals who are granted this access may not directly correspond to project governance.
- As a new project relation: if maintainers (as defined by project governance) always get certain access, but it does NOT align to one of our existing roles—or we wish to us it as an abstraction for existing roles ("maintainers get both viewer and meeting manager")—we could add a new maintainer *project relation*.
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: "wish to us it" should be "wish to use it".

Suggested change
- As a new project relation: if maintainers (as defined by project governance) always get certain access, but it does NOT align to one of our existing roles—or we wish to us it as an abstraction for existing roles ("maintainers get both viewer and meeting manager")—we could add a new maintainer *project relation*.
- As a new project relation: if maintainers (as defined by project governance) always get certain access, but it does NOT align to one of our existing roles—or we wish to use it as an abstraction for existing roles ("maintainers get both viewer and meeting manager")—we could add a new maintainer *project relation*.

Copilot uses AI. Check for mistakes.
Comment thread docs/entity-design.md
If these related/attached resources have their own OpenFGA _type_, meaning they can be uniquely-referenced within OpenFGA tuples, then neither of these applies—see also the section "When to create OpenFGA types". The resource should be served from the "root" of the LFX API. However, value sets or collections may still map to _OpenFGA relations_ on their parent type (e.g. project writers, which is an attribute of project settings, and committee members, which is represented as a sub-collection, are both _relations_ of their respective types).

**In-Resource Arrays**:
- **API Design**: Data sets stored as arrays within the main resource: `GET /widget/{id}` returns `{"colors": ["red", "blue"], ...}`. Changes to the collection are made with a PUT to `/widget/{id} containing all properties, including any new/changed set of colors to save.
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Markdown formatting: the inline code span for the PUT example is missing a closing backtick after /widget/{id} which will break rendering for the rest of the paragraph.

Suggested change
- **API Design**: Data sets stored as arrays within the main resource: `GET /widget/{id}` returns `{"colors": ["red", "blue"], ...}`. Changes to the collection are made with a PUT to `/widget/{id} containing all properties, including any new/changed set of colors to save.
- **API Design**: Data sets stored as arrays within the main resource: `GET /widget/{id}` returns `{"colors": ["red", "blue"], ...}`. Changes to the collection are made with a PUT to `/widget/{id}` containing all properties, including any new/changed set of colors to save.

Copilot uses AI. Check for mistakes.
Comment thread docs/entity-design.md
- **`PUT /projects/{id}/legal`**: requires `owner` to update formation details
- **`PUT /projects/{id}/settings`**: requires `writer` relation to update non-formation sensitive data

In this example, there are *3 attributes sets*:
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: "3 attributes sets" should be "3 attribute sets".

Suggested change
In this example, there are *3 attributes sets*:
In this example, there are *3 attribute sets*:

Copilot uses AI. Check for mistakes.
Comment thread docs/entity-design.md
Comment on lines +286 to +287
- **Non-sensitive, non-formation-related metadata**: readably by viewer (everyone, for active projects), writable by writer (any admin)
- **Sensitive, non-formation-related setting**: readably by auditor, writable by writer
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typos: "readably" should be "readable" in these bullets describing attribute sets.

Suggested change
- **Non-sensitive, non-formation-related metadata**: readably by viewer (everyone, for active projects), writable by writer (any admin)
- **Sensitive, non-formation-related setting**: readably by auditor, writable by writer
- **Non-sensitive, non-formation-related metadata**: readable by viewer (everyone, for active projects), writable by writer (any admin)
- **Sensitive, non-formation-related setting**: readable by auditor, writable by writer

Copilot uses AI. Check for mistakes.
Comment thread docs/entity-design.md

**Tuple**: A relation between two _specific_ objects of any type, conforming to the model, which is stored as a fact in OpenFGA. For example: "user:alice is a member of team:frontend" or "project:platform is the parent of project:web-app". The v2 platform maintains a live sync that indexes LFX data (projects, committees, etc.) into corresponding OpenFGA tuples, in real time.

**Relationship**: A relationship is the traversal or chaining of tuples, as defined by the model, to determine if a given *transitive* relationship exists. For example, if Alice is a member of the frontend team, and the frontend team owns the web-app project, then Alice has an ownership relationship to the web-app project. Throughout this document, when we refer to a "relationship", we always an queried relationship derived from chained tuples and the model definitions, and we will use the term "tuple" when referring to a *direct* relation "fact". The v2 platform uses OpenFGA's "batch check" endpoint to query relationships.
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There’s a grammar issue in the definition of "Relationship" ("we always an queried relationship"). Rephrase so it clearly states that "relationship" refers to a queried/transitive relationship derived from tuples and the model.

Suggested change
**Relationship**: A relationship is the traversal or chaining of tuples, as defined by the model, to determine if a given *transitive* relationship exists. For example, if Alice is a member of the frontend team, and the frontend team owns the web-app project, then Alice has an ownership relationship to the web-app project. Throughout this document, when we refer to a "relationship", we always an queried relationship derived from chained tuples and the model definitions, and we will use the term "tuple" when referring to a *direct* relation "fact". The v2 platform uses OpenFGA's "batch check" endpoint to query relationships.
**Relationship**: A relationship is the traversal or chaining of tuples, as defined by the model, to determine if a given *transitive* relationship exists. For example, if Alice is a member of the frontend team, and the frontend team owns the web-app project, then Alice has an ownership relationship to the web-app project. Throughout this document, when we refer to a "relationship", we always mean a queried (possibly transitive) relationship derived from chained tuples and the model definitions, and we will use the term "tuple" when referring to a *direct* relation "fact". The v2 platform uses OpenFGA's "batch check" endpoint to query relationships.

Copilot uses AI. Check for mistakes.
Comment thread docs/entity-design.md

### When to create separate collection endpoints

When a entity or resource has an attribute which is itself a set of values or a collection of objects, it may be able to be represented either as an array within that resource, or, as its own endpoint implementing REST semantics (POST/PUT/DELETE) to add & remove entries.
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grammar: "When a entity" should be "When an entity".

Suggested change
When a entity or resource has an attribute which is itself a set of values or a collection of objects, it may be able to be represented either as an array within that resource, or, as its own endpoint implementing REST semantics (POST/PUT/DELETE) to add & remove entries.
When an entity or resource has an attribute which is itself a set of values or a collection of objects, it may be able to be represented either as an array within that resource, or, as its own endpoint implementing REST semantics (POST/PUT/DELETE) to add & remove entries.

Copilot uses AI. Check for mistakes.
Comment thread docs/entity-design.md
Comment on lines +300 to +303
| 4. Sub-collections | `committee_member:{mbr_id}` | Query Service | `/committees/{id}/` `members/{mbr_id}` | Indexed as `committee_member` - permission (API path & Query Service) is a relation to the `committee` OpenFGA type. |
| 5. Attribute sets of sub-collections | `committee_member_priv:{mbr_id}` | Query Service | `/committees/{id}/` `members/{mbr_id}/priv` | Indexed as `committee_member_priv` - permission (API path & Query Service) is a relation to the `committee` OpenFGA type. |
| 6. Sub-collections (distinct type) | `domain:{dom_id}` | Query Service | `/projects/{id}/` `domains/{dom_id}` | Functionally identical to a "sub-collection", including needing the OpenFGA type as an API path prefix, but dropping the prefix from our indexer type for brevity. |
| 7. Attribute sets of #6 | `domain_restricted:{dom_id}` | Query Service | `/projects/{id}/` `domains/{dom_id}/restricted` | Functionally identical to #5, but with the leading type dropped as with #6. |
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the Attribute naming table, the API path column splits a single path into multiple inline-code spans (e.g., /committees/{id}/ members/{mbr_id}), which renders with a space and looks like two separate tokens. Use a single code span per path (e.g., /committees/{id}/members/{mbr_id}) for clarity/copy-paste correctness.

Suggested change
| 4. Sub-collections | `committee_member:{mbr_id}` | Query Service | `/committees/{id}/` `members/{mbr_id}` | Indexed as `committee_member` - permission (API path & Query Service) is a relation to the `committee` OpenFGA type. |
| 5. Attribute sets of sub-collections | `committee_member_priv:{mbr_id}` | Query Service | `/committees/{id}/` `members/{mbr_id}/priv` | Indexed as `committee_member_priv` - permission (API path & Query Service) is a relation to the `committee` OpenFGA type. |
| 6. Sub-collections (distinct type) | `domain:{dom_id}` | Query Service | `/projects/{id}/` `domains/{dom_id}` | Functionally identical to a "sub-collection", including needing the OpenFGA type as an API path prefix, but dropping the prefix from our indexer type for brevity. |
| 7. Attribute sets of #6 | `domain_restricted:{dom_id}` | Query Service | `/projects/{id}/` `domains/{dom_id}/restricted` | Functionally identical to #5, but with the leading type dropped as with #6. |
| 4. Sub-collections | `committee_member:{mbr_id}` | Query Service | `/committees/{id}/members/{mbr_id}` | Indexed as `committee_member` - permission (API path & Query Service) is a relation to the `committee` OpenFGA type. |
| 5. Attribute sets of sub-collections | `committee_member_priv:{mbr_id}` | Query Service | `/committees/{id}/members/{mbr_id}/priv` | Indexed as `committee_member_priv` - permission (API path & Query Service) is a relation to the `committee` OpenFGA type. |
| 6. Sub-collections (distinct type) | `domain:{dom_id}` | Query Service | `/projects/{id}/domains/{dom_id}` | Functionally identical to a "sub-collection", including needing the OpenFGA type as an API path prefix, but dropping the prefix from our indexer type for brevity. |
| 7. Attribute sets of #6 | `domain_restricted:{dom_id}` | Query Service | `/projects/{id}/domains/{dom_id}/restricted` | Functionally identical to #5, but with the leading type dropped as with #6. |

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/entity-design.md`:
- Line 7: Fix multiple typos in the entity-design document by replacing specific
incorrect phrases with the provided corrections: change "it is an transitive
relationship" to "it is a transitive relationship" (remove the extra "an");
change "we always an queried relationship" to "we always mean a queried
relationship"; remove the extra parenthesis in "closer))" → "closer)"; change
"etc)" to "etc.)" (add period before close-paren); change "When a entity" to
"When an entity"; change "3 attributes sets" to "3 attribute sets"; change
"readably by viewer" to "readable by viewer"; and change "readably by auditor"
to "readable by auditor". Locate these exact strings in the document and apply
the replacements ensuring punctuation and article fixes are applied as
specified.
- Line 30: The document uses asterisk-based italics (e.g., *transitive*,
*direct*) which violates MD049; replace all instances of asterisk emphasis with
underscore emphasis (e.g., _transitive_, _direct_) on the flagged occurrences
including the "**Relationship**" paragraph and other lines referencing
"relationship" and "tuple" to conform to the configured style; ensure you change
only single-asterisk italics to single-underscore italics and leave bold
(double-asterisk) or code backticks untouched.
- Around line 50-73: The OpenFGA DSL uses '#' to reference a type's relation, so
replace all userset references using “[team:member]” with “[team#member]” and
similarly “[organization:member with is_internal_visibility]” with
“[organization#member with is_internal_visibility]”; update occurrences inside
the repository and pullrequest relation definitions (symbols: type repository,
relations define writer, define reader, type pullrequest, relations define
writer/reader/closer) and any explanatory text that mentions “[team:member]” or
“[organization:member …]” so the model uses the correct userset syntax.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Jira integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 223082c and a71ab0a.

⛔ Files ignored due to path filters (1)
  • docs/google-doc-rebac.excalidraw.png is excluded by !**/*.png
📒 Files selected for processing (2)
  • .cspell.json
  • docs/entity-design.md

Comment thread docs/entity-design.md

## Introduction to OpenFGA permissions

OpenFGA (<https://openfga.dev>) is a relationship-based access control (ReBAC) system, which is a permission system where access decisions take into account a network of relationships between entities. Most systems implementing this derive from the Google Zanzibar paper. For example, in order to have "writer" access to a Google document, I might be a "writer" on the document itself, or I might be a "member" of a team that has "writer" access to folder which is a "parent" of yet another folder that is the "parent" of the document, or I might have "owner" access to a team drive containing that folder! The only check for granting write access is whether I have a "writer" relationship to the document, and this relation will evaluate as true even if it is an transitive relationship across _multiple_ hops in the relationship graph. This kind of access inheritance is also intuitive for users. Nobody has to show them a complex graph of relationships to understand why they have access to a given document: it just works the way they expect.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Several grammar/typo errors across the document.

Consolidated list of issues to fix:

Line Current Fix
7 it is an transitive relationship it is a transitive relationship
30 we always an queried relationship we always mean a queried relationship
120 closer)) closer) (extra ))
237 etc) etc.) (period before close-paren per American English)
248 When a entity When an entity
283 3 attributes sets 3 attribute sets
286 readably by viewer readable by viewer
287 readably by auditor readable by auditor
🧰 Tools
🪛 LanguageTool

[style] ~7-~7: ‘take into account’ might be wordy. Consider a shorter alternative.
Context: ...ermission system where access decisions take into account a network of relationships between enti...

(EN_WORDINESS_PREMIUM_TAKE_INTO_ACCOUNT)


[style] ~7-~7: Consider a more concise word here.
Context: ...the Google Zanzibar paper. For example, in order to have "writer" access to a Google docume...

(IN_ORDER_TO_PREMIUM)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/entity-design.md` at line 7, Fix multiple typos in the entity-design
document by replacing specific incorrect phrases with the provided corrections:
change "it is an transitive relationship" to "it is a transitive relationship"
(remove the extra "an"); change "we always an queried relationship" to "we
always mean a queried relationship"; remove the extra parenthesis in "closer))"
→ "closer)"; change "etc)" to "etc.)" (add period before close-paren); change
"When a entity" to "When an entity"; change "3 attributes sets" to "3 attribute
sets"; change "readably by viewer" to "readable by viewer"; and change "readably
by auditor" to "readable by auditor". Locate these exact strings in the document
and apply the replacements ensuring punctuation and article fixes are applied as
specified.

Comment thread docs/entity-design.md

**Tuple**: A relation between two _specific_ objects of any type, conforming to the model, which is stored as a fact in OpenFGA. For example: "user:alice is a member of team:frontend" or "project:platform is the parent of project:web-app". The v2 platform maintains a live sync that indexes LFX data (projects, committees, etc.) into corresponding OpenFGA tuples, in real time.

**Relationship**: A relationship is the traversal or chaining of tuples, as defined by the model, to determine if a given *transitive* relationship exists. For example, if Alice is a member of the frontend team, and the frontend team owns the web-app project, then Alice has an ownership relationship to the web-app project. Throughout this document, when we refer to a "relationship", we always an queried relationship derived from chained tuples and the model definitions, and we will use the term "tuple" when referring to a *direct* relation "fact". The v2 platform uses OpenFGA's "batch check" endpoint to query relationships.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

MD049: Replace asterisk emphasis with underscore emphasis throughout.

markdownlint-cli2 reports MD049 violations (emphasis-style: expected underscore, actual asterisk) on lines 30, 32, 206, 224, 241, 274, 283, and 289. All italic emphasis should use _text_ instead of *text* to be consistent with the configured style.

Example fix (representative; the same pattern applies to all flagged lines):

-we will use the term "tuple" when referring to a *direct* relation
+we will use the term "tuple" when referring to a _direct_ relation
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
**Relationship**: A relationship is the traversal or chaining of tuples, as defined by the model, to determine if a given *transitive* relationship exists. For example, if Alice is a member of the frontend team, and the frontend team owns the web-app project, then Alice has an ownership relationship to the web-app project. Throughout this document, when we refer to a "relationship", we always an queried relationship derived from chained tuples and the model definitions, and we will use the term "tuple" when referring to a *direct* relation "fact". The v2 platform uses OpenFGA's "batch check" endpoint to query relationships.
**Relationship**: A relationship is the traversal or chaining of tuples, as defined by the model, to determine if a given *transitive* relationship exists. For example, if Alice is a member of the frontend team, and the frontend team owns the web-app project, then Alice has an ownership relationship to the web-app project. Throughout this document, when we refer to a "relationship", we always an queried relationship derived from chained tuples and the model definitions, and we will use the term "tuple" when referring to a _direct_ relation "fact". The v2 platform uses OpenFGA's "batch check" endpoint to query relationships.
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 30-30: Emphasis style
Expected: underscore; Actual: asterisk

(MD049, emphasis-style)


[warning] 30-30: Emphasis style
Expected: underscore; Actual: asterisk

(MD049, emphasis-style)


[warning] 30-30: Emphasis style
Expected: underscore; Actual: asterisk

(MD049, emphasis-style)


[warning] 30-30: Emphasis style
Expected: underscore; Actual: asterisk

(MD049, emphasis-style)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/entity-design.md` at line 30, The document uses asterisk-based italics
(e.g., *transitive*, *direct*) which violates MD049; replace all instances of
asterisk emphasis with underscore emphasis (e.g., _transitive_, _direct_) on the
flagged occurrences including the "**Relationship**" paragraph and other lines
referencing "relationship" and "tuple" to conform to the configured style;
ensure you change only single-asterisk italics to single-underscore italics and
leave bold (double-asterisk) or code backticks untouched.

Comment thread docs/entity-design.md
Comment on lines +50 to +73
define member: [user] or [team:member]

type repository
relations
define organization: [organization]
define writer: [user] or [team:member] or owner from organization
# Unlike writer, we don't inherit readers from organization members, because
# this depends on a "condition": the visibility configuration of the repo.
# See the "conditional relations" section of this doc for more info.
define reader: [user:*] or [user] or [team:member] or writer

type pullrequest
relations
define repository: [repository]
define author: [user]
define writer: writer from repository
define closer: author or writer
define reader: author or writer or reader from repository
```

Considerations:
- There is no "singleton" relation definition, for example, that a pull request only may have (or must have) exactly **one** repository relation. These kinds of data constraints are enforced by the system that creates and deletes tuples into OpenFGA, not by OpenFGA itself.
- Ordinarily, "greater" relations will explicitly cascade into "lesser" relations (owner → writer → reader), because any given permission (the allow/deny rule defined for a given URL and method) should only check a _single_ relationship. Rather than "writers or readers can GET this resource", you want "readers can GET this resource", and then you include writers as having the readers relation automatically. This also is why a relation like "closer" is defined in the model: it's just an abstraction over two other relations, so that the permission can be defined with only a single relationship to check.
- The `[team:member]` syntax means that the tuple will point to a team, but when evaluated, it's actually a reference to the "members" relation of that team. The example model not only allows configuring teams as repo readers or writers, but it also allows teams to include other teams (nested teams).
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

OpenFGA DSL userset reference syntax # vs : documentation

💡 Result:

In OpenFGA, : and # mean different things:

  • type:id (uses :) is the canonical syntax for an object (and also for a user identifier), where type is the object type and id is its identifier (e.g. document:budget, user:anne). [2]
  • object#relation (uses #) is the canonical syntax for a userset: “all users that have relation on object” (e.g. org:xyz#member, company:xyz#employee). [1], [2]

In the DSL, you’ll see usersets appear anywhere a userset is allowed, e.g. in type restrictions like [user, domain#member] (meaning the relation can be directly assigned either a user or the userset domain:<id>#member). [3]

Sources
[1] OpenFGA Docs – Usersets https://openfga.dev/docs/modeling/building-blocks/usersets
[2] OpenFGA Docs – Concepts https://openfga.dev/docs/concepts
[3] OpenFGA Docs – Configuration Language https://openfga.dev/docs/configuration-language


🏁 Script executed:

# Check if the file exists and examine the lines mentioned
find . -name "entity-design.md" -type f

Repository: linuxfoundation/lfx-v2-helm

Length of output: 94


🏁 Script executed:

# Get the file size and check content around the mentioned lines
wc -l ./docs/entity-design.md
echo "---"
# Check lines 48-75 (around the first issue)
sed -n '48,75p' ./docs/entity-design.md | cat -n

Repository: linuxfoundation/lfx-v2-helm

Length of output: 2269


🏁 Script executed:

# Check lines 181, 198, and 218 for additional instances
echo "=== Line 181 context (175-185) ==="
sed -n '175,185p' ./docs/entity-design.md | cat -n
echo ""
echo "=== Line 198 context (192-205) ==="
sed -n '192,205p' ./docs/entity-design.md | cat -n
echo ""
echo "=== Line 218 context (210-225) ==="
sed -n '210,225p' ./docs/entity-design.md | cat -n

Repository: linuxfoundation/lfx-v2-helm

Length of output: 3240


Fix OpenFGA DSL syntax: change [team:member] to [team#member] throughout the file.

In OpenFGA DSL, # separates a type from a relation to form a userset reference (e.g., team#member), while : separates a type from an object ID (e.g., user:alice). The current model incorrectly uses : instead of # for userset type restrictions.

Affected locations:

  • Line 50: [team:member][team#member]
  • Line 55: [team:member][team#member]
  • Line 59: [team:member][team#member]
  • Line 73: Explanatory text references [team:member] → update to [team#member]
  • Line 181: [team:member][team#member]
  • Line 198: [organization:member with is_internal_visibility][organization#member with is_internal_visibility]
  • Line 218: [team:member][team#member]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/entity-design.md` around lines 50 - 73, The OpenFGA DSL uses '#' to
reference a type's relation, so replace all userset references using
“[team:member]” with “[team#member]” and similarly “[organization:member with
is_internal_visibility]” with “[organization#member with
is_internal_visibility]”; update occurrences inside the repository and
pullrequest relation definitions (symbols: type repository, relations define
writer, define reader, type pullrequest, relations define writer/reader/closer)
and any explanatory text that mentions “[team:member]” or “[organization:member
…]” so the model uses the correct userset syntax.

@andrest50 andrest50 merged commit 89c8d70 into main Mar 20, 2026
3 checks passed
@andrest50 andrest50 deleted the ems/entity-docs branch March 20, 2026 19:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants