state-hub/api/schemas/inbox_feed.py
tegwick 4ffc723673
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Build and Publish Multi-Context Image / build-and-push (push) Successful in 38s
Inbox change feed v1 (STATE-WP-0098-T05); close out open waits
- migration c4d5e6f7a8b9: inbox revision sequence, per-transaction revision
  trigger with advisory lock, message tombstones
- GET /messages/feed/snapshot and /messages/feed?after= per the accepted
  contract; archived_at is now first-write-wins
- STATE-WP-0098 blocked: T02/T04 qualified waits on owners and HUB-WP-0011-T01
- STATE-WP-0094-T05: correct the cited CCR, wait on railiance-platform
- legacy-meter review evidence 2026-10-14

Not deployed; the migration deploy is a separate confirmed step.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 356991@bnt-lap001
Assistant-Session: 5d99ea69-ed55-4a1e-8d97-f4f312db247f
2026-10-14 02:48:06 +02:00

68 lines
1.6 KiB
Python

"""Inbox change-feed envelopes (docs/inbox-change-feed-contract.md, v1)."""
from __future__ import annotations
import re
from datetime import datetime
from typing import Any, Literal
from pydantic import BaseModel
CONTRACT = "statehub.inbox-feed.v1"
CURSOR_VERSION = "v1"
_CURSOR_RE = re.compile(r"^(v\d+):(\d+)$")
class CursorError(ValueError):
"""Malformed cursor (400)."""
class ForeignCursorError(ValueError):
"""Cursor of another contract version (409)."""
def encode_cursor(revision: int) -> str:
return f"{CURSOR_VERSION}:{int(revision)}"
def decode_cursor(cursor: str) -> int:
match = _CURSOR_RE.match(cursor or "")
if match is None:
raise CursorError(f"malformed cursor {cursor!r}")
if match.group(1) != CURSOR_VERSION:
raise ForeignCursorError(
f"cursor {cursor!r} belongs to another contract version; re-snapshot"
)
return int(match.group(2))
class FeedChange(BaseModel):
revision: int
entity: Literal["message", "receipt", "tombstone"]
key: str
op: Literal["upsert", "delete"]
state: dict[str, Any]
class FeedPage(BaseModel):
contract: str = CONTRACT
source_instance: str | None
source_time: datetime
high_water: str
next_cursor: str
has_more: bool
compacted_before: str
changes: list[FeedChange]
class FeedSnapshot(BaseModel):
contract: str = CONTRACT
source_instance: str | None
source_time: datetime
cursor: str
compacted_before: str
messages: list[dict[str, Any]]
receipts: list[dict[str, Any]]
tombstones: list[dict[str, Any]]
count: int
content_hash: str