"""FORGED CHECKER for calendar/meeting_series (checker forge, claude-sonnet-5). Gate: references accepted, every spoiler rejected and file-changing, empty file rejected, on 2 given specs plus 6 generated by make_specs.""" # ---- reference builder import datetime as _dt # NOT "datetime": line 49 rebinds that name to the class from icalendar import Calendar, Event, Alarm def build_reference(spec: dict, out_path: str) -> None: cal = Calendar() cal.add('prodid', spec['prodid']) cal.add('version', '2.0') event = Event() event.add('uid', spec['uid']) event.add('summary', spec['summary']) event.add('location', spec['location']) dtstart = _dt.datetime.strptime( spec['start'], '%Y%m%dT%H%M%SZ' ).replace(tzinfo=_dt.timezone.utc) dtend = _dt.datetime.strptime( spec['end'], '%Y%m%dT%H%M%SZ' ).replace(tzinfo=_dt.timezone.utc) event.add('dtstart', dtstart) event.add('dtend', dtend) event.add('rrule', { 'freq': 'WEEKLY', 'byday': list(spec['byday']), 'count': int(spec['count']), }) alarm = Alarm() alarm.add('action', 'DISPLAY') alarm.add('description', spec['summary']) trigger = _dt.timedelta(minutes=-int(spec['alarm_minutes'])) alarm.add('trigger', trigger) event.add_component(alarm) cal.add_component(event) with open(out_path, 'wb') as f: f.write(cal.to_ical()) # ---- checker import icalendar from datetime import datetime, timedelta, timezone def grade(spec: dict, path: str) -> tuple: try: with open(path, 'rb') as f: raw = f.read() except Exception as e: return 0, f"Could not open file: {e}" # CRLF check if b'\r\n' not in raw: return 0, "File does not use CRLF line endings" stripped = raw.replace(b'\r\n', b'') if b'\n' in stripped: return 0, "File contains bare LF line endings (not CRLF)" try: cal = icalendar.Calendar.from_ical(raw) except Exception as e: return 0, f"File failed to parse with icalendar: {e}" version = cal.get('VERSION') if version is None or str(version) != '2.0': return 0, f"VERSION is not 2.0, got {version}" prodid = cal.get('PRODID') if prodid is None or str(prodid) != spec['prodid']: return 0, f"PRODID mismatch: expected {spec['prodid']!r}, got {prodid!r}" events = [c for c in cal.walk() if c.name == 'VEVENT'] if len(events) != 1: return 0, f"Expected exactly one VEVENT, found {len(events)}" event = events[0] uid = event.get('UID') if uid is None or str(uid) != spec['uid']: return 0, f"UID mismatch: expected {spec['uid']!r}, got {uid!r}" summary = event.get('SUMMARY') if summary is None or str(summary) != spec['summary']: return 0, f"SUMMARY mismatch: expected {spec['summary']!r}, got {summary!r}" location = event.get('LOCATION') if location is None or str(location) != spec['location']: return 0, f"LOCATION mismatch: expected {spec['location']!r}, got {location!r}" dtstart_prop = event.get('DTSTART') dtend_prop = event.get('DTEND') if dtstart_prop is None or dtend_prop is None: return 0, "Missing DTSTART or DTEND" dtstart_dt = dtstart_prop.dt dtend_dt = dtend_prop.dt expected_start = datetime.strptime(spec['start'], '%Y%m%dT%H%M%SZ').replace(tzinfo=timezone.utc) expected_end = datetime.strptime(spec['end'], '%Y%m%dT%H%M%SZ').replace(tzinfo=timezone.utc) if not isinstance(dtstart_dt, datetime) or dtstart_dt.tzinfo is None or dtstart_dt.utcoffset() != timedelta(0): return 0, "DTSTART is not a UTC datetime" if dtstart_dt != expected_start: return 0, f"DTSTART mismatch: expected {expected_start}, got {dtstart_dt}" if not isinstance(dtend_dt, datetime) or dtend_dt.tzinfo is None or dtend_dt.utcoffset() != timedelta(0): return 0, "DTEND is not a UTC datetime" if dtend_dt != expected_end: return 0, f"DTEND mismatch: expected {expected_end}, got {dtend_dt}" dtstart_raw = dtstart_prop.to_ical() if isinstance(dtstart_raw, bytes): dtstart_raw = dtstart_raw.decode() if not dtstart_raw.endswith('Z'): return 0, "DTSTART does not end with Z (not a UTC stamp)" dtend_raw = dtend_prop.to_ical() if isinstance(dtend_raw, bytes): dtend_raw = dtend_raw.decode() if not dtend_raw.endswith('Z'): return 0, "DTEND does not end with Z (not a UTC stamp)" rrule = event.get('RRULE') if rrule is None: return 0, "Missing RRULE" freq = rrule.get('FREQ') if freq != ['WEEKLY']: return 0, f"RRULE FREQ is not WEEKLY, got {freq}" byday = rrule.get('BYDAY') if byday is None: return 0, "RRULE missing BYDAY" if set(byday) != set(spec['byday']): return 0, f"RRULE BYDAY mismatch: expected {spec['byday']}, got {byday}" count = rrule.get('COUNT') if count is None or int(list(count)[0]) != int(spec['count']): return 0, f"RRULE COUNT mismatch: expected {spec['count']}, got {count}" if 'UNTIL' in rrule: return 0, "RRULE should not have UNTIL when COUNT is specified" alarms = [c for c in event.walk() if c.name == 'VALARM'] if len(alarms) != 1: return 0, f"Expected exactly one VALARM, found {len(alarms)}" alarm = alarms[0] action = alarm.get('ACTION') if action is None or str(action) != 'DISPLAY': return 0, f"VALARM ACTION is not DISPLAY, got {action}" trigger = alarm.get('TRIGGER') if trigger is None: return 0, "VALARM missing TRIGGER" trigger_dt = trigger.dt if not isinstance(trigger_dt, timedelta): return 0, "TRIGGER is not a duration (timedelta)" expected_trigger = timedelta(minutes=-int(spec['alarm_minutes'])) if trigger_dt != expected_trigger: return 0, f"TRIGGER mismatch: expected {expected_trigger}, got {trigger_dt}" return 1, "ok" # ---- spoilers (the mutation gate) import re def _read(path): with open(path, 'rb') as f: return f.read() def _write(path, data): with open(path, 'wb') as f: f.write(data) def spoil_prodid(path): data = _read(path).decode('utf-8') lines = data.split('\r\n') new_lines = [] for line in lines: if line.startswith('PRODID:'): new_lines.append('PRODID:-//Broken//Spoiler//EN') else: new_lines.append(line) new_data = '\r\n'.join(new_lines) out_path = path + '.spoil_prodid.ics' _write(out_path, new_data.encode('utf-8')) return out_path def spoil_remove_alarm(path): data = _read(path).decode('utf-8') lines = data.split('\r\n') out_lines = [] skip = False for line in lines: if line.startswith('BEGIN:VALARM'): skip = True continue if line.startswith('END:VALARM'): skip = False continue if not skip: out_lines.append(line) new_data = '\r\n'.join(out_lines) out_path = path + '.spoil_noalarm.ics' _write(out_path, new_data.encode('utf-8')) return out_path def spoil_count(path): data = _read(path).decode('utf-8') new_data = re.sub( r'(RRULE:[^\r\n]*COUNT=)(\d+)', lambda m: m.group(1) + str(int(m.group(2)) + 7), data ) out_path = path + '.spoil_count.ics' _write(out_path, new_data.encode('utf-8')) return out_path def spoil_lf_line_endings(path): data = _read(path) new_data = data.replace(b'\r\n', b'\n') out_path = path + '.spoil_lf.ics' _write(out_path, new_data) return out_path def spoil_dtstart_not_utc(path): data = _read(path).decode('utf-8') new_data = re.sub(r'(DTSTART:\d{8}T\d{6})Z', r'\1', data) out_path = path + '.spoil_dtstart.ics' _write(out_path, new_data.encode('utf-8')) return out_path def spoil_alarm_action(path): data = _read(path).decode('utf-8') new_data = data.replace('ACTION:DISPLAY', 'ACTION:AUDIO') out_path = path + '.spoil_action.ics' _write(out_path, new_data.encode('utf-8')) return out_path SPOILERS = [ ("prodid_mismatch", spoil_prodid), ("missing_alarm", spoil_remove_alarm), ("wrong_count", spoil_count), ("bare_lf_endings", spoil_lf_line_endings), ("dtstart_not_utc", spoil_dtstart_not_utc), ("wrong_alarm_action", spoil_alarm_action), ] # ---- variants (spec generator, gated: every generated spec builds and passes) import random import copy def make_specs(n: int, seed: int) -> list: rnd = random.Random(seed) vendors = ["Neruva", "Acme", "Globex", "Initech", "Umbrella", "Stark", "Wayne", "Hooli"] products = ["Commons", "Scheduler", "Planner", "Sync", "Cal", "Events"] summaries = [ "Team standup", "Design review", "Sprint planning", "1:1 sync", "Retro", "Client call", "Sales sync", "Ops sync" ] locations = [ "Room 2A", "Zoom", "Room 5", "HQ", "Teams", "Google Meet", "Conference Room B", "Remote" ] weekdays_all = ["MO", "TU", "WE", "TH", "FR", "SA", "SU"] specs = [] for _ in range(n): vendor = rnd.choice(vendors) product = rnd.choice(products) prodid = f"-//{vendor}//{product}//EN" uid_prefix = rnd.choice(["standup", "review", "sync", "meet", "call"]) uid = f"{uid_prefix}-{rnd.randint(1, 9999):04d}@{vendor.lower()}.example" summary = rnd.choice(summaries) location = rnd.choice(locations) year = rnd.randint(2025, 2027) month = rnd.randint(1, 12) day = rnd.randint(1, 28) hour = rnd.randint(0, 22) minute = rnd.choice([0, 15, 30, 45]) duration_minutes = rnd.choice([15, 30, 45, 60]) start_str = f"{year:04d}{month:02d}{day:02d}T{hour:02d}{minute:02d}00Z" total_minutes = hour * 60 + minute + duration_minutes end_hour = (total_minutes // 60) % 24 end_minute = total_minutes % 60 # since hour<=22, minute<=45, duration<=60 -> total_minutes < 1440, no day overflow end_str = f"{year:04d}{month:02d}{day:02d}T{end_hour:02d}{end_minute:02d}00Z" num_days = rnd.randint(1, 3) byday = rnd.sample(weekdays_all, num_days) count = rnd.randint(4, 20) alarm_minutes = rnd.choice([5, 10, 15, 30, 60]) spec = { "prodid": prodid, "uid": uid, "summary": summary, "location": location, "start": start_str, "end": end_str, "byday": byday, "count": count, "alarm_minutes": alarm_minutes, } specs.append(copy.deepcopy(spec)) return specs