@openpond/evals 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CONTRACT.md +13 -1
  2. package/README.md +26 -2
  3. package/conformance/telemetry/v1/invalid-batch.json +17 -0
  4. package/conformance/telemetry/v1/valid-batch.json +58 -0
  5. package/dist/index.js +5 -0
  6. package/dist/learned-preference.js +334 -0
  7. package/dist/preferences.js +77 -11
  8. package/dist/telemetry/index.js +4 -0
  9. package/dist/telemetry-analysis.js +183 -0
  10. package/dist/telemetry-bundle.js +60 -0
  11. package/dist/telemetry-catalog.js +50 -0
  12. package/dist/telemetry.js +112 -0
  13. package/dist/types/index.d.ts +5 -0
  14. package/dist/types/index.d.ts.map +1 -1
  15. package/dist/types/learned-preference.d.ts +282 -0
  16. package/dist/types/learned-preference.d.ts.map +1 -0
  17. package/dist/types/preferences.d.ts +52 -4
  18. package/dist/types/preferences.d.ts.map +1 -1
  19. package/dist/types/telemetry/index.d.ts +5 -0
  20. package/dist/types/telemetry/index.d.ts.map +1 -0
  21. package/dist/types/telemetry-analysis.d.ts +116 -0
  22. package/dist/types/telemetry-analysis.d.ts.map +1 -0
  23. package/dist/types/telemetry-bundle.d.ts +309 -0
  24. package/dist/types/telemetry-bundle.d.ts.map +1 -0
  25. package/dist/types/telemetry-catalog.d.ts +269 -0
  26. package/dist/types/telemetry-catalog.d.ts.map +1 -0
  27. package/dist/types/telemetry.d.ts +266 -0
  28. package/dist/types/telemetry.d.ts.map +1 -0
  29. package/package.json +12 -2
  30. package/schemas/telemetry/v1/evidence-completeness.schema.json +82 -0
  31. package/schemas/telemetry/v1/evidence-reference.schema.json +41 -0
  32. package/schemas/telemetry/v1/metric-definition.schema.json +96 -0
  33. package/schemas/telemetry/v1/metric-observation.schema.json +182 -0
  34. package/schemas/telemetry/v1/run-metric-summary.schema.json +98 -0
  35. package/schemas/telemetry/v1/run-telemetry-batch.schema.json +405 -0
  36. package/schemas/telemetry/v1/run-telemetry-event.schema.json +201 -0
  37. package/schemas/telemetry/v1/telemetry-cohort.schema.json +100 -0
  38. package/schemas/telemetry/v1/telemetry-export-bundle.schema.json +655 -0
