Skip to content

Visibility and Privacy

Visibility rules control what a query or mutation actually returns once permission to call it has already been granted (see Permission Model Reference). They apply to the GraphQL types those operations return (CargoListing, Conversation, Shipment) and answer two separate questions:

  1. Which instances of this type can this user see at all?
  2. Of the fields on a visible instance, which ones does this particular user see?

These two questions are independent. A user might be allowed to see a given CargoListing exists, but only some of its fields, or might not be able to see it at all, in which case field visibility never even comes up.

Which Instances Are Visible

Every type has a scope that determines the widest audience it can ever reach:

Scope Meaning Who Sees It Example
own_only Only the creator can see it Just the owner A draft conversation only its creator can see, even if others are eventually assigned
assigned Only users (or groups) in assigned_to can see it Anyone in the instance's assigned_to list, directly or via a group A conversation only its participants can see
public Anyone can see it, once published Everyone, once the instance is published A cargo listing, visible to all capacity providers once published
all Anyone can see it, regardless of assignment or state Admin only, in practice Admins reviewing all shipments

A type's scope isn't necessarily fixed forever. See Scope Changes With State below, where a CargoListing moves from own_only to public to assigned over its lifetime.

Regardless of scope, two viewers always see an instance: its owner (@owner, whoever created it) and admins. Scope governs everyone else.

Scopes can also carry an extra condition: for example, assigned scope limited to conversations that are still active or pending, so archived conversations drop out of view even though the user is still technically assigned.

Special Symbols

Assignment lists (assigned_to) and scope conditions are written in terms of three symbols:

Symbol Meaning Example
@self The current user Matches if the current user is in an instance's assigned_to list
@group Any of the current user's groups Matches if any of the user's groups appear in assigned_to
@owner The instance's owner Always true for whoever created/owns the instance

assigned_to, and therefore @group, is also the only way a group affects visibility: a group doesn't carry its own visibility rule. It only gains visibility into a specific instance by being added to that instance's assigned_to list. See Sharing & Resource Assignment for how that list gets populated, and Assignment & Visibility for the group case specifically.

Which Fields Are Visible

Once an instance is visible at all, field visibility narrows it further. A permission specifies which fields are allowed for each operation. Only listed fields are visible. If a field is not listed, it's hidden.

Grouped by viewer: a list of allowed fields per relationship to the instance:

Viewer Allowed Fields
@owner ["*"] (all fields)
@assigned ["id", "status", "location", "eta"] (specific fields only)
public ["title", "origin", "destination", "price"] (public fields)
admin ["*"] (all fields, including hidden ones)

Example configuration:

cargoListing:
  owner:    ["*"]                                          # sees every field
  assigned: ["id", "status", "location", "eta"]           # tracking only
  public:   ["title", "origin", "destination", "price"]   # public info
  admin:    ["*"]                                          # everything

Grouped by field: the same rule, read from the field's side: on a CargoListing, title and origin might be listed for the public viewer, while price and internalNotes are listed only for @owner and admin.

Either way, the outcome is identical: a non-owner, non-admin user sees only the fields listed for them: price and internalNotes are silently omitted from the response, never an error, just absent.

Default Rules by Type

Type Owner Sees Assigned Party Sees Public Sees Admin Sees
CargoListing Everything, including internal notes -- (not an assignment-scoped type) Public fields of published listings only (title, origin, destination, weight, volume, dates) Everything
Conversation -- (assignment-scoped, not owner-based) Everything, once assigned Not visible at all unless assigned Everything, on every conversation
Shipment Everything, including cost breakdown Tracking fields only (id, status, location, eta, progress); cost breakdown and internal notes hidden Not visible Everything

Reading this table alongside the scope model above: CargoListing's scope is public (reachable by everyone once published), but the owner column exists because the owner relationship always overrides scope, per Which Instances Are Visible. Conversation's scope is assigned. There's no public column with real content because its scope never reaches that far.

Scope Changes With State

A type's scope isn't always static. It can change as the instance moves through its lifecycle. CargoListing, for example:

State Scope Fields
Draft own_only Everything
Published public Public fields only (title, origin, destination, price); internal notes hidden
Matched assigned Everything, for those assigned
Shipped assigned Everything, for those assigned

See State-Based Constraints for how the same state also locks which mutations are callable at all, independent of visibility.

Enforcement Order

Visibility is checked after the permission check for the operation itself (see GraphQL Implementation for that full request flow), in two steps:

  1. Instance visibility: is this specific instance visible to this user at all? If not, it's excluded, from a single lookup that means a 404 (existence isn't leaked), from a list query it's just dropped from the results.
  2. Field visibility: of the fields the operation is otherwise permitted to return, which does this instance's current rule allow for this user? Hidden fields are silently omitted, never an error, and never a per-field 403, since checking every field on every request that way would add cost the app doesn't need at scale.

This ensures no data leaks, users only see what they should, and admins can override any visibility rule.