feat(tag-vocab): telemetryEvents — tag_vocab_hit/miss zod schemas (#3 v0.2.3)

- TagVocabHitPayload { tagId: int>0, vocabSize: int>=0 } .strict()
- TagVocabMissPayload { vocabSize: int>=0 } .strict()
- TelemetryEventSchema union 13 → 15
- 단위 +3 cases (hit accept, miss accept, hit extra field 거부)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
altair823
2026-05-02 12:23:31 +09:00
parent daa8507364
commit b81fc82621
2 changed files with 40 additions and 1 deletions

View File

@@ -50,6 +50,15 @@ 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();
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(),
@@ -63,7 +72,9 @@ export const TelemetryEventSchema = z.discriminatedUnion('kind', [
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('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()
]);
export type TelemetryEvent = z.infer<typeof TelemetryEventSchema>;

View File

@@ -278,3 +278,31 @@ describe('ai_retry_manual event', () => {
})).toThrow();
});
});
describe('validateEvent — tag vocab', () => {
it('accepts tag_vocab_hit event', () => {
const e = validateEvent({
ts: '2026-05-02T00:00:00.000Z',
kind: 'tag_vocab_hit',
payload: { tagId: 42, vocabSize: 17 }
});
expect(e.kind).toBe('tag_vocab_hit');
});
it('accepts tag_vocab_miss event without tagId', () => {
const e = validateEvent({
ts: '2026-05-02T00:00:00.000Z',
kind: 'tag_vocab_miss',
payload: { vocabSize: 17 }
});
expect(e.kind).toBe('tag_vocab_miss');
});
it('rejects tag_vocab_hit with extra field (privacy invariant)', () => {
expect(() => validateEvent({
ts: '2026-05-02T00:00:00.000Z',
kind: 'tag_vocab_hit',
payload: { tagId: 42, vocabSize: 17, tagName: 'leak' }
})).toThrow();
});
});