> For the complete documentation index, see [llms.txt](https://v2.dataos.info/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://v2.dataos.info/build/stage-2-productize/define-governance.md).

# Define governance

Governance in Vulcan is applied at the semantic layer. A project plugin derives the user's policy group after authorization, and each semantic model uses that group to decide which fields are visible and which rows can be queried.

This page uses the plugin examples in `examples/orders-analytics-with-auth/plugins` and `examples/b2b_saas/plugins`, and the semantic policy examples in their `models/semantics` directories.

***

## How plugin governance works

Plugin governance has two parts:

1. The project registers an authorization plugin in `config.yaml`.
2. Semantic models declare `policies` that match the group returned by that plugin.

At query time, Vulcan authorizes the user with Heimdall, calls the configured plugin, receives a `SecurityContext`, and applies the matching semantic policy before the query is executed.

***

## Register an auth plugin

Add an `afterAuthorize` hook in `config.yaml`. The value is a Python import path in the form `module:function`.

```yaml
afterAuthorize: "plugins.auth_ext:resolve_user_groups"
```

This points to a function named `resolve_user_groups` in `plugins/auth_ext.py`.

The `b2b_saas` example keeps the plugin small. It reads Heimdall role tags, converts each role tag into a group label, and returns the first group:

```python
from schema.auth import AuthExtensionContext, SecurityContext

ROLE_ID_TAG_PREFIX = "roles:id:"


async def resolve_user_groups(ctx: AuthExtensionContext) -> SecurityContext:
    """Derive the primary group label from Heimdall tags for ``X-User-Group``."""

    groups = [
        tag.replace(ROLE_ID_TAG_PREFIX, "role:", 1)
        for tag in ctx.user_tags
        if tag.startswith(ROLE_ID_TAG_PREFIX)
    ]

    group = groups[0] if groups else ""
    return SecurityContext(group=group)
```

The `orders-analytics-with-auth` example maps Heimdall role tags to policy groups and chooses the highest-priority group:

```python
from schema.auth import AuthExtensionContext, SecurityContext

ROLE_ID_TAG_PREFIX = "roles:id:"
GROUP_DELIMITER = ","
POLICY_GROUP_PRIORITY = (
    "vulcan_ap_user1",
    "vulcan_ap_user2",
    "vulcan_ap_user3",
    "vulcan_ap_user4",
)


async def resolve_user_groups(ctx: AuthExtensionContext) -> SecurityContext:
    groups = [
        tag.replace(ROLE_ID_TAG_PREFIX, "", 1).replace("-", "_")
        for tag in ctx.user_tags
        if tag.startswith(ROLE_ID_TAG_PREFIX)
    ]

    group = next(
        (policy_group for policy_group in POLICY_GROUP_PRIORITY if policy_group in groups),
        groups[0] if groups else "",
    )
    return SecurityContext(group=group, groups=GROUP_DELIMITER.join(groups))
```

Use the plugin when your semantic policies need a project-specific group name. For example, Heimdall may return role tags such as `roles:id:vulcan-ap-user2`, while the semantic model policy expects `vulcan_ap_user2`.

***

## Apply policies on semantics

Policies are declared in semantic model YAML files under `policies`. Each entry starts with a `group`. If the plugin returns that group in the `SecurityContext`, Vulcan applies the entry to the semantic query.

A policy can:

* Leave a group unrestricted by declaring only `group`.
* Mask sensitive dimensions with `mask`.
* Restrict rows with `filter`.
* Combine masking and row filters in the same policy.

### Mask a dimension

The semantic dimension defines its masking expression:

```yaml
dimensions:
  - name: email
    description: Customer email address (PII masked for restricted groups)
    mask_expression: "CAST(NULL AS TEXT)"
```

The policy decides which groups receive the mask:

```yaml
policies:
  - group: vulcan_ap_user2
    mask:
      - email
```

When `vulcan_ap_user2` queries the semantic model, `email` is returned using the configured `mask_expression`.

### Filter rows

Use `filter` to limit the values a group can query:

```yaml
policies:
  - group: vulcan_ap_user2
    filter:
      - member: region_name
        operator: in
        values:
          - North
          - South
```

The `member` must be a semantic member in the same model. The examples use these operators:

* `equals`: allow rows where the member equals one of the listed values.
* `notEquals`: exclude rows where the member equals one of the listed values.
* `in`: allow rows where the member is in the listed values.
* `notIn`: exclude rows where the member is in the listed values.

### Combine mask and filter

The `orders-analytics-with-auth` `customer_profile` semantic model uses one unrestricted group and several restricted groups:

```yaml
policies:
  - group: vulcan_ap_user1
  - group: vulcan_ap_user2
    mask:
      - email
    filter:
      - member: customer_segment
        operator: notEquals
        values:
          - Churned
      - member: region_name
        operator: in
        values:
          - North
          - South
  - group: vulcan_ap_user3
    mask:
      - email
      - customer_name
    filter:
      - member: customer_segment
        operator: in
        values:
          - High Value
          - Medium Value
      - member: region_name
        operator: equals
        values:
          - East
```

In this example:

* `vulcan_ap_user1` can query the semantic model without additional masks or filters.
* `vulcan_ap_user2` cannot see raw email values and cannot query churned customers.
* `vulcan_ap_user3` cannot see raw email or customer name values and can only query selected segments in the East region.

The `b2b_saas` `users` semantic model shows a smaller policy block:

```yaml
policies:
  - group: executive
  - group: analyst
    mask:
      - signup_date
    filter:
      - member: status
        operator: equals
        values:
          - active
```

Here, `executive` is unrestricted. `analyst` can only query active users, and `signup_date` is masked.

***

## End-to-end flow

1. A user sends a semantic query through SQL, REST, or GraphQL.
2. Heimdall authorizes the request and returns user tags.
3. Vulcan calls the configured plugin, such as `plugins.auth_ext:resolve_user_groups`.
4. The plugin returns a `SecurityContext` with a primary `group`.
5. Vulcan finds the matching semantic `policies` entry and applies masks and filters before executing the query.

Keep policy group names consistent between the plugin and the semantic YAML. If the plugin returns `vulcan_ap_user2`, the semantic model must declare `group: vulcan_ap_user2` for that policy to apply.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://v2.dataos.info/build/stage-2-productize/define-governance.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
