Tables
Availability says whether a table is served today. A planned table is not referenceable yet: a query that names it fails at validation withPV_REF_002.
Secrets, API tokens, sessions, and credential payloads are never exposed in
this pool.
Ids are served in their scalar’s spelling, base62 (
YQJpYwUwvbaLOwTUr4thA), as
in every Provider table and Plot, though Postgres stores them as uuid. An id
typed into a comparison with an Identity.UUID column matches in either
spelling, hyphenated or base62. The config tables’ tenant_connector_id
uses the same spelling as workspace.providers.id.
List active Providers
Every connected Provider instance is a row, disabled ones included, so “active” is a predicate rather than a hidden rule:connector_slug names the {connector} of providers.{connector}.{table} in
catalog form: lower case, with each run of other characters as one underscore,
so the claude-otel connector is served as providers.claude_otel. A join
from Provider rows to this table folds the slug the same way.
connection_status carries its enum values in the served schema. id is the
tenant_connector_id of the per-connector config tables, so an instance’s
settings and allowlist sit one join away:
List a Provider’s streams
Every stream configured on a Provider connection is a row, disabled ones included, so “enabled” is a column rather than a hidden rule. Join toworkspace.providers on provider_id to name the connector and to drop the
streams of disabled or deleted connections:
kind carries its enum values in the served schema. An enabled override
stream is served as providers.{connector_slug}.{platform_tap_name} and an
enabled custom stream as providers.{connector_slug}.{name}, the slug in
the catalog form above and the name lowercased; a disabled stream has no
Provider table. An enabled artifact
stream for a legacy upload is served as artifacts.public.{published_slug}
(also artifacts.{published_slug}), and artifact_id names the upload behind it.
Publication-backed Artifact streams are omitted from provider_connections,
including disabled streams: a publication table is served as
artifacts.{namespace}.{table}, never as a stream, and is discovered through
the Artifact catalog.
Other streams of a disabled or deleted connection stay rows of
provider_connections and drop out through the join above.
Roles are platform-level and scoped through membership
Roles are permission bundles defined once for the platform, not per Workspace.workspace.roles lists the roles that at least one member of the
Workspace holds, with every membership status counted. Because the name is a
display value an administrator can change, join on id, not name:
parent_name is NULL both when a role has no parent and when no member of
the Workspace holds the parent; child.parent_role_id still carries the id in
the second case.
Permissions cover their descendants
workspace.role_permissions lists the permission strings granted directly to
each role in workspace.roles. A permission is a dotted path, and a grant
covers every descendant of its path: a role granted tenant can do
everything under tenant., including tenant.users.manage. The table does
not expand that hierarchy, and it does not fold in the parent chain a role
inherits through parent_role_id; both are one predicate away. To list the
tenant-scoped permissions each held role grants:
role_id, never on the role name, and join through workspace.roles
to reach the parent chain when a role inherits from another. Which members
hold a role is workspace.user_roles.
Who holds each role
workspace.user_roles is the assignment edge: one row per (member, role)
pair. user_id joins workspace.users.id and role_id joins
workspace.roles.id, so the members page is a three-way join:
SUSPENDED and
REVOKED included, so filter on u.status = 'ACTIVE' to see who can act
today. An assignment of a role that has since been deleted is still a row in
workspace.user_roles; the join to workspace.roles drops it, because that
table lists only live roles. assigned_by is the user ID of the person who
made the assignment and may name someone who has since left the Workspace,
so a LEFT JOIN to workspace.users is the right shape for it. To count
members per role:
Read settings from a Plot
workspace.preferences returns the settings
the person running the query can read for the selected Workspace and Parable:
their own settings (Me), and the settings shared for This Parable or for a group
whose Pane’s view.preferences Policy allows them. Each row is one setting: the
Pane it belongs to (pane_key), its handle, its scope (actor_kind: person
for Me, workspace for This Parable, group for a group setting, at any level
of the Parable’s hierarchy, and actor_id, which for a group setting is the
group’s ID in canonical form), and its value.
actor_kind when the query needs one. Other people’s
settings are never returned.
A Pane’s read is live: a Plot read again after a setting changes sees the new
value, and the read records this table as unpinned.
A Report run reads the settings as they were when the run was captured.
Pressing Run, or a scheduled occurrence, stores the shared settings, This
Parable’s and every group’s, on the run whenever a Plot of the published version reads
workspace.preferences. Every Plot reads the captured rows whose Pane’s
view.preferences Policy allows the run’s principal. Planning again reads the
same captured values under the current grants. A person’s own settings are never
captured, so in a Report run the table holds only rows with actor_kind
workspace or group. The run’s pin set records the rows under
snapshotInputs with the reason captured_settings, not as an unpinned live
read. A run captured before settings were captured is refused; run the Report
again.
A Pane that shows a Report run reads the same captured rows. A Pane’s Plot that
is not part of the Report can read the run’s stored outputs and
workspace.preferences together, for example to apply the Workspace’s category
corrections to a Report’s Meetings. In a Current reading it reads the settings
live; when the reader picks a past Report run, it reads the settings that run
captured, so a later setting never changes what the run showed. A run that
captured no settings refuses that reading; run the Report again.
Report runs also capture the Workspace directory projections their Version’s
Plots read, for the live Plots a Page reads beside the run’s stored outputs.
The generated projection SQL supplies the columns and Workspace predicates, and
one bounded statement reads every directory, so they share a database snapshot.
When a reader picks a past Report run, a live Plot reads each directory from
the rows that run captured, under the same Workspace and Parable participation
checks as its settings; a captured empty directory reads as empty. A run from
before directories were captured, or one that did not capture a directory, reads
current rows, and the Pane says Directory data is current, not as of this
Refresh.
A Plot with its own schedule or materialization has no Report run to capture
settings, so publish and its schedule refuse it when it reads
workspace.preferences. To read settings on a schedule, make the Plot part of a
Report and schedule the Report.
value is JSON text. Parse it with the JSON functions: json_get_str,
json_get_int, json_get_float, json_get_bool, json_get_json,
json_get_array, json_length, json_object_keys and json_contains, or the
-> and ->> operators. json_get_array returns a list of JSON texts that
unnest turns into rows. This Plot reads a shared category set, whose items
are {id, parent, name, definition}, and the shared prompt, and asks Jev to
place each Meeting in one of the set’s leaves. map() builds Jev’s criteria
from the rows, so no JSON is written by hand:
QL-PL-004): their raw record holds custom fields, which are read through
the projected custom-field columns and the custom_fields MAP column.
Declared projections
Tables below are generated from psgen@projection declarations in
platform-schemas/services/web-db: the same declaration produces the
Postgres view, the Arrow schema the query layer serves, and this block. The
docs site build fills the block; the page source keeps only its markers.
Generated from psgen @projection declarations; edit the schema, not this block.
workspace.preferences
Every setting the principal may read for the selected Workspace and Parable line: their own settings, and the Workspace’s shared settings when they take part in the Parable. A Plot reads settings with plain SQL, for example SELECT value FROM workspace.preferences WHERE handle = 'categories'. Required request settings are workspace_id, parable_root_ref, actor_id and actor_kind; workspace.preference_can_view applies them.
Rows are scoped by the session settings
platform.workspace_id, platform.parable_root_ref, platform.actor_id, platform.actor_kind; the query layer sets them for every scan.
workspace.provider_connections
Streams of the current Workspace’s Provider connections: one row per tenant tap (an override of a platform tap, a custom tap, or an artifact-derived tap), joined to the platform tap it is bound to. Disabled streams are served with enabled = false so “enabled” is explicit in the consumer’s SQL; soft-deleted streams are not. Streams of a disabled or deleted connection are served too and drop out through the join to workspace.providers. The platform tap is reference data and is never filtered on its own soft-delete. Served by the query layer as workspace.provider_connections (EDR-0081).
Rows are scoped by the session settings
platform.workspace_id; the query layer sets them for every scan.
workspace.providers
Connected Providers of the current Workspace: one row per connector instance, joined to the Provider definition it was created from. Disabled instances are served with disabled_at set so “active” is explicit in the consumer’s SQL; soft-deleted instances are not. The definition is global reference data and is never filtered on its own soft-delete: a deleted definition with live instances is an inconsistency worth seeing. Served by the query layer as workspace.providers (EDR-0081).
Rows are scoped by the session settings
platform.workspace_id; the query layer sets them for every scan.
workspace.role_permissions
Permission strings directly granted to each role held in the current Workspace: one row per grant on a platform role that at least one member holds, whatever the membership status. Direct grants only: a role also inherits its parent chain (workspace.roles.parent_role_id), and a permission covers every dotted descendant of itself (‘tenant’ covers ‘tenant.users.manage’), so a coverage question is permission = ‘x’ OR starts_with(permission, ‘x.’). Grants of soft-deleted roles and of roles held only through soft-deleted memberships are not served. Served by the query layer as workspace.role_permissions (EDR-0081).
Rows are scoped by the session settings
platform.workspace_id; the query layer sets them for every scan.
workspace.roles
Roles held in the current Workspace: one row per platform role that at least one member holds, whatever the membership status. Roles are platform-level permission bundles with no Workspace of their own, so a role no member holds is not served, and a parent_role_id may name a role that is absent from this table. Soft-deleted roles and memberships are not served. Served by the query layer as workspace.roles (EDR-0081).
Rows are scoped by the session settings
platform.workspace_id; the query layer sets them for every scan.
workspace.user_roles
Role assignments for members of the current Workspace: one row per (member, role) pair, keyed by the person’s user id so it joins workspace.users directly and by role id so it joins workspace.roles. Assignments of every membership status are served, SUSPENDED and REVOKED included; join workspace.users.status to filter. Assignments of soft-deleted memberships are not served. Served by the query layer as workspace.user_roles (EDR-0081).
Rows are scoped by the session settings
platform.workspace_id; the query layer sets them for every scan.
workspace.users
Members of the current Workspace: one row per membership, joined to the person’s profile. Every membership status is served, including REVOKED, so a query can reconcile who left; soft-deleted memberships and profiles are not. Served by the query layer as workspace.users (EDR-0081).
Rows are scoped by the session settings
platform.workspace_id; the query layer sets them for every scan.
Join live metadata to Provider data
workspace.users.email and Provider columns with the Contact.Email semantic
scalar represent compatible values:
workspace.* table once, when it plans the Plot, scoped to the run’s Workspace.
The plan carries those rows, so every stage of the Plot reads the same rows.
The Plot’s pin set lists the table under unpinnedInputs with reason
captured and the time it read it; a restart from planning reads the table
again. This holds for a Report run (see Reports)
and for a scheduled run of a single Plot outside a Report, which has no person
and reads each table as the Workspace. workspace.preferences is the
exception: a Report run reads the settings it captured, and a Plot with its own
schedule cannot read it (Read settings from a Plot).
That read is bounded as a live one is: it fetches only the columns the Plot
uses and, when the Plot’s LIMIT reaches the table, only those rows. A table past
QUERY_LAYER_WEB_DB_MAX_ROWS rows is refused at planning when a live read of
the same query would be refused.
Use catalog discovery
for the current table schemas and
types and metadata before
joining semantic values.