Skip to content

Per-view & per-vault ACL

Section permissions (R/W/E/D) are the outer gate. For some surfaces, that’s not granular enough — a Sales rep with cars:read = true should see their assigned yard’s cars, not every car in the company. The platform layers inner gates for this.

  1. Section visibility — does this role see the route/sidebar item at all? (cars:read, vaults:read)
  2. Sub-tab or sub-content gating — within a shared section, does this role see this particular tab/view? (Saved views; vault-row visibility)
  3. Row-level scoping — within an accessible view, which rows are returned? (Per-location ACL filters; per-vault ACL gates)

All three must pass for the user to see a row.

Each saved view carries a per-role bindings table. Each binding has:

  • Visible toggle — does this role see the view in the tab strip?
  • Offering price toggle — can this role edit internalOfferingPrice on rows in this view?

A role with no binding to a view doesn’t see it. A role with multiple bound views sees a tab strip with each.

Same pattern, applied to vaults. Each vault row has per-role bindings:

  • Visible toggle — does this role see the vault in /vaults?
  • Write toggle — can this role create transactions on this vault?

A role bound only to “Cash KEB USD” can’t see “Cash KEB KRW” — the vault is filtered out of every list query for them.

Cars also use locations as an action-level ACL. Each role gets per-location flags, all of them enforced:

  • canSee — see cars at this location
  • canReceive — accept a location change to this location
  • canMoveOut — initiate a location change away from this location
  • canSell — issue an invoice for cars at this location

There is no canEdit flag — it was never implemented. Editing a car is still gated by the outer cars:edit section permission; the location layer only controls visibility and the receive/move-out/sell actions. See Locations.

A Sales role with:

  • cars:read = true, cars:edit = true (section layer — passes)
  • Bound to the “Showroom A” view, canEditOfferingPrice = true (sub-tab layer — passes)
  • canSell = true and canSee = true on the Showroom A location (row layer — passes)

…can see cars at Showroom A, edit their offering price, and issue invoices against them. Sees nothing at the Korea yard. The platform composes all three gates; missing any one = no access.

Change log

Updated onFeature / changeReason
2026-07-07Corrected per-location ACL: canReceive/canMoveOut/canSell are actively enforced (not planned); removed the nonexistent canEdit flagCode audit against server/db/carLocationScope.ts found all four action flags enforced and no canEdit field ever implemented