# How the Sharing System in Agent-Native Works: Ownership, Visibility, and Access Control

> Discover the Agent-Native sharing system: understand ownership rights, data visibility, and access control for all user resources through its unified privacy model.

- Repository: [Builder.io/agent-native](https://github.com/BuilderIO/agent-native)
- Tags: internals
- Published: 2026-06-28

---

**Agent-Native implements a unified, framework-wide sharing model that combines ownable database schemas, companion shares tables, and runtime access helpers to enforce consistent privacy rules across all user-created resources.**

The sharing system in Agent-Native is built into the core package at `BuilderIO/agent-native` and provides a declarative primitive for managing who can view or edit documents, decks, designs, and extensions. Every resource becomes *ownable* with three coarse visibility levels and fine-grained per-principal grants, enforced automatically at the database query layer.

## Core Architecture of the Agent-Native Sharing System

The architecture rests on three pillars: database schema extensions, a global registry, and runtime access helpers. These components work together to ensure that every list query and action respects ownership and sharing rules without manual checks.

### Ownable Schema and Database Columns

In [`packages/core/src/sharing/schema.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/sharing/schema.ts), the `ownableColumns()` function augments any resource table with three critical columns:

- **`owner_email`** – Identifies the resource owner who retains full control regardless of organization boundaries.
- **`org_id`** – Links the resource to an organization for `org`-level visibility.
- **`visibility`** – Stores the coarse access level as `"private"`, `"org"`, or `"public"` (defaults to `"private"`).

When defining a new resource, you spread these columns into your table definition:

```typescript
import { table, text, ownableColumns } from "@agent-native/core/db/schema";

export const decks = table("decks", {
  id: text("id").primaryKey(),
  title: text("title").notNull(),
  ...ownableColumns(), // Adds owner_email, org_id, visibility
});

```

### The Shares Table for Fine-Grained Grants

The `createSharesTable()` helper generates a companion table that stores explicit grants. Each row represents a share assignment with a principal (user or organization) and a role:

```typescript
export const deckShares = createSharesTable("deck_shares");

```

According to [`schema.ts`](https://github.com/BuilderIO/agent-native/blob/main/schema.ts) lines 86-94, roles follow a strict hierarchy defined by `ROLE_RANK`: **viewer** (1), **editor** (2), **admin** (3), and **owner** (4). This ranking allows the system to determine the highest permission level when multiple grants apply.

### Global Resource Registry

The registry in [`packages/core/src/sharing/registry.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/sharing/registry.ts) maintains a global `Map` attached to `globalThis` that links resource types to their database tables and policy configurations. The `registerShareableResource()` function populates this map:

```typescript
import { registerShareableResource } from "@agent-native/core/sharing";

registerShareableResource({
  type: "deck",
  resourceTable: schema.decks,
  sharesTable: schema.deckShares,
  displayName: "Deck",
  titleColumn: "title",
  getDb,
});

```

This registration stores metadata including optional **hardening flags** that restrict sharing behavior for sensitive resources.

## Visibility Levels and Ownership Mechanics

Every ownable resource operates on a tiered visibility model defined by the `visibility` column and `owner_email` field.

- **Private** – Only the owner and users with explicit share rows can access the resource.
- **Org** – Anyone sharing the same `org_id` gains read-only access, plus explicit shares.
- **Public** – Any authenticated user can read the resource via direct link, though it remains hidden from sidebars unless `includePublic` is explicitly requested.

The owner identified by `owner_email` automatically receives the highest role rank (4), bypassing all share table lookups during permission resolution.

## Runtime Enforcement and Access Control

The [`packages/core/src/sharing/access.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/sharing/access.ts) file exports three primary functions that enforce the sharing model at query-time and action-time.

### Filtering Queries with `accessFilter()`

When listing resources, `accessFilter()` constructs a SQL `WHERE` clause that restricts rows to only those the current user can see. As implemented in lines 108-166, it combines:

1. Owner scope filtering via `ownerScopeFilter()` for rows matching the current user's email.
2. Visibility checks for `org` and optional `public` levels.
3. Share scope filtering via `restrictedShareScopeSql()` that respects the `requireOrgMemberForUserShares` policy flag.

```typescript
const rows = await db
  .select()
  .from(schema.decks)
  .where(accessFilter(schema.decks, schema.deckShares));

```

### Resolving Permissions with `resolveAccess()`

For single-resource operations, `resolveAccess()` (lines 40-80) determines the effective role by evaluating:

1. Owner shortcut – Returns `"owner"` immediately if the current user matches `owner_email`.
2. Public visibility check – Grants default viewer access if `visibility` is `"public"` and `allowPublic` is not disabled.
3. Organization membership – Grants viewer access for matching `org_id` when visibility is `"org"`.
4. Explicit shares – Queries the shares table via `highestShareRole()` to find the highest ranked role.

The function returns `{ role, resource }` or `null` if access is denied.

### Asserting Permissions with `assertAccess()`

Actions call `assertAccess()` at entry points to guarantee minimum role requirements before executing business logic. If the caller lacks sufficient permissions, the function throws `ForbiddenError` (lines 31-33):

```typescript
import { assertAccess } from "@agent-native/core/sharing";

export const updateDeck = defineAction({
  name: "update-deck",
  input: z.object({ deckId: z.string(), data: z.string() }),
  async run({ deckId, data }) {
    await assertAccess("deck", deckId, "editor"); // Requires editor or higher
    // ... perform update ...
  },
});

```

## Security Hardening with Policy Flags

When registering resources that execute code or contain sensitive data, you can enforce additional constraints through policy flags in `registerShareableResource()`:

- **`allowPublic: false`** – Prevents setting `visibility` to `"public"` and treats existing public rows as private.
- **`requireOrgMemberForUserShares: true`** – Restricts user shares to emails belonging to the same organization or users with pending invitations.

These flags are critical for extensions and other executable resources. The [`restricted-sharing.spec.ts`](https://github.com/BuilderIO/agent-native/blob/main/restricted-sharing.spec.ts) test suite validates their behavior, ensuring that policy violations are rejected at the API layer.

## Implementing Sharing in Your Application

Building a shareable resource requires three steps: defining the schema, registering the resource, and integrating the UI components.

First, define the ownable schema and shares table:

```typescript
import {
  table,
  text,
  ownableColumns,
  createSharesTable,
} from "@agent-native/core/db/schema";

export const designs = table("designs", {
  id: text("id").primaryKey(),
  title: text("title").notNull(),
  config: text("config").notNull(),
  ...ownableColumns(),
});

export const designShares = createSharesTable("design_shares");

```

Next, register the resource with optional hardening:

```typescript
registerShareableResource({
  type: "design",
  resourceTable: schema.designs,
  sharesTable: schema.designShares,
  displayName: "Design",
  titleColumn: "title",
  getDb,
  allowPublic: false, // Hardening for sensitive designs
});

```

Finally, add the share button to your UI:

```tsx
import { ShareButton } from "@agent-native/core/client";

<ShareButton
  resourceType="design"
  resourceId={design.id}
  resourceTitle={design.title}
/>;

```

The `ShareButton` component handles the full workflow: calling the `share-resource` action, validating against policy flags, and re-rendering the share dialog with updated permissions from `list-resource-shares`.

## Summary

- **Agent-Native** provides a built-in sharing system combining `ownableColumns()`, shares tables, and a global registry to manage resource access.
- **Three visibility levels** (`private`, `org`, `public`) and **four role ranks** (viewer, editor, admin, owner) determine access rights.
- **`accessFilter()`** secures list queries by generating SQL filters that combine ownership, visibility, and explicit shares.
- **`resolveAccess()`** and **`assertAccess()`** enforce permissions at the action layer, throwing `ForbiddenError` for unauthorized requests.
- **Policy flags** (`allowPublic`, `requireOrgMemberForUserShares`) allow developers to harden resources against unsafe sharing configurations.
- The **`ShareButton`** component provides a drop-in UI for managing grants while respecting all backend enforcement rules.

## Frequently Asked Questions

### How does Agent-Native handle public sharing versus organization-wide sharing?

Agent-Native distinguishes between **`public`** and **`org`** visibility at the database level. Public resources allow any authenticated user to read via direct link but remain excluded from sidebars unless explicitly requested with `includePublic`. Organization visibility automatically grants read access to all users sharing the same `org_id`, while still requiring explicit shares for write access. The `allowPublic` policy flag can disable public sharing entirely for sensitive resource types.

### What happens when a user has multiple share grants with different roles?

The system resolves to the highest ranked role using the `ROLE_RANK` constant defined in [`schema.ts`](https://github.com/BuilderIO/agent-native/blob/main/schema.ts). A user might hold a **viewer** grant through their organization membership and an **editor** grant through an explicit share row; `resolveAccess()` returns **editor** because it has a higher rank (2 vs 1). This ranking ensures that explicit grants always override baseline permissions.

### Can I restrict sharing to only organization members?

Yes. When calling `registerShareableResource()`, set **`requireOrgMemberForUserShares: true`**. This policy flag forces the sharing system to validate that any user email added to a share belongs to the same organization as the resource or has a pending invitation. Combined with `allowPublic: false`, this creates a locked-down resource that only flows within your organizational boundaries.

### Where does Agent-Native store the sharing configuration for each resource type?

The configuration lives in a **global Map** attached to `globalThis`, managed by [`packages/core/src/sharing/registry.ts`](https://github.com/BuilderIO/agent-native/blob/main/packages/core/src/sharing/registry.ts). When you call `registerShareableResource()`, the system stores the resource type, table references, and policy flags in this global registry, ensuring a single source of truth across server-side rendering bundles. This registry enables `accessFilter()` and `resolveAccess()` to locate the correct tables and policies at runtime without manual imports.