Sharing and access control
Access control is enforced in PostgreSQL with row-level security, not in application code. A query that should not see a row does not see it — regardless of which controller, endpoint, or trigger issued it.
That is the important property: a bug in a controller cannot leak records, because the filtering happens below the application entirely.
Three ways a record becomes visible
Section titled “Three ways a record becomes visible”Ownership. Every record carries created_by. Creators see their own records.
Share rules. Model-level rules that grant access declaratively — “everyone in Support can read Issues”, “the assignee can write”. Evaluated automatically, and they are configuration, so they deploy with everything else.
Manual shares. A specific record shared with a specific user or group, the way you would share a document. Data, not configuration — they do not deploy.
The distinction matters: share_type is manual or rule, and a deploy
replaces rule-derived shares while leaving manual ones alone.
Permissions and targets
Section titled “Permissions and targets”share_permission | Meaning |
|---|---|
read | See the record |
write | See and modify it |
share_target | Shared with |
|---|---|
all | Everyone in the workspace |
user | One user |
group | One group, and its descendants |
Groups are hierarchical, so granting to a parent grants to everything beneath it. That is usually what you want, and occasionally more than you meant — check the tree before sharing at a high level.
Rules in configuration
Section titled “Rules in configuration”Share rules live in share_rules/ and move with polymesa deploy. Review them
in a --dry-run like any other change: widening a rule is a data-exposure
change, and it looks the same as any other diff line until you read it.
Guest code
Section titled “Guest code”Plain sql runs as the current user with RLS applied — you get their rows, no
filtering to write. This is why the default is safe.
// Only what this user may seeconst { rows } = await sql.query<Issue>(`SELECT * FROM ${i}`);sql.systemMode bypasses RLS completely:
// Every row, regardless of the callerconst all = await sql.systemMode.query<Issue>(`SELECT * FROM ${i}`);Legitimate uses are operations with no user behind them — scheduled jobs, inbound email handlers, cross-workspace rollups.
Scheduled jobs
Section titled “Scheduled jobs”Jobs have no triggering user, so plain sql sees almost nothing. They run as the
built-in SYSTEM account and generally use systemMode deliberately. See
Scheduled jobs.
The CLI
Section titled “The CLI”polymesa query runs as your user with RLS applied, so exports are limited to
what you can see. Two people running the same query can get different rows —
that is correct, not a bug.