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

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, 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:

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:

export const deckShares = createSharesTable("deck_shares");

According to 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 maintains a global Map attached to globalThis that links resource types to their database tables and policy configurations. The registerShareableResource() function populates this map:

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 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.
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):

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 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:

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:

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:

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. 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. 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →