Skip to main content
Trust architecture · April 2026

Privacy isn't a policy. It's the architecture.

No DMs. Ever. ClarityLift turns workplace communication patterns into aggregate signal without ever exposing an individual. Three consent layers, one structural floor of ten, and a data path that makes re-identification not difficult. Not possible.

Cohort floor
n ≥ 10Hard. Structural. No override.
Integrations
Slack · Microsoft TeamsOAuth, scoped, revocable
Data residency
Azure US Eastsingle region at launch · multi-region planned
SOC 2
Type I · plannedtooling selection in progress
§ 01

Consent cascades from the organization down to the individual.

Each layer has a distinct consent model, distinct data access, and a distinct blast radius. Nothing reaches aggregation without passing through all three.

L1
Admin consent

Formal channels

Workspace admin authorizes ClarityLift to read public and admin-approved channels such as #general, #announcements, or named team channels. Scoped OAuth. No direct messages, ever.
consent_by
workspace_admin
revocable
instantly, tenant-wide
L2
Member opt-out

Per-employee consent

Every employee controls their own participation at /my-data. Opt out and their messages are dropped at the ingest gate before classification runs, even in channels the admin has enabled. Group DMs are not ingested at all — the scope is not requested and the handler drops the event type at step 1.
consent_by
individual_employee
revocable
per member, any time
L3
Structural aggregation

Cohort signal · n ≥ 10

Every query that leaves the aggregation engine is gated by a hard cohort floor of ten people. Fewer than ten and the query returns no signal. Not a warning, not a rounded number, not a placeholder. The floor is enforced in the query planner, not the UI.
consent_by
structural_invariant
revocable
n/a · cannot be disabled
Read the invariant: raw messages → hashed features → cohort-gated aggregates. No downstream system receives pre-aggregate data.fig.00 · permissions cascade
§ 02

Four filters. Every message passes all four or it does not exist.

ClarityLift connects to Slack or Microsoft Teams via standard OAuth, scoped to channel-read permissions. DM scopes are never requested. The moment an event lands at our webhook, it passes through four filters before anything else happens.

  1. 01

    DM rejection at ingest

    Every inbound webhook carries a channel-type indicator. If the channel type is a DM or a group DM, the handler returns HTTP 200 and drops the event before any processing runs. Enforced on the server, not in the classifier. A misconfigured classifier cannot expose DMs because the DMs never reached the classifier.

    src/app/api/events/slack/route.ts
  2. 02

    Channel opt-in

    A channel is analyzed only if an admin has explicitly enabled it in the ClarityLift dashboard. Opt-in defaults to off. Connecting a workspace does not analyze anything until channels are picked. Disabling a channel stops analysis within 30 seconds on the next ingest cycle and deletes pending jobs in the durable queue.

    ChannelSettings.enabled
  3. 03

    In-memory processing, retention-zero

    The message body is held in a request-scoped variable long enough to run the fast classifier (regex, no I/O) and, if the message matches a pattern of interest, one call to the LLM provider. Provider calls use retention-zero options: OpenAI store: false, Azure OpenAI inside Microsoft's boundary, Anthropic under ZDR contract. The moment the classifier returns, the message body drops out of scope and is garbage-collected. No row in our database contains message text.

    processMessage()
  4. 04

    Ten-member minimum for every output

    Signal rows carry a channelId and a teamId, never a user id. Before any team-level score is computed, a database subscriber checks that the associated team has at least ten members. Teams below that floor are dropped from the scoring output entirely. The floor is enforced at the database layer, not the UI layer. It cannot be bypassed by a misbehaving client. Admins can raise the floor above ten but not lower it.

    SignalFloorSubscriber

What lands in the database is small. A row per detected signal with seven fields: organization id, team id, channel id, signal type, severity, confidence, and a detection timestamp. That is the entire persisted shape. No message id that resolves to a user, no message text, no author, no reactant list.

§ 03

Message lifecycle, from ingest to insight.

A message spends milliseconds as text, seconds as a hashed feature vector, and the rest of its life as a statistic contributed by a cohort of ten or more.

fig.01 — message lifecycle
01 · ingest

Webhook in

Slack Events API or Microsoft Graph webhook. Raw message held in memory only. Never persisted in raw form.

0 ms on disk
02 · verify

Signature check

HMAC-SHA256 over timestamp + raw body. Constant-time compare. Rejects before any downstream code runs. 5-minute replay window.

reject on fail
03 · gate

Consent gate

