The signals tables
signals is the CRM's sales-intelligence layer - first-class events like funding rounds, hires and acquisitions, attached to companies and people through two join tables. This page traces it top-down: where signals are served from, how one is born and attached, then the full schema across all three tables, their relationships and rules.
System design
interactive Start from the top. The signals feed never touches storage directly - it talks to the signals subdomain, the only thing that reads or writes signals and its two join tables. Producers emit signals during their runs; the UI reads them back as chips. Click a node to trace what it connects to.
Data flow - birth of a signal
interactiveHow a signal lands and is surfaced, as a closed loop. A producer emits an event, it normalizes to a canonical type, a row is written under RLS, a join row attaches it to a company or person, the UI reads it back as a chip - and the lifecycle closes back to the start. Click a stage to follow it.
Schema
interactiveThree tables: the signal itself plus two join tables that attach it to an entity. Pick a table; colour marks each column's kind, and clicking a column expands its category and any table it references. Everything is org-scoped; the signal carries its own provenance and soft-delete columns.
The intelligence event itself - one org-scoped row per signal, with provenance baked in.
- primary key
Primary key.
- text
Producer: sonar · envoy · manual · backfill.
- text
One of 13 canonical types: HIRING, FUNDING, IPO, ACQUISITION, DEBT, PARTNERSHIP, REBRANDING, LEADERSHIP_CHANGE, EXPANSION, NEW_LOCATION, TECH_PIVOT, PRODUCT_LAUNCH, AWARD.
- text
Human-readable summary of the event.
- timestamp
When the event happened.
- text
Link back to the source.
- text
Quoted excerpt from the source.
- timestamp
When the producer found it.
- timestamp
Audit timestamps.
- timestamp
Soft delete - queries filter NULL.
Relationships
interactiveSignals are cross-entity intelligence - they attach to companies and people through two scored join tables, and point back at the run that discovered them. Hover or click a table to trace its link and see its key columns.
signals attach to companies and people through two scored join tables, with provenance back to the workflow run.
Signals are cross-entity sales intelligence - what is happening to a company or person, observed by an agent. That is distinct from activities and communications, which record the interaction history between your team and the entity. One signal can attach to many companies and people; the unique join keys stop the same signal attaching twice.
First-class entity, RLS & lifecycle
modelThe database is storage only - no business logic. What it does enforce: tenant isolation, the canonical type set, idempotent attachment, and the soft-delete that keeps the feed clean.
ENG-661 promoted signals from a sonar JSONB blob into a relational signals table, so signals are queryable, filterable and de-dupable instead of buried in an opaque column.
13 fixed signal_type values (HIRING, FUNDING, IPO, ACQUISITION, DEBT, PARTNERSHIP, REBRANDING, LEADERSHIP_CHANGE, EXPANSION, NEW_LOCATION, TECH_PIVOT, PRODUCT_LAUNCH, AWARD) keep the feed consistent across every producer.
attachment_score on the join lets the UI rank which signals matter most for a given company or person.
unique (company_id, signal_id) and unique (person_id, signal_id) prevent the same signal attaching to the same entity twice.
deleted_at marks a signal removed; list queries filter deleted_at IS NULL. A signal can also detach from an entity without being deleted.
is_member_of_organization on all three tables gates every read. Superadmins bypass. The scoped client applies the same filter at the app layer.
The signals tables hold no rules of their own - tables, indexes, RLS and triggers only. Signals are the CRM's intelligence layer: written by agents, read by humans. Provenance travels with every row - source_agent records the producer and workflow_run_id records the run that found it - so where a signal came from is never lost.
Where this is going
v1 design The three tables above are what ships today. The decided v1 design splits this single org-scoped signals table into shared layers, so one objective fact can fan out to many orgs at different scores instead of being copied per org. This page stays the source of truth until that lands; the new model is specced on the intelligence-database pages.
The fact loses organization_id and becomes a shared, append-only, polymorphic store - company and person behind one subject_type. Relevance and score move out of the row.
A provenance log - one row per discovery - replaces the single workflow_run_id column, so a re-find by another run or the scraper is attributed without doubling the fact, and the sonar app reads its own results by ingest_path.
The join tables and their never-populated attachment_score give way to a per-org, RLS-per-org feed carrying the computed score, tier, origin and rank. A user pull writes ungated; an automated push is threshold-gated.
Every feed row records an origin (sonar or proactive); the launchpad shows a ranked mix of both. A competitor-ICP check can suppress a pushed lead for rival agencies sharing an ICP (sketched, not yet built).
The full design lives in Signals Intelligence Database (the shared store plus the provenance log) and Org Signal Feed (the per-org activation layer).