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.
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.
Formal channels
Per-employee consent
Cohort signal · n ≥ 10
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.
- 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 - 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 - 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() - 04
Ten-member minimum for every output
Signal rows carry a
channelIdand ateamId, 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.
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.
Webhook in
Slack Events API or Microsoft Graph webhook. Raw message held in memory only. Never persisted in raw form.
0 ms on diskSignature check
HMAC-SHA256 over timestamp + raw body. Constant-time compare. Rejects before any downstream code runs. 5-minute replay window.
reject on failConsent gate
Per-channel enablement plus per-employee opt-in resolved against the workspace members table. Messages from non-consenting senders are dropped here.
fail-closedClassifier (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 trainingCohort 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)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 · auditedWho 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.
| Role | Channel list | Individual message | Cohort signal (n≥10) | Raw export | Audit log |
|---|---|---|---|---|---|
Employee default | own only | denied | their cohorts | denied | own access only |
People manager manager_role | team-scoped | denied | if n≥10 on team | denied | own access only |
HR leader people_ops | authorized | denied | all cohorts ≥10 | denied | own access only |
Workspace admin tenant_admin | authorized | denied | all cohorts ≥10 | denied | full tenant log |
ClarityLift staff support, engineering | denied by default | denied always | denied by default | denied always | access logged |
“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.
# 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"]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.
Retention, encryption, subprocessors, deletion.
Procurement-grade detail about what persists, where, for how long, and under whose key.
What persists and for how long
Keys and transport
CL_ENCRYPTION_KEY stored in Azure Key Vault · never in the repoWhere the bits live
Four, listed completely
No Google Analytics, no Segment, no ad tech, no session-replay tool on the product.
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.
US labor law, privacy regimes, and works councils.
Differentiating content that demonstrates real legal thinking. Named-counsel opinions are available to enterprise prospects on request.
NLRA Section 7 (US private sector, non-union)
Section 7 of the National Labor Relations Act protects concerted employee activity — wage discussion, working-conditions discussion, union organizing — in non-union workplaces as well as union ones. A tool that observes the channels where such activity happens is exposed even if its output is aggregate. The 10-member floor plus the absence of individual attribution is the defense: the tool cannot identify a specific speaker, and therefore cannot be used to retaliate against concerted activity.
Active collective bargaining agreements
ClarityLift is not currently configured for workplaces with collective bargaining agreements covering employee-communications analysis, active union organizing campaigns, or where communications analysis has been a recent bargaining subject. This is a sales-qualification filter, not a ToS exclusion. Enterprise tier will ship with a configuration mode appropriate for those workplaces. Until then, we decline the engagement.
GDPR / EU works councils
US and Canada are the current addressable scope. The architecture is compatible with GDPR aggregation posture (Art. 89 research exemption applies when outputs cannot re-identify individuals) but EU-specific features such as works council notification flows and DPIA scaffolding are out of scope for v1.
Colorado AI Act and similar state laws
Jurisdictions with algorithmic-decision laws (Colorado AI Act, Illinois AI Video Interview Act, NYC Local Law 144) specifically scope “consequential decisions” about individuals — hiring, firing, pay. ClarityLift outputs cannot be used that way, and the AUP every admin acknowledges prohibits it. That posture places the product outside the high-risk scope of those laws, confirmed by counsel.
This section summarizes ClarityLift's current legal posture. It is not legal advice for your organization.
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.
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.
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.
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.
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.