Per-channel enablement plus per-employee opt-in resolved against the workspace members table. Messages from non-consenting senders are dropped here.

fail-closed
04 · classify

Classifier (Azure OpenAI)

Microsoft Azure OpenAI deployment, eastus region. Prompt stays inside Microsoft's Azure boundary; OpenAI as a company never receives it. Returns signal type, severity, confidence. Structured labels, no message text.

Azure US · no training
05 · floor

Cohort floor (n ≥ 10)

Structural check. Signals tied to a team with fewer than ten consenting members are dropped before persistence. No override, no flag, no admin setting.

COHORT_FLOOR = 10 (const)
06 · emit

Aggregate signal row

Team id, signal type, severity, confidence, HMAC-hashed sender id (keyed with org id so hashes are not cross-tenant joinable). First persistent surface in the pipeline.

metadata only · audited
in-memory / hashedstructural gate · cannot be bypassedpersistent, queryable surfacetarget: sub-2s median · sub-5s p99
§ 04

Who sees what, at which aggregation level.

Every cell in this matrix maps to an RBAC rule in the query planner. Every access, including denials, writes to an append-only audit log your security team owns.

RoleChannel listIndividual messageCohort signal (n≥10)Raw exportAudit log
Employee
default
own onlydeniedtheir cohortsdeniedown access only
People manager
manager_role
team-scopeddeniedif n≥10 on teamdeniedown access only
HR leader
people_ops
authorizeddeniedall cohorts ≥10deniedown access only
Workspace admin
tenant_admin
authorizeddeniedall cohorts ≥10deniedfull tenant log
ClarityLift staff
support, engineering
denied by defaultdenied alwaysdenied by defaultdenied alwaysaccess logged
Audit stream · tenant-owned · append-onlyappend-only · 18-month default retention · configurable (Enterprise) · SIEM export on roadmap
2026-04-22 09:14:02ZALLOWpeople_ops/ana.r · query(cohort=engineering, n=47) · returned
2026-04-22 09:14:08ZDENYpeople_ops/ana.r · query(cohort=engineering/platform-infra, n=6) · floor
2026-04-22 09:17:41ZALLOWmanager/jay.k · query(cohort=design, n=14) · returned
2026-04-22 09:22:10ZNOTEtenant_admin/morgan.t · reviewed audit window · downloaded CSV
2026-04-22 09:31:55ZDENYclaritylift/support · requested raw_export · break-glass not initiated
§ 05

“What if someone asks you to reveal an individual?”

This is the question procurement reviewers actually care about. A policy-based answer (we wouldn't) is not an answer. A structural answer is.

We can't. That's the point.

Raw messages are not stored. By the time data reaches a surface anyone can query, the only record of an individual message is a salted hash and a contribution to a cohort counter. There is no row to retrieve, no table to decrypt, no ops engineer who can be socially engineered into exporting it.

If a CEO asks what a specific person said, or a subpoena demands a named individual's sentiment history, the honest and complete answer is the same.

“That data does not exist in our system. It was never written to disk in a form that could be indexed by person. The architecture refuses the request before anyone has to.”
  • 01No raw retention. Messages exist in memory long enough to extract features. Then they are dropped.
  • 02Per-tenant salts. Hashed tokens are not cross-tenant joinable. A leak in one tenant cannot re-identify another.
  • 03Floor in the planner. The cohort floor lives in the query compiler, not the UI. Removing it would require shipping a new build through code review and a signed release approval. The constant is a structural invariant, not a configuration flag.
  • 04No individual endpoint exists. There is no API route, internal or external, that returns a named person's messages or features. Not access-controlled. Not built.
aggregator/cohort_gate.pyread-only
# Simplified from production implementation · full source under NDA.
# Enforced in the query planner. Not a policy.
# Changes require a signed release + tenant approval.

from typing import Optional
from claritylift.core import Cohort, Signal, AuditLog

COHORT_FLOOR = 10  # structural invariant · do not parameterize

def emit_signal(cohort: Cohort, principal) -> Optional[Signal]:
    if cohort.size < COHORT_FLOOR:
        AuditLog.deny(
            principal,
            reason="cohort_floor",
            observed_n=cohort.size,
        )
        return None            # no signal. not a rounded value.

    AuditLog.allow(principal, cohort=cohort.id, n=cohort.size)
    return Signal.from_cohort(cohort)


# There is no sibling function that returns individual data.
# `emit_signal` is the only export from this module.
__all__ = ["emit_signal"]
§ 06

Negative capabilities, listed explicitly.

The category this product sits in is crowded with tools that claim these limits as policy. For ClarityLift they are structural, baked into the schema and the ingest pipeline.

  • Cannot analyze direct messages or group DMs.

    DM scopes are never requested during OAuth. If Slack or Teams somehow delivered a DM to our webhook anyway, the handler rejects it before any classifier call.

  • Cannot identify individual speakers in any output.

    No output row carries a user id. The signal schema has no such column. If an admin wanted to retroactively attribute a signal to a specific person, they could not. The data to do it was never persisted.

  • Cannot retrieve deleted messages.

    We do not store message text at all, deleted or not. If a message is deleted before our fast classifier processes it, we never see it. If it is deleted after, we have no body to recover.

  • Cannot export message content.

    DSAR exports include every persisted row. The message-text row does not exist. Exports contain signal counts, pillar scores, insights, and audit entries. Nothing transcribable back to a conversation.

  • Cannot score teams below ten members.

    The compliance floor is a pre-insert database check. A team of nine produces no team-level score. Even if the admin asked for one, even if every signal confidence was 1.0.

  • Cannot be used for hiring, firing, performance review, or compensation decisions.

    Prohibited in the Acceptable Use Policy every admin acknowledges at onboarding. Also the reason outputs never include individual attribution. They could not be used that way even if a rogue admin wanted to.

§ 07

Retention, encryption, subprocessors, deletion.

Procurement-grade detail about what persists, where, for how long, and under whose key.

RETENTION

What persists and for how long

Message textzero retention · in-memory, then discarded
Signal rows, scores, insightsindefinite by default · per-org retention policies on the enterprise roadmap
Audit log18-month default · investigation and audit-support window · configurable per-org on the Enterprise roadmap
Consent records18-month default · retained beyond deletion per state employment-claim limitations (US/Canada)
ENCRYPTION

Keys and transport

In transitTLS 1.2+ · webhook ingress, dashboard, LLM egress, Key Vault
At restAzure SQL TDE · column-level AES-256-GCM on OAuth refresh tokens
Key custodyCL_ENCRYPTION_KEY stored in Azure Key Vault · never in the repo
PROCESSING LOCATION

Where the bits live

Primary regionAzure East US
In-regiondatabases, application servers, Key Vault, Redis cache, Azure OpenAI deployments
LLM egressinside Microsoft's boundary (Azure OpenAI) or contracted ZDR (Anthropic)
Multi-regionplanned
SUBPROCESSORS

Four, listed completely

Microsoft CorporationAzure (infrastructure, DB, Key Vault, Redis), Azure OpenAI (LLM inference), Azure Communication Services (transactional email)
Anthropic PBCoptional ZDR classification backend
OpenAI, LPlegacy direct-OpenAI classification · new workspaces default to Azure OpenAI
PostHog, Inc.authenticated-dashboard product analytics only · NOT on marketing pages · autocapture off · session replay disabled

No Google Analytics, no Segment, no ad tech, no session-replay tool on the product.

DELETION

How to exit

A workspace admin can request full deletion from the settings page. The request enters a 30-day grace window during which ingestion pauses and cancellation is possible. After 30 days, every signal, score, insight, and connection record is physically deleted. The Organization row survives as an anonymized stub so audit log foreign keys remain valid. Nothing else.

§ 09

Certifications, residency, and what's in motion.

Procurement-grade documentation is available under NDA. We publish what we can today, and we're honest about what's in flight.

PLANNED

SOC 2 Type I · planned

SOC 2 Type I is the near-term priority. We are scoping the control framework and selecting the continuous-monitoring platform. Timeline and auditor will be published once the observation period begins.

Status updates · /privacy-architecture/status
LIVE

Azure US data residency

Tenants provision in Azure East US at launch. Data at rest is encrypted with Azure-managed keys. Customer-managed keys (CMK) are on the roadmap. Multi-region replication is planned.

Region · US EastMulti-region · planned
LIVE

Dependency + secret scanning

Gitleaks scans every PR and full git history for leaked secrets. npm audit runs on every PR, every push to master, and nightly — blocks CI on HIGH or CRITICAL production-dep advisories. Third-party penetration test is planned; DPA is GDPR-aligned and shared under NDA.

Scanning · gitleaks · npm auditPentest · planned

Want your security team in the room?

We run a 45-minute technical review with our engineering lead and your IT and security team. Real system, real query planner, real audit log.