Add two new shared models to models.py that all subsequent Phase 2 components depend on: the PollStrategy enum and the MessageMetadata Pydantic model.
Requirements
Add PollStrategy(str, Enum) with values PUSH = "push", PULL = "pull", WEBHOOK = "webhook" matching the SQLite schema CHECK constraint: poll_strategy IN ('push', 'pull', 'webhook')
Add MessageMetadata(BaseModel) as a frozen Pydantic model with fields: thread_id: str, message_id: str, sender: str, recipients: list[str], timestamp: str, in_reply_to: str | None, subject: str | None, is_thread_root: bool
MessageMetadata must use model_config = ConfigDict(frozen=True) consistent with all existing models
Add @field_validator("timestamp") on MessageMetadata using the existing validate_iso8601_timestamp function already present in models.py
Both additions must be importable from src/context_library/storage/models.py
Design Guidance
File to modify: src/context_library/storage/models.py
Existing patterns to follow:
All enums in this file use (str, Enum) pattern (see Domain and ChunkType)
All BaseModels use model_config = ConfigDict(frozen=True)
Validators use @field_validator with @classmethod
The existing validate_iso8601_timestamp utility is already defined in models.py — reuse it for MessageMetadata.timestamp
PollStrategy enum:
class PollStrategy (str , Enum ):
PUSH = "push"
PULL = "pull"
WEBHOOK = "webhook"
Values must match exactly the SQLite schema CHECK constraint on sources.poll_strategy.
MessageMetadata model:
class MessageMetadata (BaseModel ):
model_config = ConfigDict (frozen = True )
thread_id : str
message_id : str
sender : str
recipients : list [str ]
timestamp : str # ISO 8601
in_reply_to : str | None
subject : str | None
is_thread_root : bool
@field_validator ("timestamp" )
@classmethod
def validate_timestamp (cls , v : str ) -> str :
return validate_iso8601_timestamp (v )
Where MessageMetadata is used downstream:
EmailAdapter populates it and places it in NormalizedContent supplementary metadata
MessagesDomain reads it from content and serializes via .model_dump() into Chunk.domain_metadata
Chunk.domain_metadata is typed as dict[str, object] | None — so MessageMetadata is always serialized before assignment
Acceptance Criteria
PollStrategy.PUSH, PollStrategy.PULL, PollStrategy.WEBHOOK enum values equal "push", "pull", "webhook" respectively
PollStrategy is a str subclass (so values are directly usable in SQL queries)
MessageMetadata(thread_id="t1", message_id="m1", sender="a@b.com", recipients=["c@d.com"], timestamp="2024-01-01T00:00:00Z", in_reply_to=None, subject=None, is_thread_root=True) instantiates without error
MessageMetadata raises ValidationError when timestamp is not ISO 8601
MessageMetadata instance raises ValidationError on mutation attempt (frozen)
MessageMetadata(...).model_dump() returns a JSON-serializable dict
Both PollStrategy and MessageMetadata are importable from context_library.storage.models
All existing model tests continue to pass
Parent Issue
Part of #83
Discussion
This work is detailed in discussion 84
Add two new shared models to models.py that all subsequent Phase 2 components depend on: the PollStrategy enum and the MessageMetadata Pydantic model.
Requirements
PollStrategy(str, Enum)with valuesPUSH = "push",PULL = "pull",WEBHOOK = "webhook"matching the SQLite schema CHECK constraint:poll_strategy IN ('push', 'pull', 'webhook')MessageMetadata(BaseModel)as a frozen Pydantic model with fields:thread_id: str,message_id: str,sender: str,recipients: list[str],timestamp: str,in_reply_to: str | None,subject: str | None,is_thread_root: boolMessageMetadatamust usemodel_config = ConfigDict(frozen=True)consistent with all existing models@field_validator("timestamp")onMessageMetadatausing the existingvalidate_iso8601_timestampfunction already present in models.pysrc/context_library/storage/models.pyDesign Guidance
File to modify:
src/context_library/storage/models.pyExisting patterns to follow:
(str, Enum)pattern (seeDomainandChunkType)model_config = ConfigDict(frozen=True)@field_validatorwith@classmethodvalidate_iso8601_timestamputility is already defined in models.py — reuse it forMessageMetadata.timestampPollStrategy enum:
Values must match exactly the SQLite schema CHECK constraint on
sources.poll_strategy.MessageMetadata model:
Where MessageMetadata is used downstream:
EmailAdapterpopulates it and places it inNormalizedContentsupplementary metadataMessagesDomainreads it from content and serializes via.model_dump()intoChunk.domain_metadataChunk.domain_metadatais typed asdict[str, object] | None— so MessageMetadata is always serialized before assignmentAcceptance Criteria
PollStrategy.PUSH,PollStrategy.PULL,PollStrategy.WEBHOOKenum values equal"push","pull","webhook"respectivelyPollStrategyis astrsubclass (so values are directly usable in SQL queries)MessageMetadata(thread_id="t1", message_id="m1", sender="a@b.com", recipients=["c@d.com"], timestamp="2024-01-01T00:00:00Z", in_reply_to=None, subject=None, is_thread_root=True)instantiates without errorMessageMetadataraisesValidationErrorwhentimestampis not ISO 8601MessageMetadatainstance raisesValidationErroron mutation attempt (frozen)MessageMetadata(...).model_dump()returns a JSON-serializable dictPollStrategyandMessageMetadataare importable fromcontext_library.storage.modelsParent Issue
Part of #83
Discussion
This work is detailed in discussion 84