feat(auth): provision $member + identities on signup and install#359
Merged
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
79cc802 to
d28dd50
Compare
f8140e1 to
a190663
Compare
This was referenced Apr 25, 2026
302a0c3 to
42d58da
Compare
Closes the identity-graph gap that was blocking gateway routing — the
gateway can now resolve "which user owns this WhatsApp JID?" via a
single entity_identities lookup once these rows are populated.
New:
- packages/owletto-backend/src/auth/subject-identities.ts
- provisionMemberAndCoreIdentities(orgId, subject) — creates the
user's $member entity in the given org and writes
entity_identities rows for auth_user_id and email.
- linkWhatsAppToMember({orgId, email, rawPhone}) — normalizes the
user's phone to E.164, derives the WhatsApp JID, and writes
phone + wa_jid identity rows. Idempotent.
- normalizePhoneE164 + phoneToWhatsAppJid pure helpers (unit tested).
Modified:
- packages/owletto-backend/src/auth/personal-org-provisioning.ts
- After creating a user's personal org, call
provisionMemberAndCoreIdentities so every signed-up user lands with
a $member entity in their own org and discoverable identities.
Failure is logged but doesn't roll back the org creation —
identities are recoverable via a re-link.
- packages/owletto-backend/src/agents/install-routes.ts
- POST /api/install accepts an optional whatsapp_phone in the body.
On a valid phone, writes the wa_jid + phone identities to the
user's $member. Reports whatsappError="invalid_phone" or
"no_member" without failing the install (agent is already in;
user can re-link later).
Tests:
- 7 unit tests for normalizePhoneE164 + phoneToWhatsAppJid (UK +44
national, leading 0, E.164, +44 (0) trunk-prefix form, non-UK).
- Integration tests: install with whatsapp_phone writes both rows;
install with unparseable phone returns whatsappError="invalid_phone"
but still completes the install.
Note on stacking: this PR is based on a snapshot of feat/personal-org-
on-signup (#352) and feat/install-endpoint (#357). Both have since seen
upstream refactors (advisory locks, classifier/watcher mirroring). At
merge time, this branch will need to rebase onto their new tips —
my additions are confined to a new helper file plus one new call
site, so the conflicts should be small.
a190663 to
e5b755e
Compare
Contributor
|
Triage decision: Reasons:
Next: Manual merge required after final review — all CI checks are passing, but the change volume requires human eyes per auto-merge policy |
4 tasks
buremba
added a commit
that referenced
this pull request
Apr 26, 2026
, #359 install-half) (#372) The install flow as built — schema-mirror clones a template's entity types / relationship types / classifiers / watchers into each user's personal org — was the wrong abstraction. Cross-org vocabulary (an entity in tenant org A referencing a type defined in a public-catalog org B by FK) is the planned direction; the mirror pipeline duplicated rows per user and added re-sync complexity for no working installs (verified 0 rows used the mirror columns in prod). Removed: - packages/owletto-backend/src/agents/install.ts (installAgentFromTemplate, resyncInstalledAgent) - packages/owletto-backend/src/agents/install-routes.ts (POST /api/install) - packages/owletto-backend/src/agents/install-manifest-routes.ts (GET /api/install/manifest/:slug) - All associated integration tests - subject-identities WhatsApp helpers (normalizePhoneE164, phoneToWhatsAppJid, linkWhatsAppToMember) + their unit tests - db/migrations/20260425120000_add_template_mirror_tracking.sql (rolled back on prod first) - Route registrations from src/index.ts Kept: - subject-identities.ts provisionMemberAndCoreIdentities — used by the signup hook in personal-org-provisioning.ts, orthogonal to install flow. - #352 personal-org-on-signup, #350/#354/#355/#356 personal-finance content — no install dependencies. DB state: prod migrated down via dbmate (mirror columns dropped), then 20260426120000_entities_entity_type_fk re-applied. 0 user-visible data lost.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the identity-graph gap that was blocking gateway routing — the gateway can now resolve "which user owns this WhatsApp JID?" via a single `entity_identities` lookup once these rows are populated.
New file: `packages/owletto-backend/src/auth/subject-identities.ts`
Modified: `personal-org-provisioning.ts` (signup hook)
Modified: `install-routes.ts` (POST /api/install)
Integration tests
Stacked on
Targets `feat/install-endpoint` (#357). Pulls in `feat/personal-org-on-signup` (#352) via merge so both `personal-org-provisioning.ts` and `install-routes.ts` are in scope.
Test plan
Why this matters
This is the missing link. Before: WhatsApp routing (#7) had nothing to query because no code wrote the JID anywhere. After: every install writes `wa_jid` to the right `$member` in the right org, and the gateway routing PR can be a clean `SELECT organization_id FROM entity_identities WHERE namespace='wa_jid' AND identifier=$senderJid`.