Skip to content

Ownership model mismatch: app-authored owner lookups vs the platform's owner_id — reassigning Owner in the UI does not move record access #548

Description

@yinlianghui

Split out of #488 (surfaced while fixing the profile grants in #547) — this is an app-wide modelling question, not a missing grant.

The mismatch

Every HotCRM business object authors its own owner field as Field.lookup('sys_user') (e.g. src/objects/opportunity.object.ts, forecast.object.ts, account.object.ts). But the platform's record-level machinery never reads it:

  • @objectstack/plugin-sharing's SharingService hard-codes OWNER_FIELD = 'owner_id' — the hidden system column ObjectQL auto-injects into every object (applySystemFields) and auto-stamps to the inserting user.
  • @objectstack/plugin-security's explain/OWD path uses the same OWNER_FIELD = 'owner_id'.

So for a private-OWD object, "own" in readScope: 'own' / writeScope means owner_id = whoever inserted the row, while the visible Owner field on forms, views, highlight strips and reports is the app's owner lookup — a completely separate column.

Consequences

  1. Reassigning Owner in the UI does not transfer access. Changing owner on an account/opportunity moves it in every list and report, but the old owner keeps record-level access and the new owner gains none — owner_id never moved.
  2. Records created on someone's behalf are scoped to the creator. A manager or a flow creating a lead "for" a rep leaves owner_id on the creator; the rep sees the record in "My Leads" (the view filters on owner) but own-scope security evaluates against owner_id. The two answers can disagree in both directions.
  3. Sharing-rule ownedBy and owner-scope widening (own_and_reports, unit …) all resolve against owner_id, so any org design built around the visible Owner field silently doesn't do what it says.

demo_bootstrap (src/flows/demo-bootstrap.flow.ts) papers over this for seed data by claiming ownerless records for the first user, but it can't help live data.

Possible directions (needs a decision, in order of preference)

  • A. Sync owner_id from owner in a shared before-save hook on every object that exposes an Owner field (and backfill once). Keeps the friendly lookup UX; makes the visible field authoritative.
  • B. Drop the app-authored owner fields and surface the system owner_id on forms/views instead (relabelled "Owner"). One column, no drift, but a wide metadata churn (views, pages, highlightFields, translations, seed data).
  • C. Upstream: make the owner field configurable (ownership.field on the object schema) so the platform scopes on the authored lookup. Cleanest, but a platform change with its own timeline.

Until one lands, readScope: 'own' grants (sales_rep, service_agent) and the is_private row filter added in #547 (owner == current_user.id — deliberately written against the visible field for form consistency) should be read with this caveat in mind.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions