Skip to content
Help
HelpTrust & provenance

Enrichment & touchpoints

How connectors fan out into the Enrichment Gate, become usable / disclaimer / blocked facts, synthesize a profile DB, and personalize copy — with the rule that an un-receipted fact can never be inlined. · 5 min read

Enrichment applies the same discipline to personal data that the Gate applies to product claims. Just as a claim binds to a source span, a personal fact binds to a source connector, a lawful basis, and a freshness TTL. This is PRD amendment A2. The lane lives in pipeline/enrichment/.

Connectors fan out

When a lead submits the form, pipeline/enrichment/engine.py fans out to a set of connectors (pipeline/enrichment/connectors.py), each answering one question and returning RawFacts tagged with the source id the Gate keys on:

  • Email-domain parse — a pure parse of the domain the user gave us (no network).
  • Public news RSS — keyed on the company name. Live in live mode, simulated by default.
  • Simulated firmographics (Clearbit/ZoomInfo class) and simulated account intent (6sense/Bombora class) — deterministic synthetic stand-ins for paid vendors; nothing calls a paid API.

The Enrichment Gate

Each fact passes the Enrichment Gate (pipeline/enrichment/gate.py), which checks four things against the human-owned policy in rules/helix_tenant.yaml: the source must be allow-listed, the recipient must have consented, the fact must be fresh (age within the source's TTL), and the key must not be a blocked PHI/PII key. The verdict is one of three:

usableallow-listed + consented + fresh — may be inlined, basis on file
disclaimerusable but account-level inferred — inlined with an "inferred" flag
blockedno basis / not allow-listed / stale / non-consent / PHI — never reaches a message

Synthesize, then personalize

The usable facts are merged into a Profile and written to the profile DB (profiles.sqlite), where every row is a receipt: value + source + basis + verdict. Personalization (Fork 1-B) may then inline usable/disclaimer facts into the copy — but each one is placed in a clearly labelled block and is recorded with its source and basis in the message receipts. Verified product claims still come from the claim Gate; enrichment adds the personal layer, it does not replace verification.

Property E1: an un-receipted fact can never be inlined. Personalization only ever iterates profile.usable_facts (verdict usable or disclaimer); blocked facts are withheld by construction. This is proved in tests/test_enrichment.py.

Default: synthetic, offline, $0

By default the demo runs in synthetic mode — every connector emits deterministic synthetic facts, so the build is offline, $0, and byte-identical. The one live connector is the news RSS (Fork 2-B), opt-in via PROVENANCE_ENRICH=live. Even live, it sends only the public company name to a public feed (never the recipient's name, email, or role), caches every response so re-runs replay deterministically, and fails honestly — no network means no fact, never a fabricated one. No PII goes to any third party.

See the Enrichment catalog for every source, its cost, its basis, and where the data lives.