Stream A — Foundation
Labels: core, foundation
Depends on: Nothing
Effort: 4–5 hours
Summary
The single most critical file. Every other component depends on it. Defines the universal message format all channels normalize to AND the structured log event format that gets exported to GiveLight. Two contracts in one file.
What to Build
File: app/core/envelope.py
CanonicalMessage fields: session_id (SHA-256 hash, never raw phone), channel, input_type (text/image/audio/document/location), text_content, media_url (temporary, never persisted), language_hint (ISO 639-1), location_context (country+region only, never coordinates), session_context, submission_type (intake | verification_evidence | monitoring_checkin | unknown), prior_context (in-memory only), timestamp.
LocationContext: country_code (ISO 3166-1), region, city (best-effort), source (device | prompt | phone_prefix | none), confidence (0.0–1.0). Coordinates are NEVER stored in this dataclass — the fields do not exist.
LogEvent: event_type (enum), session_id_hash, timestamp, cleartext_payload (dict), pii_envelope (bytes | None — populated by the encryption layer in GL-20), agent_version, platform_version, schema_version. LogEvent.emit() appends to the in-memory log buffer on the Session object.
make_session_id(channel_user_id, channel) -> str # SHA-256, rotates daily
Privacy Contract
make_session_id() uses SHA-256. Same user same day = same hash. Next day = different hash.
- Coordinates never stored in any field. LocationContext has no lat/lng fields.
to_agent_prompt() never includes session_id, phone, channel name, or coordinates.
LogEvent.cleartext_payload never contains raw phone, name, or coordinates.
LogEvent.pii_envelope is opaque bytes — set by encryption layer, never inspected here.
Acceptance Criteria
- All dataclasses with type hints and docstrings.
make_session_id() is SHA-256, consistent within a day, different next day.
submission_type enum covers all 4 values, defaults to unknown.
LogEvent.emit() appends to session-scoped in-memory buffer.
- Unit tests: hash consistency, daily rotation, prompt variants, log event emission.
Stream A — Foundation
Labels: core, foundation
Depends on: Nothing
Effort: 4–5 hours
Summary
The single most critical file. Every other component depends on it. Defines the universal message format all channels normalize to AND the structured log event format that gets exported to GiveLight. Two contracts in one file.
What to Build
File:
app/core/envelope.pyCanonicalMessage fields:
session_id(SHA-256 hash, never raw phone),channel,input_type(text/image/audio/document/location),text_content,media_url(temporary, never persisted),language_hint(ISO 639-1),location_context(country+region only, never coordinates),session_context,submission_type(intake | verification_evidence | monitoring_checkin | unknown),prior_context(in-memory only),timestamp.LocationContext:
country_code(ISO 3166-1),region,city(best-effort),source(device | prompt | phone_prefix | none),confidence(0.0–1.0). Coordinates are NEVER stored in this dataclass — the fields do not exist.LogEvent:
event_type(enum),session_id_hash,timestamp,cleartext_payload(dict),pii_envelope(bytes | None — populated by the encryption layer in GL-20),agent_version,platform_version,schema_version.LogEvent.emit()appends to the in-memory log buffer on the Session object.Privacy Contract
make_session_id()uses SHA-256. Same user same day = same hash. Next day = different hash.to_agent_prompt()never includes session_id, phone, channel name, or coordinates.LogEvent.cleartext_payloadnever contains raw phone, name, or coordinates.LogEvent.pii_envelopeis opaque bytes — set by encryption layer, never inspected here.Acceptance Criteria
make_session_id()is SHA-256, consistent within a day, different next day.submission_typeenum covers all 4 values, defaults to unknown.LogEvent.emit()appends to session-scoped in-memory buffer.