@@ -0,0 +1,183 @@
1
+ import { z } from "zod";
2
+ import { ReleaseHashSchema, ReleaseIdSchema, ReleaseTimestampSchema, contentHash } from "@openpond/harness";
3
+ import { getCoreMetricDefinition, validateCoreMetricObservation } from "./telemetry-catalog.js";
4
+ import { MetricObservationSchema, RunTelemetryEventSchema, TelemetryVisibilitySchema, } from "./telemetry.js";
5
+ export const TelemetryCohortSchema = z.object({
6
+ checkpointIds: z.array(ReleaseIdSchema).max(1_000),
7
+ steps: z.array(z.number().int().nonnegative()).max(10_000),
8
+ scenarioIds: z.array(ReleaseIdSchema).max(100_000),
9
+ rolloutGroupIds: z.array(ReleaseIdSchema).max(100_000),
10
+ attemptIds: z.array(ReleaseIdSchema).max(1_000_000),
11
+ splits: z.array(z.string().trim().min(1).max(100)).max(32),
12
+ failureOwners: z.array(z.string().trim().min(1).max(100)).max(32),
13
+ graders: z.array(z.string().trim().min(1).max(200)).max(1_000),
14
+ rewardEligible: z.boolean().nullable(),
15
+ }).strict();
16
+ export const EvidenceReferenceSchema = z.object({
17
+ id: ReleaseIdSchema,
18
+ contentHash: ReleaseHashSchema,
19
+ kind: z.enum(["rollout", "attempt", "trace", "grader", "checkpoint", "artifact"]),
20
+ visibility: TelemetryVisibilitySchema,
21
+ }).strict();
22
+ export const EvidenceCompletenessSchema = z.object({
23
+ schemaVersion: z.literal("openpond.telemetryEvidenceCompleteness.v1"),
24
+ runId: ReleaseIdSchema,
25
+ status: z.enum(["complete", "partial", "missing"]),
26
+ expectedEventTypes: z.array(z.string().trim().min(1).max(100)).max(100),
27
+ observedEventTypes: z.array(z.string().trim().min(1).max(100)).max(100),
28
+ missingEventTypes: z.array(z.string().trim().min(1).max(100)).max(100),
29
+ lastSequence: z.number().int().nonnegative().nullable(),
30
+ sequenceGaps: z.array(z.number().int().nonnegative()).max(10_000),
31
+ }).strict();
32
+ export const MetricSeriesPointSchema = z.object({
33
+ step: z.number().int().nonnegative().nullable(),
34
+ observedAt: ReleaseTimestampSchema,
35
+ value: z.number().finite(),
36
+ sampleCount: z.number().int().positive(),
37
+ }).strict();
38
+ export const RunMetricSummarySchema = z.object({
39
+ schemaVersion: z.literal("openpond.runMetricSummary.v1"),
40
+ runId: ReleaseIdSchema,
41
+ metricId: ReleaseIdSchema,
42
+ aggregation: z.enum(["last", "sum", "mean", "min", "max", "p50", "p95"]),
43
+ value: z.number().finite().nullable(),
44
+ sampleCount: z.number().int().nonnegative(),
45
+ series: z.array(MetricSeriesPointSchema).max(100_000),
46
+ }).strict();
47
+ export function createRunTelemetryEvent(input) {
48
+ const idHash = contentHash({ runId: input.lineage.runId, sequence: input.sequence, type: input.type, source: input.source });
49
+ return RunTelemetryEventSchema.parse({
50
+ schemaVersion: "openpond.runTelemetryEvent.v1",
51
+ eventId: `telemetry-${idHash.slice(0, 32)}`,
52
+ ...input,
53
+ attributes: input.attributes ?? {},
54
+ });
55
+ }
56
+ export function createMetricObservation(input) {
57
+ const idHash = contentHash({ runId: input.lineage.runId, metricId: input.metricId, sequence: input.sequence });
58
+ return validateCoreMetricObservation(MetricObservationSchema.parse({
59
+ schemaVersion: "openpond.metricObservation.v1",
60
+ observationId: `metric-${idHash.slice(0, 32)}`,
61
+ ...input,
62
+ dimensions: input.dimensions ?? {},
63
+ }));
64
+ }
65
+ export function telemetryIdempotencyKey(item) {
66
+ const id = item.schemaVersion === "openpond.metricObservation.v1" ? item.observationId : item.eventId;
67
+ return `${item.lineage.runId}:${item.sequence}:${id}`;
68
+ }
69
+ export function mergeTelemetryItems(input) {
70
+ const accepted = new Map();
71
+ const sequences = new Map();
72
+ for (const item of [...input.events, ...input.observations]) {
73
+ const key = telemetryIdempotencyKey(item);
74
+ const existing = accepted.get(key);
75
+ if (existing) {
76
+ if (contentHash(existing) !== contentHash(item))
77
+ throw new Error(`Telemetry idempotency conflict: ${key}`);
78
+ continue;
79
+ }
80
+ const sequenceKey = `${item.lineage.runId}:${item.sequence}`;
81
+ const priorKey = sequences.get(sequenceKey);
82
+ if (priorKey && priorKey !== key)
83
+ throw new Error(`Telemetry sequence conflict: ${sequenceKey}`);
84
+ sequences.set(sequenceKey, key);
85
+ accepted.set(key, item);
86
+ }
87
+ const sorted = [...accepted.values()].sort((left, right) => left.sequence - right.sequence);
88
+ return {
89
+ events: sorted.filter((item) => item.schemaVersion === "openpond.runTelemetryEvent.v1"),
90
+ observations: sorted.filter((item) => item.schemaVersion === "openpond.metricObservation.v1"),
91
+ };
92
+ }
93
+ function matchesOptionalSet(values, candidate) {
94
+ return values.length === 0 || (candidate !== null && values.includes(candidate));
95
+ }
96
+ export function filterTelemetryCohort(input) {
97
+ const cohort = TelemetryCohortSchema.parse(input.cohort);
98
+ const lineageMatches = (lineage) => matchesOptionalSet(cohort.checkpointIds, lineage.checkpointId)
99
+ && matchesOptionalSet(cohort.steps, lineage.step)
100
+ && matchesOptionalSet(cohort.scenarioIds, lineage.scenarioId)
101
+ && matchesOptionalSet(cohort.rolloutGroupIds, lineage.rolloutGroupId)
102
+ && matchesOptionalSet(cohort.attemptIds, lineage.attemptId);
103
+ const attributeMatches = (values) => matchesOptionalSet(cohort.splits, typeof values.split === "string" ? values.split : null)
104
+ && matchesOptionalSet(cohort.failureOwners, typeof values.failureOwner === "string" ? values.failureOwner : null)
105
+ && matchesOptionalSet(cohort.graders, typeof values.grader === "string" ? values.grader : null)
106
+ && (cohort.rewardEligible === null || values.rewardEligible === cohort.rewardEligible);
107
+ return {
108
+ events: input.events.filter((event) => lineageMatches(event.lineage) && attributeMatches(event.attributes)),
109
+ observations: input.observations.filter((observation) => lineageMatches(observation.lineage) && attributeMatches(observation.dimensions)),
110
+ };
111
+ }
112
+ export function deriveEvidenceCompleteness(input) {
113
+ const events = input.events.filter((event) => event.lineage.runId === input.runId).sort((left, right) => left.sequence - right.sequence);
114
+ const observed = [...new Set(events.map((event) => event.type))].sort();
115
+ const missing = [...new Set(input.expectedEventTypes)].filter((type) => !observed.includes(type)).sort();
116
+ const sequenceValues = [...new Set(events.map((event) => event.sequence))].sort((left, right) => left - right);
117
+ const lastSequence = events.at(-1)?.sequence ?? null;
118
+ const sequenceGaps = [];
119
+ let expected = 0;
120
+ for (const sequence of sequenceValues) {
121
+ while (expected < sequence && sequenceGaps.length < 10_000)
122
+ sequenceGaps.push(expected++);
123
+ expected = sequence + 1;
124
+ }
125
+ return EvidenceCompletenessSchema.parse({
126
+ schemaVersion: "openpond.telemetryEvidenceCompleteness.v1",
127
+ runId: input.runId,
128
+ status: events.length === 0 ? "missing" : missing.length || sequenceGaps.length ? "partial" : "complete",
129
+ expectedEventTypes: [...new Set(input.expectedEventTypes)].sort(),
130
+ observedEventTypes: observed,
131
+ missingEventTypes: missing,
132
+ lastSequence,
133
+ sequenceGaps,
134
+ });
135
+ }
136
+ function aggregate(values, method) {
137
+ if (!values.length)
138
+ return null;
139
+ if (method === "last")
140
+ return values.at(-1) ?? null;
141
+ if (method === "sum")
142
+ return values.reduce((total, value) => total + value, 0);
143
+ if (method === "mean")
144
+ return values.reduce((total, value) => total + value, 0) / values.length;
145
+ if (method === "min")
146
+ return Math.min(...values);
147
+ if (method === "max")
148
+ return Math.max(...values);
149
+ const sorted = [...values].sort((left, right) => left - right);
150
+ const percentile = method === "p50" ? 0.5 : 0.95;
151
+ return sorted[Math.min(sorted.length - 1, Math.ceil(sorted.length * percentile) - 1)] ?? null;
152
+ }
153
+ export function summarizeMetric(runId, metricId, observations) {
154
+ const definition = getCoreMetricDefinition(metricId);
155
+ if (!definition)
156
+ throw new Error(`Unknown core metric: ${metricId}`);
157
+ const selected = observations.filter((item) => item.lineage.runId === runId && item.metricId === metricId).sort((left, right) => left.sequence - right.sequence);
158
+ const buckets = new Map();
159
+ for (const item of selected) {
160
+ const key = item.lineage.step === null ? `time:${item.observedAt}` : `step:${item.lineage.step}`;
161
+ buckets.set(key, [...(buckets.get(key) ?? []), item]);
162
+ }
163
+ const series = [...buckets.values()].map((items) => ({
164
+ step: items[0]?.lineage.step ?? null,
165
+ observedAt: items[0]?.observedAt,
166
+ value: aggregate(items.map((item) => item.value), definition.aggregation),
167
+ sampleCount: items.length,
168
+ })).filter((point) => point.value !== null && point.observedAt !== undefined);
169
+ return RunMetricSummarySchema.parse({ schemaVersion: "openpond.runMetricSummary.v1", runId, metricId, aggregation: definition.aggregation, value: aggregate(selected.map((item) => item.value), definition.aggregation), sampleCount: selected.length, series });
170
+ }
171
+ export function redactTelemetryEvent(event, maximumVisibility) {
172
+ const rank = { policy_visible: 0, team_visible: 1, host_private: 2 };
173
+ if (rank[event.visibility] > rank[maximumVisibility])
174
+ return null;
175
+ return RunTelemetryEventSchema.parse(event);
176
+ }
177
+ export function redactTelemetryAttributes(event, deniedKeys) {
178
+ const denied = new Set(deniedKeys);
179
+ return RunTelemetryEventSchema.parse({
180
+ ...event,
181
+ attributes: Object.fromEntries(Object.entries(event.attributes).filter(([key]) => !denied.has(key))),
182
+ });
183
+ }
@@ -0,0 +1,60 @@
1
+ import { z } from "zod";
2
+ import { ReleaseHashSchema, ReleaseIdSchema, ReleaseTimestampSchema, contentHash } from "@openpond/harness";
3
+ import { MetricCatalogSchema } from "./telemetry-catalog.js";
4
+ import { deriveEvidenceCompleteness, EvidenceCompletenessSchema, EvidenceReferenceSchema, redactTelemetryAttributes, redactTelemetryEvent } from "./telemetry-analysis.js";
5
+ import { MetricObservationSchema, RunTelemetryEventSchema } from "./telemetry.js";
6
+ export const TelemetryExportBundleContentSchema = z.object({
7
+ schemaVersion: z.literal("openpond.telemetryExportBundle.v1"),
8
+ id: ReleaseIdSchema,
9
+ runId: ReleaseIdSchema,
10
+ exportedAt: ReleaseTimestampSchema,
11
+ definitions: MetricCatalogSchema,
12
+ events: z.array(RunTelemetryEventSchema).max(1_000_000),
13
+ observations: z.array(MetricObservationSchema).max(10_000_000),
14
+ evidenceRefs: z.array(EvidenceReferenceSchema).max(1_000_000),
15
+ completeness: EvidenceCompletenessSchema,
16
+ }).strict();
17
+ export const TelemetryExportBundleSchema = TelemetryExportBundleContentSchema.extend({
18
+ contentHash: ReleaseHashSchema,
19
+ }).strict();
20
+ export function createTelemetryExportBundle(input) {
21
+ const content = TelemetryExportBundleContentSchema.parse(input);
22
+ if (content.completeness.runId !== content.runId) {
23
+ throw new Error("Telemetry export completeness belongs to another Run.");
24
+ }
25
+ if (content.events.some((event) => event.lineage.runId !== content.runId) || content.observations.some((observation) => observation.lineage.runId !== content.runId)) {
26
+ throw new Error("Telemetry export bundle contains evidence from another Run.");
27
+ }
28
+ return TelemetryExportBundleSchema.parse({ ...content, contentHash: contentHash(content) });
29
+ }
30
+ export function verifyTelemetryExportBundle(input) {
31
+ const parsed = TelemetryExportBundleSchema.safeParse(input);
32
+ if (!parsed.success)
33
+ return false;
34
+ const { contentHash: actual, ...content } = parsed.data;
35
+ return contentHash(TelemetryExportBundleContentSchema.parse(content)) === actual;
36
+ }
37
+ export function redactTelemetryExportBundle(input) {
38
+ const bundle = TelemetryExportBundleSchema.parse(input.bundle);
39
+ if (!verifyTelemetryExportBundle(bundle))
40
+ throw new Error("Telemetry export bundle has an invalid content hash.");
41
+ const rank = { policy_visible: 0, team_visible: 1, host_private: 2 };
42
+ const definitions = bundle.definitions.filter((definition) => rank[definition.visibility] <= rank[input.maximumVisibility]);
43
+ const metricIds = new Set(definitions.map((definition) => definition.id));
44
+ const events = bundle.events.flatMap((event) => {
45
+ const visible = redactTelemetryEvent(event, input.maximumVisibility);
46
+ return visible ? [redactTelemetryAttributes(visible, input.deniedAttributeKeys ?? [])] : [];
47
+ });
48
+ const expectedEventTypes = bundle.completeness.expectedEventTypes.filter((type) => events.some((event) => event.type === type));
49
+ return createTelemetryExportBundle({
50
+ schemaVersion: "openpond.telemetryExportBundle.v1",
51
+ id: input.id,
52
+ runId: bundle.runId,
53
+ exportedAt: input.exportedAt,
54
+ definitions,
55
+ events,
56
+ observations: bundle.observations.filter((observation) => metricIds.has(observation.metricId)),
57
+ evidenceRefs: bundle.evidenceRefs.filter((reference) => rank[reference.visibility] <= rank[input.maximumVisibility]),
58
+ completeness: deriveEvidenceCompleteness({ runId: bundle.runId, events, expectedEventTypes: expectedEventTypes }),
59
+ });
60
+ }
@@ -0,0 +1,50 @@
1
+ import { z } from "zod";
2
+ import { MetricDefinitionSchema } from "./telemetry.js";
3
+ const definition = (input) => MetricDefinitionSchema.parse({ schemaVersion: "openpond.metricDefinition.v1", ...input });
4
+ export const CORE_METRIC_CATALOG = [
5
+ definition({ id: "reward.mean", displayName: "Mean reward", description: "Mean composed reward for the cohort.", valueType: "gauge", unit: "scalar", direction: "higher", aggregation: "mean", visibility: "team_visible", boundedDimensions: ["split", "grader"] }),
6
+ definition({ id: "reward.variance", displayName: "Reward variance", description: "Population variance of composed reward within a rollout group.", valueType: "gauge", unit: "scalar", direction: "neutral", aggregation: "mean", visibility: "team_visible", boundedDimensions: ["split"] }),
7
+ definition({ id: "reward.constant_group_rate", displayName: "Constant group rate", description: "Fraction of rollout groups with no reward variation.", valueType: "gauge", unit: "ratio", direction: "lower", aggregation: "mean", visibility: "team_visible", boundedDimensions: ["split"] }),
8
+ definition({ id: "attempt.valid_rate", displayName: "Valid attempt rate", description: "Fraction of attempts passing deterministic validity checks.", valueType: "gauge", unit: "ratio", direction: "higher", aggregation: "mean", visibility: "team_visible", boundedDimensions: ["split", "failureOwner"] }),
9
+ definition({ id: "attempt.failure_count", displayName: "Attempt failures", description: "Count of failed Attempts.", valueType: "counter", unit: "count", direction: "lower", aggregation: "sum", visibility: "team_visible", boundedDimensions: ["split", "failureOwner", "failureClass"] }),
10
+ definition({ id: "optimizer.loss", displayName: "Optimizer loss", description: "Policy optimizer loss after the step.", valueType: "gauge", unit: "scalar", direction: "neutral", aggregation: "mean", visibility: "team_visible", boundedDimensions: ["split"] }),
11
+ definition({ id: "optimizer.learning_rate", displayName: "Learning rate", description: "Optimizer learning rate after the step.", valueType: "gauge", unit: "scalar", direction: "neutral", aggregation: "last", visibility: "team_visible", boundedDimensions: ["split"] }),
12
+ definition({ id: "optimizer.kl", displayName: "KL divergence", description: "Sampled KL divergence from the reference policy.", valueType: "gauge", unit: "scalar", direction: "lower", aggregation: "mean", visibility: "team_visible", boundedDimensions: ["split"] }),
13
+ definition({ id: "optimizer.entropy", displayName: "Policy entropy", description: "Observed policy entropy for trainable tokens.", valueType: "gauge", unit: "scalar", direction: "neutral", aggregation: "mean", visibility: "team_visible", boundedDimensions: ["split"] }),
14
+ definition({ id: "optimizer.gradient_norm", displayName: "Gradient norm", description: "Gradient norm reported by the optimizer.", valueType: "gauge", unit: "scalar", direction: "neutral", aggregation: "max", visibility: "team_visible", boundedDimensions: ["split"] }),
15
+ definition({ id: "optimizer.clip_fraction", displayName: "Clip fraction", description: "Fraction of policy updates affected by clipping.", valueType: "gauge", unit: "ratio", direction: "neutral", aggregation: "mean", visibility: "team_visible", boundedDimensions: ["split"] }),
16
+ definition({ id: "output.duplicate_rate", displayName: "Duplicate output rate", description: "Fraction of outputs duplicated within the measured cohort.", valueType: "gauge", unit: "ratio", direction: "lower", aggregation: "mean", visibility: "team_visible", boundedDimensions: ["split"] }),
17
+ definition({ id: "output.unique_count", displayName: "Unique outputs", description: "Distinct output count in the measured cohort.", valueType: "gauge", unit: "count", direction: "higher", aggregation: "last", visibility: "team_visible", boundedDimensions: ["split"] }),
18
+ definition({ id: "tokens.input", displayName: "Input tokens", description: "Input tokens processed.", valueType: "counter", unit: "tokens", direction: "neutral", aggregation: "sum", visibility: "team_visible", boundedDimensions: ["split", "source"] }),
19
+ definition({ id: "tokens.output", displayName: "Output tokens", description: "Output tokens generated.", valueType: "counter", unit: "tokens", direction: "neutral", aggregation: "sum", visibility: "team_visible", boundedDimensions: ["split", "source"] }),
20
+ definition({ id: "runtime.latency_ms", displayName: "Runtime latency", description: "Wall-clock latency of the measured operation.", valueType: "distribution", unit: "milliseconds", direction: "lower", aggregation: "p95", visibility: "team_visible", boundedDimensions: ["operation", "provider"] }),
21
+ definition({ id: "runtime.throughput", displayName: "Token throughput", description: "Tokens processed per second.", valueType: "gauge", unit: "scalar", direction: "higher", aggregation: "mean", visibility: "team_visible", boundedDimensions: ["operation", "provider"] }),
22
+ definition({ id: "gpu.memory_bytes", displayName: "GPU memory", description: "Peak allocated GPU memory.", valueType: "gauge", unit: "bytes", direction: "neutral", aggregation: "max", visibility: "host_private", boundedDimensions: ["provider", "gpuType"] }),
23
+ definition({ id: "gpu.utilization", displayName: "GPU utilization", description: "Observed GPU utilization ratio.", valueType: "gauge", unit: "ratio", direction: "neutral", aggregation: "mean", visibility: "host_private", boundedDimensions: ["provider", "gpuType"] }),
24
+ definition({ id: "cost.usd", displayName: "Run cost", description: "Accrued hosted execution cost.", valueType: "counter", unit: "usd", direction: "lower", aggregation: "sum", visibility: "team_visible", boundedDimensions: ["provider", "resource"] }),
25
+ ];
26
+ const catalog = new Map(CORE_METRIC_CATALOG.map((item) => [item.id, item]));
27
+ export function getCoreMetricDefinition(metricId) {
28
+ return catalog.get(metricId);
29
+ }
30
+ export function validateCoreMetricObservation(input) {
31
+ return validateMetricObservation(input, CORE_METRIC_CATALOG);
32
+ }
33
+ export function validateMetricObservation(input, definitions) {
34
+ MetricCatalogSchema.parse(definitions);
35
+ const metric = definitions.find((item) => item.id === input.metricId);
36
+ if (!metric)
37
+ throw new Error(`Unknown metric: ${input.metricId}`);
38
+ const unexpected = Object.keys(input.dimensions).filter((dimension) => !metric.boundedDimensions.includes(dimension));
39
+ if (unexpected.length)
40
+ throw new Error(`Metric ${input.metricId} has unsupported dimensions: ${unexpected.join(", ")}`);
41
+ if (metric.unit === "ratio" && (input.value < 0 || input.value > 1)) {
42
+ throw new Error(`Ratio metric ${input.metricId} must be between zero and one.`);
43
+ }
44
+ return input;
45
+ }
46
+ export const MetricCatalogSchema = z.array(MetricDefinitionSchema).min(1).superRefine((items, context) => {
47
+ if (new Set(items.map((item) => item.id)).size !== items.length) {
48
+ context.addIssue({ code: "custom", message: "Metric catalog contains duplicate ids." });
49
+ }
50
+ });
@@ -0,0 +1,112 @@
1
+ import { z } from "zod";
2
+ import { MetadataSchema, ReleaseHashSchema, ReleaseIdSchema, ReleaseTimestampSchema, } from "@openpond/harness";
3
+ export const RUN_TELEMETRY_SCHEMA_VERSION = "openpond.runTelemetryEvent.v1";
4
+ export const METRIC_DEFINITION_SCHEMA_VERSION = "openpond.metricDefinition.v1";
5
+ export const METRIC_OBSERVATION_SCHEMA_VERSION = "openpond.metricObservation.v1";
6
+ export const TelemetryVisibilitySchema = z.enum([
7
+ "policy_visible",
8
+ "team_visible",
9
+ "host_private",
10
+ ]);
11
+ export const TelemetrySourceSchema = z.enum([
12
+ "runtime",
13
+ "environment",
14
+ "grader",
15
+ "optimizer",
16
+ "control_plane",
17
+ "evaluation",
18
+ ]);
19
+ export const TelemetryEventTypeSchema = z.enum([
20
+ "run_started",
21
+ "run_state_changed",
22
+ "rollout_group_started",
23
+ "attempt_completed",
24
+ "grader_completed",
25
+ "reward_composed",
26
+ "optimizer_step_completed",
27
+ "checkpoint_committed",
28
+ "evaluation_completed",
29
+ "run_completed",
30
+ "run_failed",
31
+ "cleanup_completed",
32
+ ]);
33
+ export const RunTelemetryLineageSchema = z.object({
34
+ modelProjectId: ReleaseIdSchema,
35
+ runId: ReleaseIdSchema,
36
+ modelVersionId: ReleaseIdSchema.nullable(),
37
+ harnessReleaseHash: ReleaseHashSchema,
38
+ tasksetReleaseHash: ReleaseHashSchema,
39
+ environmentReleaseHash: ReleaseHashSchema.nullable(),
40
+ checkpointId: ReleaseIdSchema.nullable(),
41
+ step: z.number().int().nonnegative().nullable(),
42
+ rolloutGroupId: ReleaseIdSchema.nullable(),
43
+ attemptId: ReleaseIdSchema.nullable(),
44
+ scenarioId: ReleaseIdSchema.nullable(),
45
+ }).strict();
46
+ export const RunTelemetryEventSchema = z.object({
47
+ schemaVersion: z.literal(RUN_TELEMETRY_SCHEMA_VERSION),
48
+ eventId: ReleaseIdSchema,
49
+ sequence: z.number().int().nonnegative(),
50
+ occurredAt: ReleaseTimestampSchema,
51
+ source: TelemetrySourceSchema,
52
+ type: TelemetryEventTypeSchema,
53
+ visibility: TelemetryVisibilitySchema,
54
+ lineage: RunTelemetryLineageSchema,
55
+ attributes: MetadataSchema,
56
+ }).strict();
57
+ export const MetricValueTypeSchema = z.enum(["gauge", "counter", "distribution"]);
58
+ export const MetricUnitSchema = z.enum([
59
+ "ratio",
60
+ "count",
61
+ "seconds",
62
+ "milliseconds",
63
+ "tokens",
64
+ "bytes",
65
+ "usd",
66
+ "scalar",
67
+ ]);
68
+ export const MetricDirectionSchema = z.enum(["higher", "lower", "neutral"]);
69
+ export const MetricAggregationSchema = z.enum(["last", "sum", "mean", "min", "max", "p50", "p95"]);
70
+ export const MetricDefinitionSchema = z.object({
71
+ schemaVersion: z.literal(METRIC_DEFINITION_SCHEMA_VERSION),
72
+ id: ReleaseIdSchema,
73
+ displayName: z.string().trim().min(1).max(200),
74
+ description: z.string().trim().min(1).max(2_000),
75
+ valueType: MetricValueTypeSchema,
76
+ unit: MetricUnitSchema,
77
+ direction: MetricDirectionSchema,
78
+ aggregation: MetricAggregationSchema,
79
+ visibility: TelemetryVisibilitySchema,
80
+ boundedDimensions: z.array(z.string().trim().min(1).max(100)).max(32),
81
+ }).strict();
82
+ export const MetricObservationSchema = z.object({
83
+ schemaVersion: z.literal(METRIC_OBSERVATION_SCHEMA_VERSION),
84
+ observationId: ReleaseIdSchema,
85
+ metricId: ReleaseIdSchema,
86
+ eventId: ReleaseIdSchema,
87
+ sequence: z.number().int().nonnegative(),
88
+ observedAt: ReleaseTimestampSchema,
89
+ value: z.number().finite(),
90
+ lineage: RunTelemetryLineageSchema,
91
+ dimensions: z.record(z.string().trim().min(1).max(100), z.string().max(500)),
92
+ }).strict();
93
+ export const RunTelemetryBatchSchema = z.object({
94
+ schemaVersion: z.literal("openpond.runTelemetryBatch.v1"),
95
+ events: z.array(RunTelemetryEventSchema).max(1_000),
96
+ observations: z.array(MetricObservationSchema).max(10_000),
97
+ }).strict().superRefine((batch, context) => {
98
+ if (batch.events.length + batch.observations.length === 0) {
99
+ context.addIssue({ code: "custom", message: "Telemetry batch cannot be empty." });
100
+ }
101
+ const keys = new Set();
102
+ for (const item of [...batch.events, ...batch.observations]) {
103
+ const id = item.schemaVersion === METRIC_OBSERVATION_SCHEMA_VERSION
104
+ ? item.observationId
105
+ : item.eventId;
106
+ const key = `${item.lineage.runId}:${item.sequence}:${id}`;
107
+ if (keys.has(key)) {
108
+ context.addIssue({ code: "custom", message: "Telemetry batch contains a duplicate idempotency key." });
109
+ }
110
+ keys.add(key);
111
+ }
112
+ });
@@ -6,10 +6,15 @@ export * from "./execution-contracts.js";
6
6
  export * from "./execution-receipts.js";
7
7
  export * from "./graders.js";
8
8
  export * from "./harness.js";
9
+ export * from "./learned-preference.js";
9
10
  export * from "./runs.js";
10
11
  export * from "./model-improvement-qualification.js";
11
12
  export * from "./preferences.js";
12
13
  export * from "./review-conformance.js";
13
14
  export * from "./rollouts.js";
15
+ export * from "./telemetry.js";
16
+ export * from "./telemetry-catalog.js";
17
+ export * from "./telemetry-analysis.js";
18
+ export * from "./telemetry-bundle.js";
14
19
  export * from "./tasksets.js";
15
20
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,sCAAsC,CAAC;AACrD,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC;AACxC,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,yBAAyB,CAAC;AACxC,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,yBAAyB,CAAC;AACxC,cAAc,WAAW,CAAC;AAC1B,cAAc,sCAAsC,CAAC;AACrD,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC;AACxC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,uBAAuB,CAAC;AACtC,cAAc,eAAe,CAAC"}