- RecallShownPayload { noteId, ageDays: int>=0 } .strict()
- recall_opened/dismissed/snoozed → NoteIdPayload 재사용
- TelemetryEventSchema union 15 → 19
- 단위 +3 cases (recall_shown valid, extra field 거부, opened/dismissed/snoozed valid)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
95 lines
3.8 KiB
TypeScript
95 lines
3.8 KiB
TypeScript
import { z } from 'zod';
|
|
|
|
const CapturePayload = z.object({
|
|
noteId: z.string().min(1),
|
|
rawTextLength: z.number().int().nonnegative(),
|
|
hasMedia: z.boolean()
|
|
}).strict();
|
|
|
|
const AiSucceededPayload = z.object({
|
|
noteId: z.string().min(1),
|
|
durationMs: z.number().nonnegative(),
|
|
attempts: z.number().int().nonnegative()
|
|
}).strict();
|
|
|
|
const AiFailedReason = z.enum(['unreachable', 'schema', 'timeout', 'other']);
|
|
|
|
const AiFailedPayload = z.object({
|
|
noteId: z.string().min(1),
|
|
reason: AiFailedReason,
|
|
attempts: z.number().int().nonnegative()
|
|
}).strict();
|
|
|
|
const NoteIdPayload = z.object({
|
|
noteId: z.string().min(1)
|
|
}).strict();
|
|
|
|
const EmptyTrashPayload = z.object({
|
|
count: z.number().int().nonnegative()
|
|
}).strict();
|
|
|
|
const ExpiredBannerShownPayload = z.object({
|
|
candidateCount: z.number().int().nonnegative()
|
|
}).strict();
|
|
|
|
const ExpiredBatchTrashPayload = z.object({
|
|
count: z.number().int().nonnegative()
|
|
}).strict();
|
|
|
|
const OllamaUnreachablePayload = z.object({
|
|
reason: z.string().min(1).max(500)
|
|
}).strict();
|
|
|
|
const OllamaRecoveredPayload = z.object({
|
|
downtimeMs: z.number().nonnegative()
|
|
}).strict();
|
|
|
|
const EmptyPayload = z.object({}).strict();
|
|
|
|
const AiRetryManualPayload = z.object({
|
|
failedCount: z.number().int().positive()
|
|
}).strict();
|
|
|
|
const TagVocabHitPayload = z.object({
|
|
tagId: z.number().int().positive(),
|
|
vocabSize: z.number().int().nonnegative()
|
|
}).strict();
|
|
|
|
const TagVocabMissPayload = z.object({
|
|
vocabSize: z.number().int().nonnegative()
|
|
}).strict();
|
|
|
|
const RecallShownPayload = z.object({
|
|
noteId: z.string().min(1),
|
|
ageDays: z.number().int().nonnegative()
|
|
}).strict();
|
|
|
|
export const TelemetryEventSchema = z.discriminatedUnion('kind', [
|
|
z.object({ ts: z.string(), kind: z.literal('capture'), payload: CapturePayload }).strict(),
|
|
z.object({ ts: z.string(), kind: z.literal('ai_succeeded'), payload: AiSucceededPayload }).strict(),
|
|
z.object({ ts: z.string(), kind: z.literal('ai_failed'), payload: AiFailedPayload }).strict(),
|
|
z.object({ ts: z.string(), kind: z.literal('trash'), payload: NoteIdPayload }).strict(),
|
|
z.object({ ts: z.string(), kind: z.literal('restore'), payload: NoteIdPayload }).strict(),
|
|
z.object({ ts: z.string(), kind: z.literal('permanent_delete'), payload: NoteIdPayload }).strict(),
|
|
z.object({ ts: z.string(), kind: z.literal('empty_trash'), payload: EmptyTrashPayload }).strict(),
|
|
z.object({ ts: z.string(), kind: z.literal('expired_banner_shown'), payload: ExpiredBannerShownPayload }).strict(),
|
|
z.object({ ts: z.string(), kind: z.literal('expired_batch_trash'), payload: ExpiredBatchTrashPayload }).strict(),
|
|
z.object({ ts: z.string(), kind: z.literal('ollama_unreachable'), payload: OllamaUnreachablePayload }).strict(),
|
|
z.object({ ts: z.string(), kind: z.literal('ollama_recovered'), payload: OllamaRecoveredPayload }).strict(),
|
|
z.object({ ts: z.string(), kind: z.literal('ollama_recheck_manual'), payload: EmptyPayload }).strict(),
|
|
z.object({ ts: z.string(), kind: z.literal('ai_retry_manual'), payload: AiRetryManualPayload }).strict(),
|
|
z.object({ ts: z.string(), kind: z.literal('tag_vocab_hit'), payload: TagVocabHitPayload }).strict(),
|
|
z.object({ ts: z.string(), kind: z.literal('tag_vocab_miss'), payload: TagVocabMissPayload }).strict(),
|
|
z.object({ ts: z.string(), kind: z.literal('recall_shown'), payload: RecallShownPayload }).strict(),
|
|
z.object({ ts: z.string(), kind: z.literal('recall_opened'), payload: NoteIdPayload }).strict(),
|
|
z.object({ ts: z.string(), kind: z.literal('recall_dismissed'), payload: NoteIdPayload }).strict(),
|
|
z.object({ ts: z.string(), kind: z.literal('recall_snoozed'), payload: NoteIdPayload }).strict()
|
|
]);
|
|
|
|
export type TelemetryEvent = z.infer<typeof TelemetryEventSchema>;
|
|
export type TelemetryKind = TelemetryEvent['kind'];
|
|
|
|
export function validateEvent(raw: unknown): TelemetryEvent {
|
|
return TelemetryEventSchema.parse(raw);
|
|
}
|