Handoff: Enriched Shadow Mode — CalendarValidator Implementation
What: Implement CalendarValidator class that queries Google Calendar (read-only) on Tier 2 tripwire fire, fuzzy-matches extracted events against calendar events, and reports MATCH | NO_MATCH | CONFLICT status.
Why: Service account authenticated, calendar read-only verified, test event confirmed. Ready for calendar validation logic.
Files:
- /home/hoffmann_admin/.openclaw/workspace/services/icarus/docs/ENRICHED-SHADOW-SPEC.md — Full architecture spec
- /home/hoffmann_admin/.openclaw/workspace/services/icarus/core/observer/shadow_bot.py — Main observer (integrate CalendarValidator)
- /home/hoffmann_admin/.openclaw/workspace/services/icarus/core/observer/shadow_database.py — Add calendar validation columns
- /home/hoffmann_admin/.openclaw/workspace/services/icarus/core/observer/shadow_telegram.py — Add Admin DM exhaust method
Current State:
- Service account: family-calendar-sync@hoffmann-family-manager.iam.gserviceaccount.com
- Calendar ID: hoffmann.family.manager@gmail.com (primary, shared with service account)
- Scope: calendar.readonly (verified — write returns 403)
- Test query: ✅ Working, returns events
- Command: gog calendar events hoffmann.family.manager@gmail.com --from <ISO> --to <ISO> --account family-calendar-sync@hoffmann-family-manager.iam.gserviceaccount.com --json
What You Need to Implement:
1. CalendarValidator Class
# services/icarus/core/observer/calendar_validator.py
class CalendarValidator:
"""Read-only calendar validation for extracted events."""
def __init__(self, calendar_id: str, account: str):
self.calendar_id = calendar_id
self.account = account
def check_event(self, extracted_event: dict) -> CalendarCheckResult:
"""
Query calendar for events matching extracted_event.
Returns:
MATCH: Event exists with similar title/time
NO_MATCH: No similar event found
CONFLICT: Event exists but details differ
"""
def fuzzy_match_score(self, extracted_title: str, calendar_title: str) -> float:
"""Fuzzy string matching (0.0-1.0)"""
def query_calendar(self, date_start: str, date_end: str) -> list:
"""Execute gog calendar events query"""
2. Integration Points
In shadow_bot.py after Tier 2 extraction:
# After LLM extraction
if extraction_result and extraction_result.confidence >= 0.7:
# [NEW] Calendar validation
calendar_check = self.calendar_validator.check_event(
extraction_result.raw_extraction
)
if calendar_check.status == "MATCH":
# Event exists — log silently, no DM needed
self.db.update_calendar_check(
shadow_msg_id=shadow_msg_id,
status="match",
matched_event_id=calendar_check.event_id,
matched_event_title=calendar_check.event_title,
)
elif calendar_check.status == "NO_MATCH":
# Event not found — queue Admin DM
self._send_admin_dm_no_match(
shadow_msg_id=shadow_msg_id,
extracted_event=extraction_result.raw_extraction,
)
elif calendar_check.status == "CONFLICT":
# Event exists but details differ — flag conflict
self._send_admin_dm_conflict(
shadow_msg_id=shadow_msg_id,
extracted_event=extraction_result.raw_extraction,
calendar_event=calendar_check.matched_event,
)
3. Fuzzy Matching Logic
# Use difflib.SequenceMatcher or rapidfuzz for fuzzy matching
# Score thresholds:
# - ≥ 0.7: Strong match (same event)
# - 0.4-0.7: Possible match (check time/location)
# - < 0.4: No match
def fuzzy_match_score(self, extracted: str, calendar_title: str) -> float:
from difflib import SequenceMatcher
return SequenceMatcher(None, extracted.lower(), calendar_title.lower()).ratio()
4. Admin DM Exhaust Methods
# In shadow_telegram.py or shadow_bot.py
def _send_admin_dm_no_match(self, shadow_msg_id, extracted_event):
"""DM Matt about proposed calendar action."""
message = f"""
🔍 Shadow Mode — Proposed Calendar Action
Message ID: {shadow_msg_id}
Extracted: {extracted_event['what']}
When: {extracted_event['when']}
Who: {', '.join(extracted_event['who'])}
Calendar: ❌ No matching event found
I would have added:
📅 "{extracted_event['what']}" — {extracted_event['when']}
Accurate? [Yes] [No] [Edit]
⚠️ This is a simulation. Calendar not modified.
"""
self._send_dm_to_admin(message)
def _send_admin_dm_conflict(self, shadow_msg_id, extracted_event, calendar_event):
"""DM Matt about calendar conflict."""
message = f"""
🔍 Shadow Mode — Calendar Conflict Detected
Message ID: {shadow_msg_id}
Extracted: {extracted_event['what']} at {extracted_event['when']}
Calendar: 📅 "{calendar_event['summary']}" at {calendar_event['start']}
Conflict: {calendar_event['conflict_description']}
Which is correct?
[📅 Keep Calendar]
[💬 Use Message]
[❓ I'm Not Sure]
⚠️ This is a simulation. Calendar not modified.
"""
self._send_dm_to_admin(message)
5. Database Schema Update
-- Add to shadow_extractions table
ALTER TABLE shadow_extractions ADD COLUMN calendar_check_status TEXT; -- match | no_match | conflict
ALTER TABLE shadow_extractions ADD COLUMN calendar_event_id TEXT;
ALTER TABLE shadow_extractions ADD COLUMN calendar_event_title TEXT;
ALTER TABLE shadow_extractions ADD COLUMN calendar_event_time TEXT;
ALTER TABLE shadow_extractions ADD COLUMN fuzzy_match_score REAL;
ALTER TABLE shadow_extractions ADD COLUMN admin_dm_sent BOOLEAN DEFAULT FALSE;
ALTER TABLE shadow_extractions ADD COLUMN admin_response TEXT; -- yes | no | edit | timeout
Scope:
- ✅ DO: CalendarValidator class with fuzzy matching
- ✅ DO: gog command execution for calendar queries
- ✅ DO: Integration into shadow_bot.py flow
- ✅ DO: Admin DM exhaust methods
- ✅ DO: Database schema updates
- ❌ DON'T: Write to calendar (read-only only)
- ❌ DON'T: Modify tripwire logic (that's already tuned)
- ❌ DON'T: Add frontend UI (Daedalus handles that)
Success Criteria:
- CalendarValidator queries calendar on tripwire fire
- Fuzzy matching returns score 0.0-1.0
- Status correctly identified: MATCH | NO_MATCH | CONFLICT
- Admin DM format matches spec
- Database stores calendar validation results
- No calendar write operations (verified by 403 test)
Dependencies:
- gog CLI: Already configured with service account
- difflib: Python standard library (no install needed)
Calendar Query Command:
gog calendar events hoffmann.family.manager@gmail.com \
--from <start_iso> \
--to <end_iso> \
--account family-calendar-sync@hoffmann-family-manager.iam.gserviceaccount.com \
--json
ETA: TBD — provide your own after review
Coordination:
- After implementation, Wadsworth will test with real messages
- Admin DM routing will be configured
- Integration tests against test event "Test" on May 3