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
- 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.
- 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.
- 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.
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
ownerfield asField.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'sSharingServicehard-codesOWNER_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 sameOWNER_FIELD = 'owner_id'.So for a
private-OWD object, "own" inreadScope: 'own'/writeScopemeansowner_id= whoever inserted the row, while the visible Owner field on forms, views, highlight strips and reports is the app'sownerlookup — a completely separate column.Consequences
owneron 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_idnever moved.owner_idon the creator; the rep sees the record in "My Leads" (the view filters onowner) but own-scope security evaluates againstowner_id. The two answers can disagree in both directions.ownedByand owner-scope widening (own_and_reports,unit…) all resolve againstowner_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)
owner_idfromownerin 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.ownerfields and surface the systemowner_idon forms/views instead (relabelled "Owner"). One column, no drift, but a wide metadata churn (views, pages, highlightFields, translations, seed data).ownership.fieldon 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 theis_privaterow 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.