- 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
68 lines
1.6 KiB
Python
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
|