Skip to content

A sys_user_position row inserted for a user created after bootstrap never resolves that user's permissions — the row writes 201, the account still reads nothing, and it survives a restart #16712

Description

@hotlong

Filed from an app repo (objectstack-ai/hotclm, @objectstack/* 17.3.0, better-sqlite3, single-org dev posture) while provisioning audience accounts to verify dashboard gating — objectstack-ai/hotclm#29, PR objectstack-ai/hotclm#30. ⛔ Unassigned, ungraded; routing is triage's.

Provenance. Measured by the card-10 implementation run while building its audience matrix; I reviewed the evidence and separately provisioned an account the working way (a direct permission-set grant) in the same session, which is the control half. I did not myself re-drive the failing half, and I say so rather than implying otherwise.

Dedup: searched sys_user_position and position-grant resolution. Nothing open or closed covers this.

What happens

For a sys_user created after the deployment has bootstrapped — i.e. any account an operator makes in Setup — inserting a sys_user_position row does not give that user the position's permission sets:

  • the insert returns 201, with user_id, position and organization_id all set;
  • the account still reads nothing — the objects the position's sets grant come back empty;
  • GET /api/v1/meta/app/<app> serves it an empty navigation;
  • and it stays that way across a server restart, so this is not a cache that a bounce clears.

A direct sys_user_permission_set grant to the same account works immediately. That is the control, and it is what says the account, the org and the sets are all fine — the position edge specifically is what does not materialise.

The inference is that whatever materialises position→permission-set grants runs at bootstrap and has no path for a user added later. Naming the mechanism is yours; what is measured here is the outcome.

Why this is worth a card rather than a workaround note

It makes a documented, first-class operator action silently ineffective. Assigning a position in Setup is the way an admin is meant to staff a deployment — ADR-0090 puts positions at the centre of distribution, and our own DESIGN.md §10 instructs the operator to create accounts and assign the seven positions. Following that instruction produces an account that can sign in, sees an empty app, and reads nothing, with a 201 and no error anywhere to say why.

Note the failure shape: a correctly-staffed account and a silently-unstaffed one are the same observation from outside — both render an empty app. There is no signal that separates them, which is why this cost a verification round to find and why it will not be found by whoever hits it in production; they will conclude the permission set is wrong and go re-author security metadata that was never the problem.

What would resolve it

Any one of these closes it; the first is smallest:

  1. Materialise the grant on insert — a sys_user_position write for an existing user resolves that position's sets there and then.
  2. Refuse or warn if the row cannot take effect, so the 201 is not a false success.
  3. Re-materialise on boot, so a restart is at least a recovery path (it currently is not).

Reproduce

# as an admin, on a bootstrapped deployment
POST /api/v1/auth/admin/create-user      -> 201   (any new account)
POST /api/v1/data/sys_user_position      -> 201   {user_id, position, organization_id}
# sign in as that account
GET  /api/v1/data/<an object the position's set grants>   -> empty
GET  /api/v1/meta/app/<app>                               -> navigation: []
# restart the server, repeat the two reads          -> unchanged

# control, same account, same session:
POST /api/v1/data/sys_user_permission_set -> 201
GET  /api/v1/data/<the same object>                       -> rows

The control is the part that matters: without it, "the account reads nothing" is indistinguishable from a misconfigured permission set, which is exactly the wrong place to go looking.

Boundaries

  • One app, one dialect, single-org dev posture, accounts created through the admin endpoint. Re-confirm on a walled/multi-org posture before acting — the promotion path differs there, and this may or may not.
  • ⛔ Nothing was patched in the platform; the app provisions its verification accounts by direct permission-set grant and records why at the site.
  • A related consequence worth knowing when reading app-side row counts: an account provisioned by direct grant does not get the sharing rules that grant by position, so its row counts are legitimately lower than a position-staffed account's. That is not this defect; it is what makes the two easy to confuse.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingdomain:enginepm:queuepm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchpriority:p2Medium: important, M3

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions