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
livemode, 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:
| usable | allow-listed + consented + fresh — may be inlined, basis on file |
| disclaimer | usable but account-level inferred — inlined with an "inferred" flag |
| blocked | no 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.
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.