feat(team): show brand-registry hierarchy classification + report-wrong link#3450
Merged
Conversation
…h-check pattern Replaces 14 copy-pasted call sites across organizations.ts and member-profiles.ts that did the WorkOS-membership-then-resolveUserRole auth check inline. Two problems with the old pattern: - Every admin-only org endpoint 403'd in dev mode because WorkOS doesn't know about DEV_USERS. The dev-mode bypass that solved this lived in exactly one place (GET /api/me/member-profile), and most other routes didn't have it. - 14 subtly different versions of the same auth check, each with its own error message and role logic. The helper does it once. In dev mode it reads from local organization_memberships (seeded by dev-setup at boot). In prod it defers to WorkOS as source of truth. Verified end-to-end: every admin endpoint the team page hits now responds 200 to a dev-mode admin user where most previously returned 403. The single remaining 404 in dev (api/billing) is unrelated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…membership' into bokelley/show-org-brand-hierarchy
…ng link PR #3430's subsidiary toggle only showed children of the org. Two things owners actually need: - Parent (if classified as a subsidiary of another brand). Without this, an analyticsiq.com-style org can't tell they're classified under Alliant. - Classification confidence + last-validated date — context owners need to judge whether the data is trustworthy enough to flip the auto-provision toggle. GET /api/organizations/:orgId/domains now returns hierarchy_classification with self + parent. team.html renders a "Brand registry classification" section above the toggles showing parent → you → children, each with a "Report wrong" mailto link that routes corrections into the existing support flow without new infra. Verified end-to-end via Playwright in dev mode (depends on the universal dev-mode bypass from #3448): seeded a brand row + subsidiary, the hierarchy diagram renders correctly, "Report wrong" links work. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Security review found a HIGH ship-blocker on PR #3448: - Add a hard prod-boot guard that crashes startup if DEV_USER_EMAIL + DEV_USER_ID are set in a production-shaped environment (NODE_ENV=production OR FLY_APP_NAME present). Override via ALLOW_DEV_MODE_IN_PROD=true for the rare one-off. Pre-PR the dev-mode bypass affected one read endpoint; post-PR it widens to 23 mutating routes (delete org, ban member, transfer ownership, etc), so a stray secret-flip would now grant owner-level takeover instead of read access. Code review found 4 more sites that match the auth-check pattern but weren't refactored in the original commit: - POST /api/organizations/:orgId/members (line 2177) - POST /api/organizations/:orgId/invitations (line 2360) - DELETE /api/organizations/:orgId/invitations/:invitationId (line 2469) - POST /api/organizations/:orgId/invitations/:invitationId/resend (line 2536) - POST /api/organizations/:orgId/members/by-email (line 3092 — the hybrid AAO-admin / static-API-key / org-role path; refactored carefully to preserve the conditional-skip for static admin keys) - PATCH/DELETE /api/organizations/:orgId/members/:membershipId (line 3291) All 6 now route through resolveUserOrgMembership. billing-public.ts and certification.ts have hand-rolled dev-mode bypasses (isDevUserInvoice, isOrgMember). Filed as separate followup — different patterns that need their own analysis. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ey/show-org-brand-hierarchy
…nd_json visual distinction Code-review FBM and product feedback on PR #3450: - SQL bug (code review): the ORDER BY scalar subquery for is_primary did not filter by workos_organization_id. If the same domain is verified across multiple tenants, the wrong org's primary flag could win the LIMIT 1. Added the tenant filter. - Self-loop guard (code review): the LEFT JOIN brands parent ON parent.domain = db.house_domain didn't guard against db.house_domain == db.domain. A malformed registry row pointing at itself would render "you are a child of yourself". Added parent.domain != db.domain to the join. - Dropped data.org_name from the mailto body — the /domains endpoint doesn't return it, so the line always rendered as "Org: " blank. - New `source` field on hierarchy_classification.self / .parent / inferred_subsidiaries (product). brand_json self-asserted gets a green "Asserted" badge; LLM-inferred gets a neutral "Inferred" badge. - For brand_json self-asserted parent relationships, render a small guidance hint pointing the admin at their own brand.json instead of the mailto support flow — fix is one PR away from where they look, not three weeks away after a triage review. - Confidence badge now suppressed for brand_json (the "confidence" frame is wrong for a self-assertion). Tests: 3 new cases (parent-row-doesn't-exist via LEFT JOIN miss, self-loop guard, source field surfacing for brand_json). 12/12 pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
Author
|
Pushed round-2 fixups addressing all three reviews: Code-review FBM (resolved):
Product feedback (resolved):
Deferred to follow-up issues (not in this PR):
Tests: 3 new cases (parent-row-doesn't-exist via LEFT JOIN miss, self-loop guard data-quality regression, source field surfacing). 12/12 pass on this file's suite. |
Contributor
Author
|
Thanks for the thorough round-2 summary. The tenant-scoped ORDER BY fix and self-loop guard look like the right calls — good catches from the review. The deferred items (click-tracking, stale muting, member-profile deep link) are well-scoped follow-ups; leaving them off this PR makes sense. No action needed from triage. Ready for human review when you are. Generated by Claude Code |
bokelley
added a commit
that referenced
this pull request
Apr 28, 2026
…#3469) Two polish items deferred from the PR #3450 review (product expert recommendations): - Click-tracking endpoint POST /api/organizations/:orgId/brand-classification-report records {orgId, userId, kind, subject_domain} to registry_audit_log on every Report-wrong click. The mailto: opens regardless via the existing anchor href; sendBeacon doesn't block navigation. Gives us a triage queue + lets us detect when N different members all flag the same domain. Member-or-above can flag (broader than admin/owner since the action is informational and the corrective action is admin-side). - One-line deep link from /member-profile's Brand identity section to the /team page where the registry hierarchy is now shown. Closes the "I came to member-profile to check if my company is classified right" bounce-off case. 1 new integration test (happy path + invalid-kind 400 + invalid-domain 400). 13/13 pass on the toggles suite. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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
PR #3430 added the subsidiary auto-provisioning toggle but only surfaced what's below the org (children). Two things owners actually care about were hidden:
analyticsiq.com-style org couldn't tell they were classified under Alliant.This PR closes that "black box" gap.
What
Backend:
GET /api/organizations/:orgId/domainsreturnshierarchy_classification: { self, parent } | null. Sourced from thebrandstable for the org's primary verified domain plus a self-join for the parent.Frontend: new "Brand registry classification" section on
team.htmlabove the auto-provision toggles. Renders parent → you → children with confidence badge + last-validated dates. Each entry has a "Report wrong"mailto:hello@agenticadvertising.orglink that prefills subject/body, routing corrections through the existing support channel without new infra.The inferred-subsidiaries panel inside the auto-provision toggle is now a thin pointer back to the hierarchy section (no duplication).
Verification
house_domain, classification null when nobrandsrow)./api/organizations/:orgId/domainswould otherwise 403 in dev — that PR is open for review.Out of scope (future PRs if signal emerges)
escalationrow directly. The mailto routes into the same human queue with no new code; if volume justifies it later, swap for the form.🤖 Generated with Claude Code