feat(schema): protobuf event registry + buf CI gate (#54)

- Add proto schemas in packages/shared-types/events/ (oo.events.v1):
  envelope.proto, signals.proto, integration.proto
- buf.yaml with STANDARD lint + FILE breaking-change rules
- .gitea/workflows/buf-check.yaml: lint + breaking check on every PR
  touching events/ (needs a Gitea Actions runner to execute)
- scripts/buf-check.sh: local equivalent of the CI check
- NormalizedEvent TS envelope gains eventId, schemaVersion, producer
  to align with the proto Envelope message
- ml/serving/schemas.py: pydantic models mirroring the v1 proto types
- nats_consumer.py: validate payloads via pydantic instead of raw .get()

A field-rename PR will now fail buf breaking with exit code 100 and
show the offending messages. To make a breaking change: keep the old
field reserved, add the new one, bump schema_version to v2.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-25 16:48:24 +00:00
parent f48b5a7646
commit d539fde0c1
10 changed files with 213 additions and 13 deletions

50
ml/serving/schemas.py Normal file
View File

@@ -0,0 +1,50 @@
"""
Pydantic models mirroring oo.events.v1 proto schemas.
Field names use camelCase to match the proto3 JSON mapping convention
and the TypeScript payload shapes published by services/api.
Keep in sync with packages/shared-types/events/oo/events/v1/.
"""
from __future__ import annotations
from typing import Literal, Optional
from pydantic import BaseModel
class TaskSyncedPayload(BaseModel):
userId: str
source: str
count: int
syncedAt: str
class TipServedPayload(BaseModel):
userId: str
tipId: str
policy: str
servedAt: str
class TipFeedbackPayload(BaseModel):
userId: str
tipId: str
action: Literal['done', 'dismiss', 'snooze', 'helpful', 'not_helpful']
reward: float
dwellMs: Optional[int] = None
createdAt: str
class TipRewardFailedPayload(BaseModel):
userId: str
tipId: str
reward: float
attempts: int
error: str
failedAt: str
class IntegrationTokenExpiredPayload(BaseModel):
userId: str
provider: str
detectedAt: str