Files
PaperClipAI/packages/shared/src/telemetry/README.md
T
1b16f9611c feat(telemetry): document credential health retention (#9248)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The shared telemetry package (`packages/shared/src/telemetry`)
defines the public contract for first-party telemetry events — event
names, dimension shapes, and now retention windows
> - A new telemetry event, `codex.credential_health`, carries
credential-observability fields (enums, booleans, counts, coarse
buckets) — no token material and no PII
> - The retention window for this event and its class was undocumented
at the contract level, leaving data-infra and reviewers without a
discoverable source of truth
> - This pull request adds `retention.ts` as the canonical
retention-contract surface, assigns `codex.credential_health` to the
`operational_enum_count` class (90-day window), exports the contract
from the shared package, and updates the README
> - The benefit is that the retention window is discoverable from the
event definition rather than being implicit pipeline knowledge, and the
no-token/no-PII note is locked in a contract test rather than relying on
prose

## Issue Description

No public GitHub issue exists for this change. Inline description
follows the [feature request
template](.github/ISSUE_TEMPLATE/feature_request.yml):

### Problem or motivation

`packages/shared/src/telemetry/` applies a 90-day retention window to
`codex.credential_health` events at the pipeline level, but this policy
is not stated anywhere in the shared telemetry contract. Without a
discoverable retention declaration, reviewers and data-infra must read
pipeline configuration to understand retention behaviour — there is no
contract-level source of truth. The `codex.credential_health` event
carries only enums, booleans, counts, and coarse buckets — no token
material and no PII.

### Proposed solution

Add a `retention.ts` module to the shared telemetry package that defines
the `operational_enum_count` retention class (90-day window) and maps
`codex.credential_health` to it. Export the contract from the package
index and add a focused contract test. Update the README with a
Retention section and a pointer in the Public Sources table. This is
additive documentation only — no runtime paths change.

### Alternatives considered

Keep retention implicit in pipeline configuration only. Rejected: this
leaves no discoverable, versioned contract for reviewers or data-infra,
and means every consumer must read pipeline config to understand
retention semantics. A schema-level declaration is the correct long-term
home.

### Roadmap alignment

Aligns with the telemetry contract hardening track — making implicit
operational knowledge explicit and testable at the shared-package level.

## What Changed

- **`packages/shared/src/telemetry/retention.ts`** (new): defines
`RETENTION_DAYS` (class → days) and `EVENT_RETENTION_CLASS` (event name
→ class). `operational_enum_count` is the only class: 90-day window for
enum/count/bucket events with no token material or PII.
`codex.credential_health` is the first entry. The `string` key type is
intentional to accommodate cross-system events (e.g. the Codex CLI) not
yet promoted to the first-party `PaperclipEventName` schema.
- **`packages/shared/src/telemetry/retention.test.ts`** (new): three
focused assertions — `operational_enum_count` is 90 days,
`codex.credential_health` is assigned that class, and the resolved
window is 90 days.
- **`packages/shared/src/telemetry/index.ts`**: exports
`RETENTION_DAYS`, `EVENT_RETENTION_CLASS`, and `RetentionClass` from the
shared package.
- **`packages/shared/src/telemetry/README.md`**: adds `retention.ts` to
the Public Sources table and a new Retention section with a class
reference table and guidance for future assignments.

## Verification

- `git diff --check` passes (no whitespace errors)
- `pnpm --filter @paperclipai/shared exec vitest run
src/telemetry/retention.test.ts src/telemetry/readme-contract.test.ts` —
runs the new contract tests and the existing readme-contract test
- `pnpm --filter @paperclipai/shared typecheck` — confirms the new
exports compile cleanly
- CI gates green

## Risks

Low risk. This is a documentation-only addition:
- No new telemetry event, dimension, emitter, or schema change
- No runtime code paths changed
- The new file is tree-shaken away in any consumer that doesn't import
from it
- `satisfies Record<string, number>` on `RETENTION_DAYS` ensures the
type stays correct as new classes are added

## Model Used

Claude Sonnet 4.6 (`claude-sonnet-4-6`) — Anthropic, 200k context
window, tool use enabled, no extended thinking mode. Used for code
authoring and PR composition.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Harold Kim <harold.kim@paperclip.ing>
Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Harold Kim <harold-kim@paperclip.ing>
2026-07-08 21:27:39 -07:00

136 lines
6.5 KiB
Markdown

# Telemetry Data Contract
This document explains how contributors should use Paperclip's public telemetry
contract. It intentionally does not list individual events or dimensions.
The canonical source for first-party event names, dimensions, optionality,
allowed primitive value types, and enum descriptions is
`packages/shared/src/telemetry/generated/paperclip-telemetry.ts`.
Shared enum constants live in `packages/shared/src/constants.ts`. Use those
constants when code needs a reusable domain, but treat the generated telemetry
types as the final authority for emitted first-party telemetry shapes.
## Public Sources
Use these files when reviewing or changing telemetry code:
| Contract item | Public source |
| --- | --- |
| First-party event names | `PaperclipEventName` in `generated/paperclip-telemetry.ts` |
| Per-event dimensions and optionality | `EventDimensionsMap` in `generated/paperclip-telemetry.ts` |
| Enum descriptions for telemetry dimensions | `PAPERCLIP_ENUM_DESCRIPTIONS` in `generated/paperclip-telemetry.ts` |
| Schema version and event envelope helpers | `SCHEMA_VERSION`, `makeEvent()`, and `makeBatch()` in `generated/paperclip-telemetry.ts` |
| Runtime-safe event names and dimensions | `TelemetryEventName` and `TelemetryEventDimensions` in `types.ts` |
| Allowed primitive dimension values | `TelemetryDimensionValue` in `types.ts` |
| 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.
## Emission Boundary
Paperclip telemetry uses named events with explicit dimension fields. Treat
open-ended string dimensions as public contract values, not as a place for user
content or private operational data. Do not send PII, secrets, credentials,
private paths, prompts, model output, or other sensitive values through
telemetry dimensions.
Telemetry emitters send raw dimension values. They must not pre-normalize
enum-like values into a reporting form just to match today's known domain.
The receiving layer owns canonicalization. Keeping canonicalization in one place
means emitters can stay simple and accurate: emit what the product observed, use
the generated contract for required and optional fields, and let the receiving
layer decide how legacy spellings, aliases, unknown names, and future values map
to a stable reporting shape.
Do not add client-side lowercasing, alias mapping, or fallback mapping unless
the generated telemetry contract specifically requires that emitted value.
If a dimension is privacy-protected before emission, emit only the protected
value and its matching public marker as defined by the typed helper or generated
contract. Do not emit private source material in telemetry dimensions.
## Dimension Values
Telemetry dimension values must be primitives. Use only the value types allowed
by `TelemetryDimensionValue`:
- `string`
- `number`
- `boolean`
Do not emit `null`, `undefined`, arrays, or objects as dimension values. Optional
dimensions should be omitted when absent.
When a dimension is enum-like, use the shared constant from `constants.ts` when
one exists. If no shared constant exists, use the generated telemetry type as the
domain. In all cases, the generated telemetry type remains the source of truth
for the emitted value.
## Required, Optional, And Sentinel Values
Required and optional dimensions are defined by `EventDimensionsMap`.
Required dimensions must be present for every event of that name. Optional
dimensions should be emitted only when the value is known and useful.
Sentinel values are only for required fields that have no observed raw value at
the emitting layer. Do not use a sentinel to hide a concrete value that is new,
custom, or not yet represented by a shared constant. Emit the concrete raw value
and let the receiving layer canonicalize it.
## Adding Or Changing Telemetry
Client code is responsible for emitting approved telemetry events at the right
place in the product. It is not responsible for deciding which new events should
exist. Do not introduce ad hoc event names, dimensions, or enum domains in client
code; they must exist in the generated telemetry contract before emitters use
them.
1. Start from `generated/paperclip-telemetry.ts`. The generated types are what
reviewers use to verify event names, dimensions, optionality, value types,
enum descriptions, and schema version.
2. Choose stable event and dimension names. Do not include user content, local
machine details, secrets, credentials, private paths, or values that are not
part of the public event contract.
3. Use only `string`, `number`, or `boolean` dimension values.
4. Reuse a shared constant from `constants.ts` for enum-like dimensions when one
exists. If the generated telemetry domain has values beyond a shared constant,
keep the emitter aligned with the generated telemetry type.
5. Keep emitters raw. Do not normalize, alias-map, or lowercase enum-like values
in the client unless the generated contract explicitly calls for that emitted
value.
6. Add or update a typed helper in `events.ts` when the event is first-party and
should have a stable helper API.
7. Update tests for helper behavior, including raw pass-through for enum-like
values when that is the intended boundary.
8. Update this README only when the contributor workflow, source-of-truth
pointers, or durable invariants change. Do not add an event catalog here.
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.