Skip to content

[schemas] CRM person tiers — relationship tier schema + dashboard page#219

Open
alanshurafa wants to merge 5 commits intoNateBJones-Projects:mainfrom
alanshurafa:contrib/alanshurafa/crm-person-tiers
Open

[schemas] CRM person tiers — relationship tier schema + dashboard page#219
alanshurafa wants to merge 5 commits intoNateBJones-Projects:mainfrom
alanshurafa:contrib/alanshurafa/crm-person-tiers

Conversation

@alanshurafa
Copy link
Copy Markdown
Contributor

Summary

Adds schemas/crm-person-tiers/ — a standalone relationship-tier schema for Open Brain's person/contact data. Classifies people referenced in thoughts into four tiers (connected, contact, known, unknown) with a configurable mention-frequency boost that promotes high-touch contacts to connected automatically.

What's included

  • crm_persons — UUID PK, relationship_tier CHECK-constrained to the four tiers, unique lowercased canonical_name, and an updated_at touch trigger.
  • crm_person_mentions — join table linking a person to a thought. No FK to thoughts by design — the contribution does not modify core tables (per repo guard rails).
  • crm_person_tiers(p_limit, p_offset, p_search, p_promote_min_mentions, p_promote_within) RPC — paginated listing with a configurable "connected" boost (defaults: 20 mentions within 7 days).
  • Optional page.tsx dashboard snippet for drop-in viewing.

Design notes

  • Standalone by intent — not dependent on an entity graph (differs from upstream ExoCortex's entity-embedded approach), so it works on a fresh Open Brain install.
  • thought_id is typed UUID to match docs/01-getting-started.md. README calls out the BIGINT alternative for older installs.
  • Core thoughts table is not modified.

Security model

The RPC runs as SECURITY INVOKER, so the caller's RLS applies. EXECUTE is granted only to service_role — no anon exposure. README includes a dedicated "Security Model" section.

Review status

Pre-review on the fork caught 2 P1s (SECURITY DEFINER + anon grant, and a pagination-before-ordering bug where LIMIT/OFFSET ran before effective_tier was globally ordered), 1 P2 (dashboard count label clarity), and 1 P3 (discriminated-union error panel). All addressed across 3 fix commits.

Fork pre-review PR: alanshurafa#18

…VOKER

The public crm_person_tiers RPC was declared SECURITY DEFINER with
EXECUTE granted to anon. An exposed Supabase anon key would let any
caller dump every person's canonical_name, aliases, and metadata from
crm_persons -- no RLS is enabled and SECURITY DEFINER bypasses table
grants.

Least-privilege fix: function is now SECURITY INVOKER and execute is
granted to authenticated + service_role only. Added README "Security"
section documenting the intended install path (server-side service_role
or authenticated-with-RLS) and how to opt into anon access on purpose.

Flagged by Claude gsd-code-reviewer + Codex review.
The crm_person_tiers RPC applied LIMIT/OFFSET inside person_page CTE
before mention_count and effective_tier were computed. The final
ORDER BY effective_tier priority then only re-sorted the preselected
page -- for datasets above p_limit, page 1 could miss high-mention
"connected" people that sort later by last_seen, contradicting the
documented ordering contract (tier priority first).

Restructured the CTE chain to: filter -> aggregate mention_counts ->
compute effective_tier -> global ORDER BY -> LIMIT/OFFSET. Added an
inline comment explaining why ordering must precede pagination.

Flagged by Claude gsd-code-reviewer + Codex review.
Two dashboard snippet fixes:

P2 - per-tier summary strip derived counts from the first 400 loaded
rows while the adjacent caption showed total persons, making the strip
look like a global breakdown. Caption now reads "N loaded (of M total
-- per-tier counts above reflect loaded rows only, capped at 400)"
when total > loaded. No aggregate RPC required.

P3 - server component threw on RPC failure, triggering Next.js's
default error surface for drop-in consumers that lack a route-level
error boundary. fetchPersonTiers now returns a typed discriminated
union; CrmPage renders an in-page rose error panel with a troubleshoot
hint (run NOTIFY pgrst, 'reload schema') and logs detail server-side.

Flagged by Claude gsd-code-reviewer + Codex review.
@github-actions github-actions Bot added the schema Contribution: database extension label Apr 21, 2026
@github-actions github-actions Bot added the recipe Contribution: step-by-step recipe label Apr 22, 2026
@alanshurafa
Copy link
Copy Markdown
Contributor Author

Refreshing upstream checks after fork-side readiness cleanup.

@alanshurafa alanshurafa reopened this Apr 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

recipe Contribution: step-by-step recipe schema Contribution: database extension

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant