diff --git a/packages/shared/src/telemetry/README.md b/packages/shared/src/telemetry/README.md index 3a741ecdfc..b81ca4eebc 100644 --- a/packages/shared/src/telemetry/README.md +++ b/packages/shared/src/telemetry/README.md @@ -26,6 +26,7 @@ Use these files when reviewing or changing telemetry code: | Shared reusable enum domains | Named exports in `constants.ts` | | First-party typed emit helpers | `events.ts` | | Generic client behavior | `client.ts` | +| Retention windows and event class assignments | `RETENTION_DAYS` and `EVENT_RETENTION_CLASS` in `retention.ts` | Do not copy generated event lists or dimension tables into this README. They will drift as the generated contract changes. @@ -114,3 +115,21 @@ them. Before opening a pull request, verify that the emitted code, typed helpers, and generated telemetry contract agree. If they disagree, fix the contract or code rather than documenting around the mismatch in this README. + +## Retention + +Retention windows are documented in `retention.ts`. Each event is assigned a +retention class; the class determines the window in days. This is a +housekeeping and query-cost concern managed by data-infra, not a schema +concern — updating a retention window does not require a schema version bump. + +Current classes: + +| Class | Window | Description | +| --- | --- | --- | +| `operational_enum_count` | 90 days | Enum/boolean/count/bucket events. No token material, no PII. | + +When a new event carries only enums, booleans, counts, or coarse buckets and +no token material or PII, assign it to `operational_enum_count` in +`EVENT_RETENTION_CLASS`. If no existing class fits, define a new class in +`RETENTION_DAYS` and document it here. diff --git a/packages/shared/src/telemetry/index.ts b/packages/shared/src/telemetry/index.ts index 16b5d8de5d..494fbbd829 100644 --- a/packages/shared/src/telemetry/index.ts +++ b/packages/shared/src/telemetry/index.ts @@ -32,3 +32,5 @@ export type { EventDimensionsMap, PaperclipEventName, } from "./generated/paperclip-telemetry.js"; +export { EVENT_RETENTION_CLASS, RETENTION_DAYS } from "./retention.js"; +export type { RetentionClass } from "./retention.js"; diff --git a/packages/shared/src/telemetry/retention.test.ts b/packages/shared/src/telemetry/retention.test.ts new file mode 100644 index 0000000000..db02ed011e --- /dev/null +++ b/packages/shared/src/telemetry/retention.test.ts @@ -0,0 +1,21 @@ +import { describe, expect, it } from "vitest"; +import { + EVENT_RETENTION_CLASS, + RETENTION_DAYS, +} from "./retention.js"; + +describe("telemetry retention contract", () => { + it("operational_enum_count has a 90-day retention window", () => { + expect(RETENTION_DAYS.operational_enum_count).toBe(90); + }); + + it("codex.credential_health is assigned the operational_enum_count class", () => { + expect(EVENT_RETENTION_CLASS["codex.credential_health"]).toBe("operational_enum_count"); + }); + + it("codex.credential_health resolves to 90 days", () => { + const cls = EVENT_RETENTION_CLASS["codex.credential_health"]; + expect(cls).toBeDefined(); + expect(RETENTION_DAYS[cls!]).toBe(90); + }); +}); diff --git a/packages/shared/src/telemetry/retention.ts b/packages/shared/src/telemetry/retention.ts new file mode 100644 index 0000000000..264c769e8e --- /dev/null +++ b/packages/shared/src/telemetry/retention.ts @@ -0,0 +1,48 @@ +/** + * Telemetry retention contract. + * + * Defines the retention class for each Paperclip telemetry event and the + * corresponding retention window in days. This is a housekeeping/cost + * concern separate from the event schema: it is updated by data-infra + * and does not require a schema version bump. + * + * Retention class definitions + * ───────────────────────────────────────────────────────────────────────── + * operational_enum_count + * Events that carry only enums, booleans, counts, and coarse buckets. + * No token material (I5-clean) and no PII. Retention is a housekeeping + * and query-cost concern, not a privacy concern. + * Window: 90 days. + */ + +/** Retention window in days for each telemetry event class. */ +export const RETENTION_DAYS = { + /** + * Enum/count/bucket events: no token material, no PII. + * Retention is a housekeeping and query-cost concern only. + */ + operational_enum_count: 90, +} as const satisfies Record; + +/** Identifies which retention class an event belongs to. */ +export type RetentionClass = keyof typeof RETENTION_DAYS; + +/** + * Maps first-party event names to their retention class. + * + * Key type is intentionally `string` rather than a tighter union because this + * contract also covers events emitted by external systems (e.g. the Codex CLI) + * that are not part of the shared generated `PaperclipEventName` type. When an + * event is promoted to the first-party schema, prefer tightening its entry to + * the named union type via an overloaded record. + * + * codex.credential_health + * Emits credential-observability fields: enums (credential source, sync + * outcome), booleans (rotation detected, refresh succeeded), and coarse + * counts (rotations detected). No token material and no PII. + * External-system event (Codex CLI) — not yet in PaperclipEventName. + * Class: operational_enum_count — 90-day window. + */ +export const EVENT_RETENTION_CLASS: Partial> = { + "codex.credential_health": "operational_enum_count", +};