Skip to content

fix(2738): identity helpers honor pinned attr.dbUserId before tiered/sapId path - #2739

Merged
jung-thomas merged 1 commit into
DEVfrom
worktree-fix-2738-identity-helpers-pin
Oct 13, 2026
Merged

jung-thomas merged 1 commit into
DEVfrom
worktree-fix-2738-identity-helpers-pin

Conversation

@jung-thomas

Copy link
Copy Markdown
Contributor

Closes #2738.

Problem

Neither resolveDbUserScoped nor getOrProvisionDbUser (packages/core/resolve-db-user.js) consulted the pinned req.user.attr.dbUserId set by the global before('*') hook (pinResolvedUser). They resolved via the tiered resolveUser / sapId path instead.

A PAT req.user (srv/lib/mcp-pat-middleware.js) pins the exact attr.dbUserId but has no iss/sub and, for a migrated user, a NULL sapId. Routed into a non-*For user-scoped handler:

  • Writes (getOrProvisionDbUser): hasToken false → sapId branch → resolveUserSapId returns the email-shaped user.id → mints a spurious sapId=<email> Users row.
  • Reads (resolveDbUserScoped + the two before('READ') filters): resolve by the same path → return empty for the pinned user.

Not a current live bug (no MCP tool routes a PAT into these non-*For handlers yet — PAT writes go through srv-mcp *For actions), but it is a latent trap for the planned PAT write tools (complete_step/reset, "Task 12"). Behavior was byte-identical to pre-#2730 at these sites.

Fix

Both helpers now honor the pin first: when req.user.attr.dbUserId is set, resolve Users by that ID before falling to the tiered/sapId path — mirroring resolveDbUser's existing fast-path. If the pin is set but the row is gone (deleted mid-request), both fall through to the tiered path.

getOrProvisionDbUser's pinned path threads req.tx when present and falls back to ambient otherwise (matching resolveDbUserScoped), avoiding the single-connection SQLite deadlock cds.tx(req) causes on a tx-less synthetic req.

Tests

Added PAT-shaped regression tests to test/unit/resolve-db-user-scoped.test.js (pinned dbUserId, null sapId, no iss/sub): both helpers resolve the pinned row and mint no spurious sapId=<email> row.

Verified green locally (file-scoped vitest): resolve-db-user-scoped (4), developer-service-identity-reads/-writes (14), resolve-db-user-{provision,resolve} + resolve-db-user + session-favorites-identity (46).

Affected sites covered

The ~16 user-scoped handlers converted in #2736, plus getMyFavorites/toggleSessionFavorite from #2734 — all route through these two helpers.

…sapId path

resolveDbUserScoped and getOrProvisionDbUser (packages/core/resolve-db-user.js)
did not consult the before('*')-pinned req.user.attr.dbUserId, resolving via the
tiered resolveUser / sapId path instead. A PAT req.user (srv/lib/mcp-pat-middleware.js)
pins the exact attr.dbUserId but has NO iss/sub and, for a migrated user, a NULL
sapId. Routed into these non-*For handlers, a write would mint a spurious
sapId=<email-shaped user.id> Users row and a read would return empty for the
pinned user.

Both helpers now resolve Users by the pinned PK first (mirroring resolveDbUser's
existing attr.dbUserId fast-path), falling through to the tiered/sapId path only
when the pin is absent or its row is gone. getOrProvisionDbUser threads req.tx
when present and falls back to ambient otherwise, matching resolveDbUserScoped
and avoiding the single-connection SQLite deadlock cds.tx(req) causes on a
tx-less synthetic req.

Adds PAT-shaped regression tests (pinned dbUserId, null sapId, no iss/sub)
asserting both helpers resolve the pinned row and mint no spurious row.
@jung-thomas
jung-thomas merged commit 2ad6d6f into DEV Oct 13, 2026
3 of 4 checks passed
@jung-thomas
jung-thomas deleted the worktree-fix-2738-identity-helpers-pin branch October 13, 2026 08:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant