feat(agents): public install endpoint for template agents#357
Merged
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
5 tasks
79cc802 to
d28dd50
Compare
buremba
added a commit
that referenced
this pull request
Apr 25, 2026
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.
Member
Author
|
Heads up: per docs/plans/world-model.md (on
No mirroring, no entity_types/entity_relationship_types touched. ~30 lines. The current implementation here can be repurposed once template-as-entity lands. |
3 tasks
POST /api/install takes { templateAgentId } from a signed-in user and:
1. Resolves the user's personal organization via the
personal_org_for_user_id metadata tag written by the
user.create.after hook.
2. Runs installAgentFromTemplate(), which creates the agent instance
in the user's org and mirrors the template's entity_types +
entity_relationship_types with managed_by_template_agent_id set.
3. Returns the new agent id, the target org slug, and a redirectTo
path — enough for the upcoming /install/:slug landing page to
post and redirect without any follow-up API calls.
Error paths:
- Missing templateAgentId → 400
- Signed-in user has no personal org (hook failed / pre-hook user) →
409 with error="no_personal_org"
- installAgentFromTemplate failure (template not found, self-install,
user-authored collision) → 400 with the upstream error message.
Integration test stubs the auth middleware and exercises the happy path,
the missing-field 400, and the missing-personal-org 409.
Pairs with the upcoming /install/personal-finance landing page in the
owletto-web submodule, which ships as a separate two-PR pair.
- Replace metadata LIKE search with `(metadata::jsonb)->>'personal_org_for_user_id' = $1` so a userId containing % or _ can't match unintended rows. - Add per-user hourly rate limit (20/hour) via the standard rate-limiter preset to prevent abusive install loops.
302a0c3 to
42d58da
Compare
buremba
added a commit
that referenced
this pull request
Apr 26, 2026
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.
buremba
added a commit
that referenced
this pull request
Apr 26, 2026
…ll (#359) 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.
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
`POST /api/install` takes `{ templateAgentId }` from a signed-in user and:
Error paths covered by integration tests:
Stacked on
Targets `feat/schema-mirror-install-flow` (#353). Rebase onto main once #353 (and transitively #351) merge.
Follow-up (separate task)
`packages/owletto-web` submodule: the `/install/personal-finance` landing page — public route, agent overview, "Install" button that POSTs here and redirects to `redirectTo`. Two-PR ship per AGENTS.md: land the submodule PR, then the parent submodule-bump PR.
Test plan