# Cohorts, telemetry and redaction ## Cohorts Four populations, deliberately coarse. Blueprint §6.3 asks for cohorts stable enough to compare over time and no more granular than the analysis requires. | Cohort | Assigned by | Why it is distinct | |---|---|---| | `hall-publishing-jobs` | `X-FLUID-Consumer` prefix `hall-` | Batch behaviour; publishes many posts at once. Becomes the `pr-hall-of-helix` campaign job | | `helix-forge-agents` | `X-FLUID-Consumer` prefix `agent-` | One post at a time, at session end | | `operators` | `X-FLUID-Consumer` prefix `op-` | Rare, deliberate, often corrective | | `telegram-subscribers` | Not a caller | An observed population, never an API consumer | `telegram-subscribers` deserves care. It appears in experiment allocation and in engagement metrics, but it never authenticates and never appears as `ConsumerRef`. Treating readers as consumers would be the first step toward observing them individually. ## Redaction ``` CohortMinimumSize: 5 RetentionDays: 365 AllowRawPayload: false ``` Beyond the fluid-core defaults, this interface adds: - **No Telegram user identity, ever.** Canon ID-01 identities — user ids, usernames, display names of subscribers — must not reach the evidence store in any form, redacted or otherwise. There is no legitimate analysis that needs them, so the safe rule is that they never arrive. - **Post text is not telemetry.** The body appears in the request and is kept in the publication record, because it is what was said in HelixForge's name. It must not appear in a telemetry event: observation is for measuring how posts performed, not for accumulating a second copy of what they said. - **The bot token appears nowhere.** Not in errors, not in traces, not in redacted form. ## The pseudonymization salt Generate once, store in OpenBao alongside the bot token, inject as `FLUID_REDACTION_SALT`: ```bash openssl rand -hex 32 ``` It must never change. Rotating it makes every previously-seen consumer look new, which silently invalidates every longitudinal comparison the interface has accumulated. If it is ever compromised, the correct response is to accept the loss and start a fresh evidence baseline, not to rotate quietly. ## Metrics | Metric | Role | Source | |---|---|---| | `engagement_rate` | primary | Telegram channel statistics, aggregate | | `unreviewed_publication` | guardrail | Adapter | | `missing_source_attribution` | guardrail | Adapter | | `missing_subject_consent` | guardrail | Adapter | | `error_rate` | guardrail | Gateway | | `source_link_rate` | secondary | Telegram channel statistics, aggregate | | `forward_rate` | secondary | Telegram channel statistics, aggregate | | `time_to_first_view` | learning | Telegram channel statistics, aggregate | `engagement_rate` is the one to be careful about. Telegram reports views, reactions and forwards per message, not per reader, and a "rate" therefore has subscribers as its denominator rather than people who saw the post. It is a proxy, and a noisy one at small subscriber counts. Say so in the experiment's amendment record rather than letting the comparison look cleaner than it is. ## Variant attribution Every post carries a `variant` label naming the composition strategy that produced it. This interface treats it as opaque: it records the label, and reports engagement grouped by it. That is deliberately the whole of the interface's involvement in composition. The campaign compares its own strategies using this data, running its own FLUID experiments over its own artifacts. The interface does not know what `variant: warm-specific-v2` means and must not acquire an opinion about it. Without this field the campaign would have to infer which post came from which strategy by timestamp, which is exactly the kind of reconstruction that quietly stops being accurate.